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

Last change on this file since 257 was 257, checked in by Sam Hocevar, 17 years ago
  • src/: + Doxygenated public functions.
  • Property svn:keywords set to Id
File size: 12.7 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 257 2003-12-18 00:11:52Z 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;
66unsigned int _caca_height;
67
68/*
69 * Local variables
70 */
71#if defined(USE_NCURSES)
72static int _caca_attr[16*16];
73#endif
74
75#if defined(USE_CONIO)
76static struct text_info ti;
77static char *_caca_screen;
78#endif
79
80#if defined(USE_X11)
81Display *_caca_dpy;
82Window _caca_window;
83GC _caca_gc;
84#endif
85
86static char *_caca_empty_line;
87static char *_caca_scratch_line;
88
89static unsigned int _caca_delay;
90static unsigned int _caca_rendertime;
91
92static enum caca_color _caca_fgcolor = CACA_COLOR_LIGHTGRAY;
93static enum caca_color _caca_bgcolor = CACA_COLOR_BLACK;
94
95/**
96 * \brief Set the default colour pair.
97 *
98 * \param fgcolor The desired foreground colour.
99 * \param bgcolor The desired background colour.
100 * \return void
101 */
102void caca_set_color(enum caca_color fgcolor, enum caca_color bgcolor)
103{
104    if(fgcolor < 0 || fgcolor > 15 || bgcolor < 0 || bgcolor > 15)
105        return;
106
107    _caca_fgcolor = fgcolor;
108    _caca_bgcolor = bgcolor;
109#if defined(USE_SLANG)
110    SLsmg_set_color((bgcolor + 16 * fgcolor) /*% 128*/);
111#elif defined(USE_NCURSES)
112    attrset(_caca_attr[fgcolor + 16 * bgcolor]);
113#elif defined(USE_CONIO)
114    textbackground(bgcolor);
115    textcolor(fgcolor);
116#elif defined(USE_X11)
117    /* FIXME */
118#endif
119}
120
121/**
122 * \brief Get the current foreground colour.
123 *
124 * \return The current foreground colour.
125 */
126enum caca_color caca_get_fg_color(void)
127{
128    return _caca_fgcolor;
129}
130
131/**
132 * \brief Get the current background colour.
133 *
134 * \return The current background colour.
135 */
136enum caca_color caca_get_bg_color(void)
137{
138    return _caca_bgcolor;
139}
140
141/**
142 * \brief Print a character at given coordinates.
143 *
144 * \param x The X coordinate of the character.
145 * \param y The Y coordinate of the character.
146 * \param c The character to print.
147 * \return void
148 */
149void caca_putchar(int x, int y, char c)
150{
151#if defined(USE_CONIO)
152    char *data;
153#endif
154    if(x < 0 || x >= (int)_caca_width ||
155       y < 0 || y >= (int)_caca_height)
156        return;
157
158#if defined(USE_SLANG)
159    SLsmg_gotorc(y, x);
160    SLsmg_write_char(c);
161#elif defined(USE_NCURSES)
162    move(y, x);
163    addch(c);
164#elif defined(USE_CONIO)
165    data = _caca_screen + 2 * (x + y * _caca_width);
166    data[0] = c;
167    data[1] = (_caca_bgcolor << 4) | _caca_fgcolor;
168//    gotoxy(x + 1, y + 1);
169//    putch(c);
170#elif defined(USE_X11)
171    /* FIXME */
172#endif
173}
174
175/**
176 * \brief Print a string at given coordinates.
177 *
178 * \param x The X coordinate of the string.
179 * \param y The Y coordinate of the string.
180 * \param s The string to print.
181 * \return void
182 */
183void caca_putstr(int x, int y, const char *s)
184{
185    unsigned int len;
186
187    if(y < 0 || y >= (int)_caca_height || x >= (int)_caca_width)
188        return;
189
190    len = strlen(s);
191
192    if(x < 0)
193    {
194        len -= -x;
195        if(len < 0)
196            return;
197        s += -x;
198        x = 0;
199    }
200
201    if(x + len >= _caca_width)
202    {
203        memcpy(_caca_scratch_line, s, _caca_width - x);
204        _caca_scratch_line[_caca_width - x] = '\0';
205        s = _caca_scratch_line;
206    }
207
208#if defined(USE_SLANG)
209    SLsmg_gotorc(y, x);
210    SLsmg_write_string((char *)(intptr_t)s);
211#elif defined(USE_NCURSES)
212    move(y, x);
213    addstr(s);
214#elif defined(USE_CONIO)
215    char *buf = _caca_screen + 2 * (x + y * _caca_width);
216    while(*s)
217    {
218        *buf++ = *s++;
219        *buf++ = (_caca_bgcolor << 4) | _caca_fgcolor;
220    }
221//    gotoxy(x + 1, y + 1);
222//    cputs(s);
223#elif defined(USE_X11)
224    /* FIXME */
225#endif
226}
227
228/**
229 * \brief Format a string at given coordinates.
230 *
231 * \param x The X coordinate of the string.
232 * \param y The Y coordinate of the string.
233 * \param format The format string to print.
234 * \param ... Arguments to the format string.
235 * \return void
236 */
237void caca_printf(int x, int y, const char *format, ...)
238{
239    char tmp[BUFSIZ];
240    char *buf = tmp;
241    va_list args;
242
243    if(y < 0 || y >= (int)_caca_height || x >= (int)_caca_width)
244        return;
245
246    if(_caca_width - x + 1 > BUFSIZ)
247        buf = malloc(_caca_width - x + 1);
248
249    va_start(args, format);
250#if defined(HAVE_VSNPRINTF)
251    vsnprintf(buf, _caca_width - x + 1, format, args);
252#else
253    vsprintf(buf, format, args);
254#endif
255    buf[_caca_width - x] = '\0';
256    va_end(args);
257
258    caca_putstr(x, y, buf);
259
260    if(buf != tmp)
261        free(buf);
262}
263
264/**
265 * \brief Clear the screen.
266 *
267 * \return void
268 */
269void caca_clear(void)
270{
271    enum caca_color oldfg = caca_get_fg_color();
272    enum caca_color oldbg = caca_get_bg_color();
273    int y = _caca_height;
274
275    caca_set_color(CACA_COLOR_LIGHTGRAY, CACA_COLOR_BLACK);
276
277    /* We could use SLsmg_cls() etc., but drawing empty lines is much faster */
278    while(y--)
279        caca_putstr(0, y, _caca_empty_line);
280
281    caca_set_color(oldfg, oldbg);
282}
283
284int _caca_init_graphics(void)
285{
286#if defined(USE_SLANG)
287    /* See SLang ref., 5.4.4. */
288    static char *slang_colors[16] =
289    {
290        /* Standard colours */
291        "black",
292        "blue",
293        "green",
294        "cyan",
295        "red",
296        "magenta",
297        "brown",
298        "lightgray",
299        /* Bright colours */
300        "gray",
301        "brightblue",
302        "brightgreen",
303        "brightcyan",
304        "brightred",
305        "brightmagenta",
306        "yellow",
307        "white",
308    };
309
310    int fg, bg;
311
312    for(fg = 0; fg < 16; fg++)
313        for(bg = 0; bg < 16; bg++)
314        {
315            int i = bg + 16 * fg;
316            SLtt_set_color(i, NULL, slang_colors[fg], slang_colors[bg]);
317        }
318
319    /* Disable alt charset support so that we get all 256 colour pairs */
320    SLtt_Has_Alt_Charset = 0;
321
322    _caca_width = SLtt_Screen_Cols;
323    _caca_height = SLtt_Screen_Rows;
324
325#elif defined(USE_NCURSES)
326    static int curses_colors[] =
327    {
328        /* Standard curses colours */
329        COLOR_BLACK,
330        COLOR_BLUE,
331        COLOR_GREEN,
332        COLOR_CYAN,
333        COLOR_RED,
334        COLOR_MAGENTA,
335        COLOR_YELLOW,
336        COLOR_WHITE,
337        /* Extra values for xterm-16color */
338        COLOR_BLACK + 8,
339        COLOR_BLUE + 8,
340        COLOR_GREEN + 8,
341        COLOR_CYAN + 8,
342        COLOR_RED + 8,
343        COLOR_MAGENTA + 8,
344        COLOR_YELLOW + 8,
345        COLOR_WHITE + 8
346    };
347
348    int fg, bg, max;
349
350    /* Activate colour */
351    start_color();
352
353    /* If COLORS == 16, it means the terminal supports full bright colours
354     * using setab and setaf (will use \e[90m \e[91m etc. for colours >= 8),
355     * we can build 16*16 colour pairs.
356     * If COLORS == 8, it means the terminal does not know about bright
357     * colours and we need to get them through A_BOLD and A_BLINK (\e[1m
358     * and \e[5m). We can only build 8*8 colour pairs. */
359    max = COLORS >= 16 ? 16 : 8;
360
361    for(bg = 0; bg < max; bg++)
362        for(fg = 0; fg < max; fg++)
363        {
364            /* Use ((max + 7 - fg) % max) instead of fg so that colour 0
365             * is light gray on black, since some terminals don't like
366             * this colour pair to be redefined. */
367            int col = ((max + 7 - fg) % max) + max * bg;
368            init_pair(col, curses_colors[fg], curses_colors[bg]);
369            _caca_attr[fg + 16 * bg] = COLOR_PAIR(col);
370
371            if(max == 8)
372            {
373                /* Bright fg on simple bg */
374                _caca_attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
375                /* Simple fg on bright bg */
376                _caca_attr[fg + 16 * (bg + 8)] = A_BLINK | COLOR_PAIR(col);
377                /* Bright fg on bright bg */
378                _caca_attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
379                                                             | COLOR_PAIR(col);
380            }
381        }
382
383    _caca_width = COLS;
384    _caca_height = LINES;
385
386#elif defined(USE_CONIO)
387    gettextinfo(&ti);
388    _caca_screen = malloc(2 * ti.screenwidth * ti.screenheight);
389    if(_caca_screen == NULL)
390        return -1;
391#   if defined(SCREENUPDATE_IN_PC_H)
392    ScreenRetrieve(_caca_screen);
393#   else
394    /* FIXME */
395#   endif
396    _caca_width = ti.screenwidth;
397    _caca_height = ti.screenheight;
398
399#elif defined(USE_X11)
400    int black_color;
401    int white_color;
402
403    _caca_dpy = XOpenDisplay(NULL);
404    if(_caca_dpy == NULL)
405        return -1;
406
407    black_color = BlackPixel(_caca_dpy, DefaultScreen(_caca_dpy));
408    white_color = WhitePixel(_caca_dpy, DefaultScreen(_caca_dpy));
409
410    _caca_window = XCreateSimpleWindow(_caca_dpy, DefaultRootWindow(_caca_dpy),
411                                       0, 0, 400, 300, 0,
412                                       black_color, black_color);
413    XSelectInput(_caca_dpy, _caca_window, StructureNotifyMask);
414    XMapWindow(_caca_dpy, _caca_window);
415
416    _caca_gc = XCreateGC(_caca_dpy, _caca_window, 0, NULL);
417    XSetForeground(_caca_dpy, _caca_gc, white_color);
418
419    for(;;)
420    {
421        XEvent event;
422        XNextEvent(_caca_dpy, &event);
423        if (event.type == MapNotify)
424            break;
425    }
426
427    XSelectInput(_caca_dpy, _caca_window, KeyPressMask);
428
429    /* FIXME */
430    _caca_width = 80;
431    _caca_height = 24;
432
433    XSync(_caca_dpy, False);
434
435#endif
436    _caca_empty_line = malloc(_caca_width + 1);
437    memset(_caca_empty_line, ' ', _caca_width);
438    _caca_empty_line[_caca_width] = '\0';
439
440    _caca_scratch_line = malloc(_caca_width + 1);
441
442    _caca_delay = 0;
443    _caca_rendertime = 0;
444
445    return 0;
446}
447
448int _caca_end_graphics(void)
449{
450#if defined(USE_SLANG)
451    /* Nothing to do */
452#elif defined(USE_NCURSES)
453    /* Nothing to do */
454#elif defined(USE_CONIO)
455    free(_caca_screen);
456#elif defined(USE_X11)
457    XSync(_caca_dpy, False);
458    XFreeGC(_caca_dpy, _caca_gc);
459    XUnmapWindow(_caca_dpy, _caca_window);
460    XDestroyWindow(_caca_dpy, _caca_window);
461    XCloseDisplay(_caca_dpy);
462#endif
463    free(_caca_empty_line);
464
465    return 0;
466}
467
468/**
469 * \brief Set the refresh delay.
470 *
471 * \param usec The refresh delay in microseconds.
472 * \return void
473 */
474void caca_set_delay(unsigned int usec)
475{
476    _caca_delay = usec;
477}
478
479/**
480 * \brief Get the average rendering time.
481 *
482 * \return The render time in microseconds.
483 */
484unsigned int caca_get_rendertime(void)
485{
486    return _caca_rendertime;
487}
488
489static unsigned int _caca_getticks(void)
490{
491    static unsigned int last_sec = 0, last_usec = 0;
492
493    struct timeval tv;
494    unsigned int ticks = 0;
495
496    gettimeofday(&tv, NULL);
497
498    if(last_sec != 0)
499    {
500        ticks = (tv.tv_sec - last_sec) * 1000000 + (tv.tv_usec - last_usec);
501    }
502
503    last_sec = tv.tv_sec;
504    last_usec = tv.tv_usec;
505
506    return ticks;
507}
508
509/**
510 * \brief Flush pending changes and redraw the screen.
511 *
512 * \return void
513 */
514void caca_refresh(void)
515{
516#define IDLE_USEC 10000
517    static int lastticks = 0;
518    int ticks = lastticks + _caca_getticks();
519
520#if defined(USE_SLANG)
521    SLsmg_refresh();
522#elif defined(USE_NCURSES)
523    refresh();
524#elif defined(USE_CONIO)
525#   if defined(SCREENUPDATE_IN_PC_H)
526    ScreenUpdate(_caca_screen);
527#   else
528    /* FIXME */
529#   endif
530#elif defined(USE_X11)
531    /* FIXME */
532    XFlush(_caca_dpy);
533#endif
534
535    /* Wait until _caca_delay + time of last call */
536    ticks += _caca_getticks();
537    for(; ticks + IDLE_USEC < (int)_caca_delay; ticks += _caca_getticks())
538        usleep(IDLE_USEC);
539
540    /* Update the sliding mean of the render time */
541    _caca_rendertime = (7 * _caca_rendertime + ticks) / 8;
542
543    lastticks = ticks - _caca_delay;
544
545    /* If we drifted too much, it's bad, bad, bad. */
546    if(lastticks > (int)_caca_delay)
547        lastticks = 0;
548}
549
Note: See TracBrowser for help on using the repository browser.