source: libcaca/trunk/caca/canvas.c @ 3448

Last change on this file since 3448 was 3448, checked in by Sam Hocevar, 11 years ago

Finish the dirty rectangle architecture. They are now reliable in the sense
that anything outside the dirty rectangle is guaranteed to be unchanged, but
we currently mark far too many cells as dirty. This must be optimised.

  • Property svn:keywords set to Id
File size: 19.8 KB
Line 
1/*
2 *  libcaca       Colour ASCII-Art library
3 *  Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: canvas.c 3448 2009-05-14 00:18:23Z sam $
7 *
8 *  This library is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15/*
16 *  This file contains the main functions used by \e libcaca applications
17 *  to initialise a drawing context.
18 */
19
20#include "config.h"
21
22#if !defined(__KERNEL__)
23#   include <stdio.h>
24#   include <stdlib.h>
25#   include <string.h>
26#   include <time.h>
27#   include <sys/types.h>
28#   if defined(HAVE_UNISTD_H)
29#       include <unistd.h>
30#   endif
31#endif
32
33#include "caca.h"
34#include "caca_internals.h"
35
36static int caca_resize(caca_canvas_t *, int, int);
37
38/** \brief Initialise a \e libcaca canvas.
39 *
40 *  Initialise internal \e libcaca structures and the backend that will
41 *  be used for subsequent graphical operations. It must be the first
42 *  \e libcaca function to be called in a function. caca_free_canvas()
43 *  should be called at the end of the program to free all allocated resources.
44 *
45 *  Both the cursor and the canvas' handle are initialised at the top-left
46 *  corner.
47 *
48 *  If an error occurs, NULL is returned and \b errno is set accordingly:
49 *  - \c EINVAL Specified width or height is invalid.
50 *  - \c ENOMEM Not enough memory for the requested canvas size.
51 *
52 *  \param width The desired canvas width
53 *  \param height The desired canvas height
54 *  \return A libcaca canvas handle upon success, NULL if an error occurred.
55 */
56caca_canvas_t * caca_create_canvas(int width, int height)
57{
58    caca_canvas_t *cv;
59
60    if(width < 0 || height < 0)
61    {
62        seterrno(EINVAL);
63        return NULL;
64    }
65
66    cv = malloc(sizeof(caca_canvas_t));
67
68    if(!cv)
69        goto nomem;
70
71    cv->refcount = 0;
72    cv->autoinc = 0;
73    cv->resize_callback = NULL;
74    cv->resize_data = NULL;
75
76    cv->frame = 0;
77    cv->framecount = 1;
78    cv->frames = malloc(sizeof(struct caca_frame));
79    if(!cv->frames)
80    {
81        free(cv);
82        goto nomem;
83    }
84
85    cv->frames[0].width = cv->frames[0].height = 0;
86    cv->frames[0].chars = NULL;
87    cv->frames[0].attrs = NULL;
88    cv->frames[0].x = cv->frames[0].y = 0;
89    cv->frames[0].handlex = cv->frames[0].handley = 0;
90    cv->frames[0].curattr = 0;
91    cv->frames[0].name = strdup("frame#00000000");
92
93    _caca_load_frame_info(cv);
94    caca_set_color_ansi(cv, CACA_DEFAULT, CACA_TRANSPARENT);
95
96    cv->dirty_xmin = 0;
97    cv->dirty_xmax = -1;
98    cv->dirty_ymin = 0;
99    cv->dirty_ymax = -1;
100
101    cv->ff = NULL;
102
103    if(caca_resize(cv, width, height) < 0)
104    {
105        int saved_errno = geterrno();
106        free(cv->frames[0].name);
107        free(cv->frames);
108        free(cv);
109        seterrno(saved_errno);
110        return NULL;
111    }
112
113    return cv;
114
115nomem:
116    seterrno(ENOMEM);
117    return NULL;
118}
119
120/** \brief Manage a canvas.
121 *
122 *  Lock a canvas to prevent it from being resized. If non-NULL,
123 *  the \e callback function pointer will be called upon each
124 *  \e caca_set_canvas_size call and if the returned value is zero, the
125 *  canvas resize request will be denied.
126 *
127 *  This function is only useful for display drivers such as the \e libcaca
128 *  library.
129 *
130 *  If an error occurs, -1 is returned and \b errno is set accordingly:
131 *  - \c EBUSY The canvas is already being managed.
132 *
133 *  \param cv A libcaca canvas.
134 *  \param callback An optional callback function pointer.
135 *  \param p The argument to be passed to \e callback.
136 *  \return 0 in case of success, -1 if an error occurred.
137 */
138int caca_manage_canvas(caca_canvas_t *cv, int (*callback)(void *), void *p)
139{
140    if(cv->refcount)
141    {
142        seterrno(EBUSY);
143        return -1;
144    }
145
146    cv->resize_callback = callback;
147    cv->resize_data = p;
148    cv->refcount = 1;
149
150    return 0;
151}
152
153/** \brief unmanage a canvas.
154 *
155 *  unlock a canvas previously locked by caca_manage_canvas(). for safety
156 *  reasons, the callback and callback data arguments must be the same as for
157 *  the caca_manage_canvas() call.
158 *
159 *  this function is only useful for display drivers such as the \e libcaca
160 *  library.
161 *
162 *  if an error occurs, -1 is returned and \b errno is set accordingly:
163 *  - \c einval the canvas is not managed, or the callback arguments do
164 *              not match.
165 *
166 *  \param cv a libcaca canvas.
167 *  \param callback the \e callback argument previously passed to
168 *                  caca_manage_canvas().
169 *  \param p the \e p argument previously passed to caca_manage_canvas().
170 *  \return 0 in case of success, -1 if an error occurred.
171 */
172int caca_unmanage_canvas(caca_canvas_t *cv, int (*callback)(void *), void *p)
173{
174    if(!cv->refcount
175        || cv->resize_callback != callback || cv->resize_data != p)
176    {
177        seterrno(EINVAL);
178        return -1;
179    }
180
181    cv->refcount = 0;
182
183    return 0;
184}
185
186/** \brief Resize a canvas.
187 *
188 *  Set the canvas' width and height, in character cells.
189 *
190 *  The contents of the canvas are preserved to the extent of the new
191 *  canvas size. Newly allocated character cells at the right and/or at
192 *  the bottom of the canvas are filled with spaces.
193 *
194 *  If as a result of the resize the cursor coordinates fall outside the
195 *  new canvas boundaries, they are readjusted. For instance, if the
196 *  current X cursor coordinate is 11 and the requested width is 10, the
197 *  new X cursor coordinate will be 10.
198 *
199 *  It is an error to try to resize the canvas if an output driver has
200 *  been attached to the canvas using caca_create_display(). You need to
201 *  remove the output driver using caca_free_display() before you can change
202 *  the canvas size again. However, the caca output driver can cause a
203 *  canvas resize through user interaction. See the caca_event() documentation
204 *  for more about this.
205 *
206 *  If an error occurs, -1 is returned and \b errno is set accordingly:
207 *  - \c EINVAL Specified width or height is invalid.
208 *  - \c EBUSY The canvas is in use by a display driver and cannot be resized.
209 *  - \c ENOMEM Not enough memory for the requested canvas size. If this
210 *    happens, the canvas handle becomes invalid and should not be used.
211 *
212 *  \param cv A libcaca canvas.
213 *  \param width The desired canvas width.
214 *  \param height The desired canvas height.
215 *  \return 0 in case of success, -1 if an error occurred.
216 */
217int caca_set_canvas_size(caca_canvas_t *cv, int width, int height)
218{
219    if(width < 0 || height < 0)
220    {
221        seterrno(EINVAL);
222        return -1;
223    }
224
225    if(cv->refcount && cv->resize_callback
226        && !cv->resize_callback(cv->resize_data))
227    {
228        seterrno(EBUSY);
229        return -1;
230    }
231
232    return caca_resize(cv, width, height);
233}
234
235/** \brief Get the canvas width.
236 *
237 *  Return the current canvas' width, in character cells.
238 *
239 *  This function never fails.
240 *
241 *  \param cv A libcaca canvas.
242 *  \return The canvas width.
243 */
244int caca_get_canvas_width(caca_canvas_t const *cv)
245{
246    return cv->width;
247}
248
249/** \brief Get the canvas height.
250 *
251 *  Returns the current canvas' height, in character cells.
252 *
253 *  This function never fails.
254 *
255 *  \param cv A libcaca canvas.
256 *  \return The canvas height.
257 */
258int caca_get_canvas_height(caca_canvas_t const *cv)
259{
260    return cv->height;
261}
262
263/** \brief Get the canvas character array.
264 *
265 *  Return the current canvas' internal character array. The array elements
266 *  consist in native endian 32-bit Unicode values as returned by
267 *  caca_get_char().
268 *
269 *  This function is only useful for display drivers such as the \e libcaca
270 *  library.
271 *
272 *  This function never fails.
273 *
274 *  \param cv A libcaca canvas.
275 *  \return The canvas character array.
276 */
277uint8_t const * caca_get_canvas_chars(caca_canvas_t const *cv)
278{
279    return (uint8_t const *)cv->chars;
280}
281
282/** \brief Get the canvas attribute array.
283 *
284 *  Returns the current canvas' internal attribute array. The array elements
285 *  consist in native endian 32-bit attribute values as returned by
286 *  caca_get_attr().
287 *
288 *  This function is only useful for display drivers such as the \e libcaca
289 *  library.
290 *
291 *  This function never fails.
292 *
293 *  \param cv A libcaca canvas.
294 *  \return The canvas attribute array.
295 */
296uint8_t const * caca_get_canvas_attrs(caca_canvas_t const *cv)
297{
298    return (uint8_t const *)cv->attrs;
299}
300
301/** \brief Get a canvas's dirty rectangle.
302 *
303 *  Get the canvas's dirty rectangle coordinates. The dirty rectangle is
304 *  the smallest area containing all the cells that have changed since it
305 *  was last reset.
306 *
307 *  The dirty rectangle is used internally by display drivers to optimise
308 *  rendering by avoiding to redraw the whole screen. Once the display driver
309 *  has rendered the canvas, it resets the dirty rectangle.
310 *
311 *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
312 *  the dirty rectangle is empty. It means that the canvas's contents have
313 *  not changed since the dirty rectangle was last reset.
314 *
315 *  FIXME: having only one dirty rectangle instead of a list of rectangles
316 *  is a severe limitation, but the potential gain does not yet look to be
317 *  worth the implementation complexity of a multiple-rectangle scheme.
318 *
319 *  This function never fails.
320 *
321 *  \param cv A libcaca canvas.
322 *  \param xmin A pointer to an integer where the leftmost edge of the
323 *              dirty rectangle will be stored.
324 *  \param xmax A pointer to an integer where the rightmost edge of the
325 *              dirty rectangle will be stored.
326 *  \param ymin A pointer to an integer where the topmost edge of the
327 *              dirty rectangle will be stored.
328 *  \param ymax A pointer to an integer where the bottommost edge of the
329 *              dirty rectangle will be stored.
330 *  \return This function always returns 0.
331 */
332int caca_get_dirty_rectangle(caca_canvas_t *cv, int *xmin, int *xmax,
333                             int *ymin, int *ymax)
334{
335    *xmin = cv->dirty_xmin;
336    *xmax = cv->dirty_xmax;
337    *ymin = cv->dirty_ymin;
338    *ymax = cv->dirty_ymax;
339
340    return 0;
341}
342
343/** \brief Add a dirty rectangle to the canvas's dirty rectangle.
344 *
345 *  Add an invalidating zone to the canvas's dirty rectangle. For more
346 *  information about the dirty rectangle, see caca_get_dirty_rectangle().
347 *
348 *  This function may be useful to force refresh of a given zone of the
349 *  canvas even if the dirty rectangle tracking indicates that it is
350 *  unchanged.
351 *
352 *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
353 *  the dirty rectangle is empty. They will be silently ignored.
354 *
355 *  This function never fails.
356 *
357 *  \param cv A libcaca canvas.
358 *  \param xmin The leftmost edge of the additional dirty rectangle.
359 *  \param xmax The rightmost edge of the additional dirty rectangle.
360 *  \param ymin The topmost edge of the additional dirty rectangle.
361 *  \param ymax The bottommost edge of the additional dirty rectangle.
362 *  \return This function always returns 0.
363 */
364int caca_add_dirty_rectangle(caca_canvas_t *cv, int xmin, int xmax,
365                             int ymin, int ymax)
366{
367    /* Ignore empty rectangles. */
368    if(xmin > xmax || ymin > ymax)
369        return 0;
370
371    /* Ignore out-of-bounds rectangles. */
372    if(xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
373        return 0;
374
375    if(xmin < cv->dirty_xmin)
376        cv->dirty_xmin = xmin;
377
378    if(xmax > cv->dirty_xmax)
379        cv->dirty_xmax = xmax;
380
381    if(ymin < cv->dirty_ymin)
382        cv->dirty_ymin = ymin;
383
384    if(ymax > cv->dirty_ymax)
385        cv->dirty_ymax = ymax;
386
387    return 0;
388}
389
390/** \brief Set a canvas's dirty rectangle.
391 *
392 *  Set the canvas's dirty rectangle coordinates. For more information
393 *  about the dirty rectangle, see caca_get_dirty_rectangle().
394 *
395 *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
396 *  the dirty rectangle is empty.
397 *
398 *  This function never fails.
399 *
400 *  \param cv A libcaca canvas.
401 *  \param xmin The leftmost edge of the desired dirty rectangle.
402 *  \param xmax The rightmost edge of the desired dirty rectangle.
403 *  \param ymin The topmost edge of the desired dirty rectangle.
404 *  \param ymax The bottommost edge of the desired dirty rectangle.
405 *  \return This function always returns 0.
406 */
407int caca_set_dirty_rectangle(caca_canvas_t *cv, int xmin, int xmax,
408                             int ymin, int ymax)
409{
410    /* Normalise values indicating an empty or out-of-bounds rectangle. */
411    if(xmin > xmax || ymin > ymax ||
412        xmax < 0 || xmin >= cv->width || ymax < 0 || ymin >= cv->height)
413    {
414        xmin = cv->width;
415        xmax = -1;
416        ymin = cv->height;
417        ymax = -1;
418    }
419
420    cv->dirty_xmin = xmin;
421    cv->dirty_xmax = xmax;
422    cv->dirty_ymin = ymin;
423    cv->dirty_ymax = ymax;
424
425    return 0;
426}
427
428/** \brief Free a \e libcaca canvas.
429 *
430 *  Free all resources allocated by caca_create_canvas(). The canvas
431 *  pointer becomes invalid and must no longer be used unless a new call
432 *  to caca_create_canvas() is made.
433 *
434 *  If an error occurs, -1 is returned and \b errno is set accordingly:
435 *  - \c EBUSY The canvas is in use by a display driver and cannot be freed.
436 *
437 *  \param cv A libcaca canvas.
438 *  \return 0 in case of success, -1 if an error occurred.
439 */
440int caca_free_canvas(caca_canvas_t *cv)
441{
442    int f;
443
444    if(cv->refcount)
445    {
446        seterrno(EBUSY);
447        return -1;
448    }
449
450    for(f = 0; f < cv->framecount; f++)
451    {
452        free(cv->frames[f].chars);
453        free(cv->frames[f].attrs);
454        free(cv->frames[f].name);
455    }
456
457    caca_canvas_set_figfont(cv, NULL);
458
459    free(cv->frames);
460    free(cv);
461
462    return 0;
463}
464
465/** \brief Generate a random integer within a range.
466 *
467 *  Generate a random integer within the given range.
468 *
469 *  This function never fails.
470 *
471 *  \param min The lower bound of the integer range.
472 *  \param max The upper bound of the integer range.
473 *  \return A random integer comprised between \p min  and \p max - 1
474 *  (inclusive).
475 */
476int caca_rand(int min, int max)
477{
478    static int need_init = 1;
479
480    if(need_init)
481    {
482        srand(getpid() + time(NULL));
483        need_init = 0;
484    }
485
486    return min + (int)((1.0 * (max - min)) * rand() / (RAND_MAX + 1.0));
487}
488
489
490/*
491 * XXX: The following functions are local.
492 */
493
494int caca_resize(caca_canvas_t *cv, int width, int height)
495{
496    int x, y, f, old_width, old_height, new_size, old_size;
497
498    old_width = cv->width;
499    old_height = cv->height;
500    old_size = old_width * old_height;
501
502    _caca_save_frame_info(cv);
503
504    cv->width = width;
505    cv->height = height;
506    new_size = width * height;
507
508    /* Step 1: if new area is bigger, resize the memory area now. */
509    if(new_size > old_size)
510    {
511        for(f = 0; f < cv->framecount; f++)
512        {
513            cv->frames[f].chars = realloc(cv->frames[f].chars,
514                                          new_size * sizeof(uint32_t));
515            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
516                                          new_size * sizeof(uint32_t));
517            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
518            {
519                seterrno(ENOMEM);
520                return -1;
521            }
522        }
523    }
524
525    /* Step 2: move line data if necessary. */
526    if(width == old_width)
527    {
528        /* Width did not change, which means we do not need to move data. */
529        ;
530    }
531    else if(width > old_width)
532    {
533        /* New width is bigger than old width, which means we need to
534         * copy lines starting from the bottom of the screen otherwise
535         * we will overwrite information. */
536        for(f = 0; f < cv->framecount; f++)
537        {
538            uint32_t *chars = cv->frames[f].chars;
539            uint32_t *attrs = cv->frames[f].attrs;
540
541            for(y = height < old_height ? height : old_height; y--; )
542            {
543                uint32_t attr = cv->frames[f].curattr;
544
545                for(x = old_width; x--; )
546                {
547                    chars[y * width + x] = chars[y * old_width + x];
548                    attrs[y * width + x] = attrs[y * old_width + x];
549                }
550
551                /* Zero the end of the line */
552                for(x = width - old_width; x--; )
553                {
554                    chars[y * width + old_width + x] = (uint32_t)' ';
555                    attrs[y * width + old_width + x] = attr;
556                }
557            }
558        }
559
560        caca_add_dirty_rectangle(cv, old_width, 0, width - 1, old_height - 1);
561    }
562    else
563    {
564        /* New width is smaller. Copy as many lines as possible. Ignore
565         * the first line, it is already in place. */
566        int lines = height < old_height ? height : old_height;
567
568        for(f = 0; f < cv->framecount; f++)
569        {
570            uint32_t *chars = cv->frames[f].chars;
571            uint32_t *attrs = cv->frames[f].attrs;
572
573            for(y = 1; y < lines; y++)
574            {
575                for(x = 0; x < width; x++)
576                {
577                    chars[y * width + x] = chars[y * old_width + x];
578                    attrs[y * width + x] = attrs[y * old_width + x];
579                }
580            }
581        }
582    }
583
584    /* Step 3: fill the bottom of the new screen if necessary. */
585    if(height > old_height)
586    {
587        for(f = 0; f < cv->framecount; f++)
588        {
589            uint32_t *chars = cv->frames[f].chars;
590            uint32_t *attrs = cv->frames[f].attrs;
591            uint32_t attr = cv->frames[f].curattr;
592
593            /* Zero the bottom of the screen */
594            for(x = (height - old_height) * width; x--; )
595            {
596                chars[old_height * width + x] = (uint32_t)' ';
597                attrs[old_height * width + x] = attr;
598            }
599        }
600
601        caca_add_dirty_rectangle(cv, 0, old_height, old_width - 1, height - 1);
602    }
603
604    /* XXX: technically we should not worry about the dirty rectangle in
605     * the bottom-right corner, because we only handle one dirty rectangle,
606     * but in case the API changes later, we make sure this is handled. */
607    if(width > old_width && height > old_height)
608        caca_add_dirty_rectangle(cv, old_width, old_height,
609                                 width - 1, height - 1);
610
611    /* Step 4: if new area is smaller, resize memory area now. */
612    if(new_size < old_size)
613    {
614        for(f = 0; f < cv->framecount; f++)
615        {
616            cv->frames[f].chars = realloc(cv->frames[f].chars,
617                                          new_size * sizeof(uint32_t));
618            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
619                                          new_size * sizeof(uint32_t));
620            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
621            {
622                seterrno(ENOMEM);
623                return -1;
624            }
625        }
626    }
627
628    /* Set new size */
629    for(f = 0; f < cv->framecount; f++)
630    {
631        if(cv->frames[f].x > (int)width)
632            cv->frames[f].x = width;
633        if(cv->frames[f].y > (int)height)
634            cv->frames[f].y = height;
635
636        cv->frames[f].width = width;
637        cv->frames[f].height = height;
638    }
639
640    /* Reset the current frame shortcuts */
641    _caca_load_frame_info(cv);
642
643    return 0;
644}
645
646/*
647 * XXX: The following functions are aliases.
648 */
649
650cucul_canvas_t * cucul_create_canvas(int, int) CACA_ALIAS(caca_create_canvas);
651int cucul_manage_canvas(cucul_canvas_t *, int (*)(void *), void *)
652    CACA_ALIAS(caca_manage_canvas);
653int cucul_unmanage_canvas(cucul_canvas_t *, int (*)(void *), void *)
654    CACA_ALIAS(caca_unmanage_canvas);
655int cucul_set_canvas_size(cucul_canvas_t *, int, int)
656    CACA_ALIAS(caca_set_canvas_size);
657int cucul_get_canvas_width(cucul_canvas_t const *)
658    CACA_ALIAS(caca_get_canvas_width);
659int cucul_get_canvas_height(cucul_canvas_t const *)
660    CACA_ALIAS(caca_get_canvas_height);
661uint8_t const * cucul_get_canvas_chars(cucul_canvas_t const *)
662    CACA_ALIAS(caca_get_canvas_chars);
663uint8_t const * cucul_get_canvas_attrs(cucul_canvas_t const *)
664    CACA_ALIAS(caca_get_canvas_attrs);
665int cucul_free_canvas(cucul_canvas_t *) CACA_ALIAS(caca_free_canvas);
666int cucul_rand(int, int) CACA_ALIAS(caca_rand);
667
Note: See TracBrowser for help on using the repository browser.