source: libcaca/trunk/src/graphics.c @ 264

Last change on this file since 264 was 264, checked in by Sam Hocevar, 17 years ago
  • src/graphics: + Initialise default height at 32 (as in aalib).
  • Property svn:keywords set to Id
File size: 16.3 KB
Line 
1/*
2 *   libcaca       ASCII-Art library
3 *   Copyright (c) 2002, 2003 Sam Hocevar <sam@zoy.org>
4 *                 All Rights Reserved
5 *
6 *   This library is free software; you can redistribute it and/or
7 *   modify it under the terms of the GNU Lesser General Public
8 *   License as published by the Free Software Foundation; either
9 *   version 2 of the License, or (at your option) any later version.
10 *
11 *   This library is distributed in the hope that it will be useful,
12 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 *   Lesser General Public License for more details.
15 *
16 *   You should have received a copy of the GNU Lesser General Public
17 *   License along with this library; if not, write to the Free Software
18 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 *   02111-1307  USA
20 */
21
22/**  \file graphics.c
23 *   \version \$Id: graphics.c 264 2003-12-21 00:39:41Z sam $
24 *   \author Sam Hocevar <sam@zoy.org>
25 *   \brief Character drawing functions
26 *
27 *   This file contains character and string drawing functions.
28 */
29
30#include "config.h"
31
32#if defined(USE_SLANG)
33#   include <slang.h>
34#elif defined(USE_NCURSES)
35#   include <curses.h>
36#elif defined(USE_CONIO)
37#   include <conio.h>
38#   if defined(SCREENUPDATE_IN_PC_H)
39#       include <pc.h>
40#   endif
41#elif defined(USE_X11)
42#   include <X11/Xlib.h>
43#else
44#   error "no graphics library detected"
45#endif
46
47#ifdef HAVE_INTTYPES_H
48#   include <inttypes.h>
49#endif
50
51#include <stdio.h> /* BUFSIZ */
52#include <string.h>
53#include <stdlib.h>
54#include <unistd.h>
55#include <stdarg.h>
56#include <sys/time.h>
57#include <time.h>
58
59#include "caca.h"
60#include "caca_internals.h"
61
62/*
63 * Global variables
64 */
65unsigned int _caca_width = 0;
66unsigned int _caca_height = 0;
67
68/*
69 * Local variables
70 */
71#if defined(USE_NCURSES)
72static int ncurses_attr[16*16];
73#endif
74
75#if defined(USE_CONIO)
76static struct text_info conio_ti;
77static char *conio_screen;
78#endif
79
80#if defined(USE_X11)
81Display *x11_dpy;
82Window x11_window;
83static GC x11_gc;
84static Pixmap x11_pixmap;
85static int *x11_screen;
86static int x11_colors[16];
87static Font x11_font;
88static XFontStruct *x11_font_struct;
89static int x11_font_width, x11_font_height, x11_font_offset;
90#endif
91
92static char *_caca_empty_line;
93static char *_caca_scratch_line;
94
95static unsigned int _caca_delay;
96static unsigned int _caca_rendertime;
97
98static enum caca_color _caca_fgcolor = CACA_COLOR_LIGHTGRAY;
99static enum caca_color _caca_bgcolor = CACA_COLOR_BLACK;
100
101/**
102 * \brief Set the default colour pair.
103 *
104 * \param fgcolor The desired foreground colour.
105 * \param bgcolor The desired background colour.
106 * \return void
107 */
108void caca_set_color(enum caca_color fgcolor, enum caca_color bgcolor)
109{
110    if(fgcolor < 0 || fgcolor > 15 || bgcolor < 0 || bgcolor > 15)
111        return;
112
113    _caca_fgcolor = fgcolor;
114    _caca_bgcolor = bgcolor;
115#if defined(USE_SLANG)
116    SLsmg_set_color((bgcolor + 16 * fgcolor) /*% 128*/);
117#elif defined(USE_NCURSES)
118    attrset(ncurses_attr[fgcolor + 16 * bgcolor]);
119#elif defined(USE_CONIO)
120    textbackground(bgcolor);
121    textcolor(fgcolor);
122#elif defined(USE_X11)
123    /* FIXME */
124#endif
125}
126
127/**
128 * \brief Get the current foreground colour.
129 *
130 * \return The current foreground colour.
131 */
132enum caca_color caca_get_fg_color(void)
133{
134    return _caca_fgcolor;
135}
136
137/**
138 * \brief Get the current background colour.
139 *
140 * \return The current background colour.
141 */
142enum caca_color caca_get_bg_color(void)
143{
144    return _caca_bgcolor;
145}
146
147/**
148 * \brief Print a character at given coordinates.
149 *
150 * \param x The X coordinate of the character.
151 * \param y The Y coordinate of the character.
152 * \param c The character to print.
153 * \return void
154 */
155void caca_putchar(int x, int y, char c)
156{
157#if defined(USE_CONIO)
158    char *data;
159#endif
160    if(x < 0 || x >= (int)_caca_width ||
161       y < 0 || y >= (int)_caca_height)
162        return;
163
164#if defined(USE_SLANG)
165    SLsmg_gotorc(y, x);
166    SLsmg_write_char(c);
167#elif defined(USE_NCURSES)
168    move(y, x);
169    addch(c);
170#elif defined(USE_CONIO)
171    data = conio_screen + 2 * (x + y * _caca_width);
172    data[0] = c;
173    data[1] = (_caca_bgcolor << 4) | _caca_fgcolor;
174//    gotoxy(x + 1, y + 1);
175//    putch(c);
176#elif defined(USE_X11)
177    x11_screen[x + y * _caca_width] =
178        ((int)c << 8) | ((int)_caca_bgcolor << 4) | (int)_caca_fgcolor;
179#endif
180}
181
182/**
183 * \brief Print a string at given coordinates.
184 *
185 * \param x The X coordinate of the string.
186 * \param y The Y coordinate of the string.
187 * \param s The string to print.
188 * \return void
189 */
190void caca_putstr(int x, int y, const char *s)
191{
192#if defined(USE_CONIO)
193    char *buf;
194#elif defined(USE_X11)
195    int *buf;
196#endif
197    unsigned int len;
198
199    if(y < 0 || y >= (int)_caca_height || x >= (int)_caca_width)
200        return;
201
202    len = strlen(s);
203
204    if(x < 0)
205    {
206        len -= -x;
207        if(len < 0)
208            return;
209        s += -x;
210        x = 0;
211    }
212
213    if(x + len >= _caca_width)
214    {
215        memcpy(_caca_scratch_line, s, _caca_width - x);
216        _caca_scratch_line[_caca_width - x] = '\0';
217        s = _caca_scratch_line;
218    }
219
220#if defined(USE_SLANG)
221    SLsmg_gotorc(y, x);
222    SLsmg_write_string((char *)(intptr_t)s);
223#elif defined(USE_NCURSES)
224    move(y, x);
225    addstr(s);
226#elif defined(USE_CONIO)
227    buf = conio_screen + 2 * (x + y * _caca_width);
228    while(*s)
229    {
230        *buf++ = *s++;
231        *buf++ = (_caca_bgcolor << 4) | _caca_fgcolor;
232    }
233//    gotoxy(x + 1, y + 1);
234//    cputs(s);
235#elif defined(USE_X11)
236    buf = x11_screen + x + y * _caca_width;
237    while(*s)
238        *buf++ = ((int)*s++ << 8) | ((int)_caca_bgcolor << 4) | (int)_caca_fgcolor;
239#endif
240}
241
242/**
243 * \brief Format a string at given coordinates.
244 *
245 * \param x The X coordinate of the string.
246 * \param y The Y coordinate of the string.
247 * \param format The format string to print.
248 * \param ... Arguments to the format string.
249 * \return void
250 */
251void caca_printf(int x, int y, const char *format, ...)
252{
253    char tmp[BUFSIZ];
254    char *buf = tmp;
255    va_list args;
256
257    if(y < 0 || y >= (int)_caca_height || x >= (int)_caca_width)
258        return;
259
260    if(_caca_width - x + 1 > BUFSIZ)
261        buf = malloc(_caca_width - x + 1);
262
263    va_start(args, format);
264#if defined(HAVE_VSNPRINTF)
265    vsnprintf(buf, _caca_width - x + 1, format, args);
266#else
267    vsprintf(buf, format, args);
268#endif
269    buf[_caca_width - x] = '\0';
270    va_end(args);
271
272    caca_putstr(x, y, buf);
273
274    if(buf != tmp)
275        free(buf);
276}
277
278/**
279 * \brief Clear the screen.
280 *
281 * \return void
282 */
283void caca_clear(void)
284{
285    enum caca_color oldfg = caca_get_fg_color();
286    enum caca_color oldbg = caca_get_bg_color();
287    int y = _caca_height;
288
289    caca_set_color(CACA_COLOR_LIGHTGRAY, CACA_COLOR_BLACK);
290
291    /* We could use SLsmg_cls() etc., but drawing empty lines is much faster */
292    while(y--)
293        caca_putstr(0, y, _caca_empty_line);
294
295    caca_set_color(oldfg, oldbg);
296}
297
298int _caca_init_graphics(void)
299{
300#if defined(USE_SLANG)
301    /* See SLang ref., 5.4.4. */
302    static char *slang_colors[16] =
303    {
304        /* Standard colours */
305        "black",
306        "blue",
307        "green",
308        "cyan",
309        "red",
310        "magenta",
311        "brown",
312        "lightgray",
313        /* Bright colours */
314        "gray",
315        "brightblue",
316        "brightgreen",
317        "brightcyan",
318        "brightred",
319        "brightmagenta",
320        "yellow",
321        "white",
322    };
323
324    int fg, bg;
325
326    for(fg = 0; fg < 16; fg++)
327        for(bg = 0; bg < 16; bg++)
328        {
329            int i = bg + 16 * fg;
330            SLtt_set_color(i, NULL, slang_colors[fg], slang_colors[bg]);
331        }
332
333    /* Disable alt charset support so that we get all 256 colour pairs */
334    SLtt_Has_Alt_Charset = 0;
335
336    _caca_width = SLtt_Screen_Cols;
337    _caca_height = SLtt_Screen_Rows;
338
339#elif defined(USE_NCURSES)
340    static int curses_colors[] =
341    {
342        /* Standard curses colours */
343        COLOR_BLACK,
344        COLOR_BLUE,
345        COLOR_GREEN,
346        COLOR_CYAN,
347        COLOR_RED,
348        COLOR_MAGENTA,
349        COLOR_YELLOW,
350        COLOR_WHITE,
351        /* Extra values for xterm-16color */
352        COLOR_BLACK + 8,
353        COLOR_BLUE + 8,
354        COLOR_GREEN + 8,
355        COLOR_CYAN + 8,
356        COLOR_RED + 8,
357        COLOR_MAGENTA + 8,
358        COLOR_YELLOW + 8,
359        COLOR_WHITE + 8
360    };
361
362    int fg, bg, max;
363
364    /* Activate colour */
365    start_color();
366
367    /* If COLORS == 16, it means the terminal supports full bright colours
368     * using setab and setaf (will use \e[90m \e[91m etc. for colours >= 8),
369     * we can build 16*16 colour pairs.
370     * If COLORS == 8, it means the terminal does not know about bright
371     * colours and we need to get them through A_BOLD and A_BLINK (\e[1m
372     * and \e[5m). We can only build 8*8 colour pairs. */
373    max = COLORS >= 16 ? 16 : 8;
374
375    for(bg = 0; bg < max; bg++)
376        for(fg = 0; fg < max; fg++)
377        {
378            /* Use ((max + 7 - fg) % max) instead of fg so that colour 0
379             * is light gray on black, since some terminals don't like
380             * this colour pair to be redefined. */
381            int col = ((max + 7 - fg) % max) + max * bg;
382            init_pair(col, curses_colors[fg], curses_colors[bg]);
383            ncurses_attr[fg + 16 * bg] = COLOR_PAIR(col);
384
385            if(max == 8)
386            {
387                /* Bright fg on simple bg */
388                ncurses_attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
389                /* Simple fg on bright bg */
390                ncurses_attr[fg + 16 * (bg + 8)] = A_BLINK | COLOR_PAIR(col);
391                /* Bright fg on bright bg */
392                ncurses_attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
393                                                        | COLOR_PAIR(col);
394            }
395        }
396
397    _caca_width = COLS;
398    _caca_height = LINES;
399
400#elif defined(USE_CONIO)
401    gettextinfo(&conio_ti);
402    conio_screen = malloc(2 * conio_ti.screenwidth
403                             * conio_ti.screenheight * sizeof(char));
404    if(conio_screen == NULL)
405        return -1;
406#   if defined(SCREENUPDATE_IN_PC_H)
407    ScreenRetrieve(conio_screen);
408#   else
409    /* FIXME */
410#   endif
411    _caca_width = conio_ti.screenwidth;
412    _caca_height = conio_ti.screenheight;
413
414#elif defined(USE_X11)
415    static int x11_palette[] =
416    {
417        /* Standard curses colours */
418        0, 0, 0,
419        0, 0, 32768,
420        0, 32768, 0,
421        0, 32768, 32768,
422        32768, 0, 0,
423        32768, 0, 32768,
424        32768, 32768, 0,
425        32768, 32768, 32768,
426        /* Extra values for xterm-16color */
427        16384, 16384, 16384,
428        16384, 16384, 65535,
429        16384, 65535, 16384,
430        16384, 65535, 65535,
431        65535, 16384, 16384,
432        65535, 16384, 65535,
433        65535, 65535, 16384,
434        65535, 65535, 65535,
435    };
436
437    Colormap colormap;
438    const char *font_name = "8x13bold";
439    int i;
440
441    if(getenv("CACA_WIDTH"))
442        _caca_width = atoi(getenv("CACA_WIDTH"));
443    if(!_caca_width)
444        _caca_width = 80;
445
446    if(getenv("CACA_HEIGHT"))
447        _caca_height = atoi(getenv("CACA_HEIGHT"));
448    if(!_caca_height)
449        _caca_height = 32;
450
451    x11_screen = malloc(_caca_width * _caca_height * sizeof(int));
452    if(x11_screen == NULL)
453        return -1;
454
455    x11_dpy = XOpenDisplay(NULL);
456    if(x11_dpy == NULL)
457    {
458        free(x11_screen);
459        return -1;
460    }
461
462    if(getenv("CACA_FONT"))
463        font_name = getenv("CACA_FONT");
464
465    x11_font = XLoadFont(x11_dpy, font_name);
466    if(!x11_font)
467    {
468        XCloseDisplay(x11_dpy);
469        free(x11_screen);
470        return -1;
471    }
472
473    x11_font_struct = XQueryFont(x11_dpy, x11_font);
474    if(!x11_font_struct)
475    {
476        XUnloadFont(x11_dpy, x11_font);
477        XCloseDisplay(x11_dpy);
478        free(x11_screen);
479        return -1;
480    }
481
482    x11_font_width = x11_font_struct->max_bounds.width;
483    x11_font_height = x11_font_struct->max_bounds.ascent
484                         + x11_font_struct->max_bounds.descent;
485    x11_font_offset = x11_font_struct->max_bounds.descent;
486
487    colormap = DefaultColormap(x11_dpy, DefaultScreen(x11_dpy));
488    for(i = 0; i < 16; i++)
489    {
490        XColor color;
491        color.red = x11_palette[i * 3];
492        color.green = x11_palette[i * 3 + 1];
493        color.blue = x11_palette[i * 3 + 2];
494        XAllocColor(x11_dpy, colormap, &color);
495        x11_colors[i] = color.pixel;
496    }
497
498    x11_window = XCreateSimpleWindow(x11_dpy, DefaultRootWindow(x11_dpy),
499                                       0, 0, _caca_width * x11_font_width,
500                                       _caca_height * x11_font_height, 0,
501                                       x11_colors[0], x11_colors[0]);
502    XSelectInput(x11_dpy, x11_window, StructureNotifyMask);
503    XMapWindow(x11_dpy, x11_window);
504
505    x11_gc = XCreateGC(x11_dpy, x11_window, 0, NULL);
506    XSetForeground(x11_dpy, x11_gc, x11_colors[15]);
507    XSetFont(x11_dpy, x11_gc, x11_font);
508
509    for(;;)
510    {
511        XEvent event;
512        XNextEvent(x11_dpy, &event);
513        if (event.type == MapNotify)
514            break;
515    }
516
517    XSelectInput(x11_dpy, x11_window, KeyPressMask);
518
519    XSync(x11_dpy, False);
520
521    x11_pixmap = XCreatePixmap(x11_dpy, x11_window,
522                               _caca_width * x11_font_width,
523                               _caca_height * x11_font_height,
524                               DefaultDepth(x11_dpy, DefaultScreen(x11_dpy)));
525#endif
526    _caca_empty_line = malloc(_caca_width + 1);
527    memset(_caca_empty_line, ' ', _caca_width);
528    _caca_empty_line[_caca_width] = '\0';
529
530    _caca_scratch_line = malloc(_caca_width + 1);
531
532    _caca_delay = 0;
533    _caca_rendertime = 0;
534
535    return 0;
536}
537
538int _caca_end_graphics(void)
539{
540#if defined(USE_SLANG)
541    /* Nothing to do */
542#elif defined(USE_NCURSES)
543    /* Nothing to do */
544#elif defined(USE_CONIO)
545    free(conio_screen);
546#elif defined(USE_X11)
547    XSync(x11_dpy, False);
548    XFreePixmap(x11_dpy, x11_pixmap);
549    XFreeFont(x11_dpy, x11_font_struct);
550    XFreeGC(x11_dpy, x11_gc);
551    XUnmapWindow(x11_dpy, x11_window);
552    XDestroyWindow(x11_dpy, x11_window);
553    XCloseDisplay(x11_dpy);
554    free(x11_screen);
555#endif
556    free(_caca_empty_line);
557
558    return 0;
559}
560
561/**
562 * \brief Set the refresh delay.
563 *
564 * \param usec The refresh delay in microseconds.
565 * \return void
566 */
567void caca_set_delay(unsigned int usec)
568{
569    _caca_delay = usec;
570}
571
572/**
573 * \brief Get the average rendering time.
574 *
575 * \return The render time in microseconds.
576 */
577unsigned int caca_get_rendertime(void)
578{
579    return _caca_rendertime;
580}
581
582static unsigned int _caca_getticks(void)
583{
584    static unsigned int last_sec = 0, last_usec = 0;
585
586    struct timeval tv;
587    unsigned int ticks = 0;
588
589    gettimeofday(&tv, NULL);
590
591    if(last_sec != 0)
592    {
593        ticks = (tv.tv_sec - last_sec) * 1000000 + (tv.tv_usec - last_usec);
594    }
595
596    last_sec = tv.tv_sec;
597    last_usec = tv.tv_usec;
598
599    return ticks;
600}
601
602/**
603 * \brief Flush pending changes and redraw the screen.
604 *
605 * \return void
606 */
607void caca_refresh(void)
608{
609#define IDLE_USEC 10000
610    static int lastticks = 0;
611    int ticks = lastticks + _caca_getticks();
612
613#if defined(USE_SLANG)
614    SLsmg_refresh();
615#elif defined(USE_NCURSES)
616    refresh();
617#elif defined(USE_CONIO)
618#   if defined(SCREENUPDATE_IN_PC_H)
619    ScreenUpdate(conio_screen);
620#   else
621    /* FIXME */
622#   endif
623#elif defined(USE_X11)
624    unsigned int x, y;
625
626    for(y = 0; y < _caca_height; y++)
627        for(x = 0; x < _caca_width; x++)
628        {
629            int item = x11_screen[x + y * _caca_width];
630            char data = item >> 8;
631            XSetForeground(x11_dpy, x11_gc, x11_colors[(item >> 4) & 0xf]);
632            XFillRectangle(x11_dpy, x11_pixmap, x11_gc,
633                           x * x11_font_width, y * x11_font_height,
634                           x11_font_width, x11_font_height);
635            XSetForeground(x11_dpy, x11_gc, x11_colors[item & 0xf]);
636            XDrawString(x11_dpy, x11_pixmap, x11_gc, x * x11_font_width,
637                        (y + 1) * x11_font_height - x11_font_offset, &data, 1);
638        }
639    XCopyArea(x11_dpy, x11_pixmap, x11_window, x11_gc, 0, 0,
640              _caca_width * x11_font_width, _caca_height * x11_font_height,
641              0, 0);
642    XFlush(x11_dpy);
643#endif
644
645    /* Wait until _caca_delay + time of last call */
646    ticks += _caca_getticks();
647    for(; ticks + IDLE_USEC < (int)_caca_delay; ticks += _caca_getticks())
648        usleep(IDLE_USEC);
649
650    /* Update the sliding mean of the render time */
651    _caca_rendertime = (7 * _caca_rendertime + ticks) / 8;
652
653    lastticks = ticks - _caca_delay;
654
655    /* If we drifted too much, it's bad, bad, bad. */
656    if(lastticks > (int)_caca_delay)
657        lastticks = 0;
658}
659
Note: See TracBrowser for help on using the repository browser.