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

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