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

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

Optimise the dirty rectangle handling by keeping track of the number of
currently dirty rectangles.

  • Property svn:keywords set to Id
File size: 15.4 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 3471 2009-05-19 00:51:55Z 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->ndirty = 0;
97    cv->ff = NULL;
98
99    if(caca_resize(cv, width, height) < 0)
100    {
101        int saved_errno = geterrno();
102        free(cv->frames[0].name);
103        free(cv->frames);
104        free(cv);
105        seterrno(saved_errno);
106        return NULL;
107    }
108
109    return cv;
110
111nomem:
112    seterrno(ENOMEM);
113    return NULL;
114}
115
116/** \brief Manage a canvas.
117 *
118 *  Lock a canvas to prevent it from being resized. If non-NULL,
119 *  the \e callback function pointer will be called upon each
120 *  \e caca_set_canvas_size call and if the returned value is zero, the
121 *  canvas resize request will be denied.
122 *
123 *  This function is only useful for display drivers such as the \e libcaca
124 *  library.
125 *
126 *  If an error occurs, -1 is returned and \b errno is set accordingly:
127 *  - \c EBUSY The canvas is already being managed.
128 *
129 *  \param cv A libcaca canvas.
130 *  \param callback An optional callback function pointer.
131 *  \param p The argument to be passed to \e callback.
132 *  \return 0 in case of success, -1 if an error occurred.
133 */
134int caca_manage_canvas(caca_canvas_t *cv, int (*callback)(void *), void *p)
135{
136    if(cv->refcount)
137    {
138        seterrno(EBUSY);
139        return -1;
140    }
141
142    cv->resize_callback = callback;
143    cv->resize_data = p;
144    cv->refcount = 1;
145
146    return 0;
147}
148
149/** \brief unmanage a canvas.
150 *
151 *  unlock a canvas previously locked by caca_manage_canvas(). for safety
152 *  reasons, the callback and callback data arguments must be the same as for
153 *  the caca_manage_canvas() call.
154 *
155 *  this function is only useful for display drivers such as the \e libcaca
156 *  library.
157 *
158 *  if an error occurs, -1 is returned and \b errno is set accordingly:
159 *  - \c einval the canvas is not managed, or the callback arguments do
160 *              not match.
161 *
162 *  \param cv a libcaca canvas.
163 *  \param callback the \e callback argument previously passed to
164 *                  caca_manage_canvas().
165 *  \param p the \e p argument previously passed to caca_manage_canvas().
166 *  \return 0 in case of success, -1 if an error occurred.
167 */
168int caca_unmanage_canvas(caca_canvas_t *cv, int (*callback)(void *), void *p)
169{
170    if(!cv->refcount
171        || cv->resize_callback != callback || cv->resize_data != p)
172    {
173        seterrno(EINVAL);
174        return -1;
175    }
176
177    cv->refcount = 0;
178
179    return 0;
180}
181
182/** \brief Resize a canvas.
183 *
184 *  Set the canvas' width and height, in character cells.
185 *
186 *  The contents of the canvas are preserved to the extent of the new
187 *  canvas size. Newly allocated character cells at the right and/or at
188 *  the bottom of the canvas are filled with spaces.
189 *
190 *  If as a result of the resize the cursor coordinates fall outside the
191 *  new canvas boundaries, they are readjusted. For instance, if the
192 *  current X cursor coordinate is 11 and the requested width is 10, the
193 *  new X cursor coordinate will be 10.
194 *
195 *  It is an error to try to resize the canvas if an output driver has
196 *  been attached to the canvas using caca_create_display(). You need to
197 *  remove the output driver using caca_free_display() before you can change
198 *  the canvas size again. However, the caca output driver can cause a
199 *  canvas resize through user interaction. See the caca_event() documentation
200 *  for more about this.
201 *
202 *  If an error occurs, -1 is returned and \b errno is set accordingly:
203 *  - \c EINVAL Specified width or height is invalid.
204 *  - \c EBUSY The canvas is in use by a display driver and cannot be resized.
205 *  - \c ENOMEM Not enough memory for the requested canvas size. If this
206 *    happens, the canvas handle becomes invalid and should not be used.
207 *
208 *  \param cv A libcaca canvas.
209 *  \param width The desired canvas width.
210 *  \param height The desired canvas height.
211 *  \return 0 in case of success, -1 if an error occurred.
212 */
213int caca_set_canvas_size(caca_canvas_t *cv, int width, int height)
214{
215    if(width < 0 || height < 0)
216    {
217        seterrno(EINVAL);
218        return -1;
219    }
220
221    if(cv->refcount && cv->resize_callback
222        && !cv->resize_callback(cv->resize_data))
223    {
224        seterrno(EBUSY);
225        return -1;
226    }
227
228    return caca_resize(cv, width, height);
229}
230
231/** \brief Get the canvas width.
232 *
233 *  Return the current canvas' width, in character cells.
234 *
235 *  This function never fails.
236 *
237 *  \param cv A libcaca canvas.
238 *  \return The canvas width.
239 */
240int caca_get_canvas_width(caca_canvas_t const *cv)
241{
242    return cv->width;
243}
244
245/** \brief Get the canvas height.
246 *
247 *  Returns the current canvas' height, in character cells.
248 *
249 *  This function never fails.
250 *
251 *  \param cv A libcaca canvas.
252 *  \return The canvas height.
253 */
254int caca_get_canvas_height(caca_canvas_t const *cv)
255{
256    return cv->height;
257}
258
259/** \brief Get the canvas character array.
260 *
261 *  Return the current canvas' internal character array. The array elements
262 *  consist in native endian 32-bit Unicode values as returned by
263 *  caca_get_char().
264 *
265 *  This function is only useful for display drivers such as the \e libcaca
266 *  library.
267 *
268 *  This function never fails.
269 *
270 *  \param cv A libcaca canvas.
271 *  \return The canvas character array.
272 */
273uint8_t const * caca_get_canvas_chars(caca_canvas_t const *cv)
274{
275    return (uint8_t const *)cv->chars;
276}
277
278/** \brief Get the canvas attribute array.
279 *
280 *  Returns the current canvas' internal attribute array. The array elements
281 *  consist in native endian 32-bit attribute values as returned by
282 *  caca_get_attr().
283 *
284 *  This function is only useful for display drivers such as the \e libcaca
285 *  library.
286 *
287 *  This function never fails.
288 *
289 *  \param cv A libcaca canvas.
290 *  \return The canvas attribute array.
291 */
292uint8_t const * caca_get_canvas_attrs(caca_canvas_t const *cv)
293{
294    return (uint8_t const *)cv->attrs;
295}
296
297/** \brief Free a \e libcaca canvas.
298 *
299 *  Free all resources allocated by caca_create_canvas(). The canvas
300 *  pointer becomes invalid and must no longer be used unless a new call
301 *  to caca_create_canvas() is made.
302 *
303 *  If an error occurs, -1 is returned and \b errno is set accordingly:
304 *  - \c EBUSY The canvas is in use by a display driver and cannot be freed.
305 *
306 *  \param cv A libcaca canvas.
307 *  \return 0 in case of success, -1 if an error occurred.
308 */
309int caca_free_canvas(caca_canvas_t *cv)
310{
311    int f;
312
313    if(cv->refcount)
314    {
315        seterrno(EBUSY);
316        return -1;
317    }
318
319    for(f = 0; f < cv->framecount; f++)
320    {
321        free(cv->frames[f].chars);
322        free(cv->frames[f].attrs);
323        free(cv->frames[f].name);
324    }
325
326    caca_canvas_set_figfont(cv, NULL);
327
328    free(cv->frames);
329    free(cv);
330
331    return 0;
332}
333
334/** \brief Generate a random integer within a range.
335 *
336 *  Generate a random integer within the given range.
337 *
338 *  This function never fails.
339 *
340 *  \param min The lower bound of the integer range.
341 *  \param max The upper bound of the integer range.
342 *  \return A random integer comprised between \p min  and \p max - 1
343 *  (inclusive).
344 */
345int caca_rand(int min, int max)
346{
347    static int need_init = 1;
348
349    if(need_init)
350    {
351        srand(getpid() + time(NULL));
352        need_init = 0;
353    }
354
355    return min + (int)((1.0 * (max - min)) * rand() / (RAND_MAX + 1.0));
356}
357
358
359/*
360 * XXX: The following functions are local.
361 */
362
363int caca_resize(caca_canvas_t *cv, int width, int height)
364{
365    int x, y, f, old_width, old_height, new_size, old_size;
366
367    old_width = cv->width;
368    old_height = cv->height;
369    old_size = old_width * old_height;
370
371    _caca_save_frame_info(cv);
372
373    cv->width = width;
374    cv->height = height;
375    new_size = width * height;
376
377    /* Step 1: if new area is bigger, resize the memory area now. */
378    if(new_size > old_size)
379    {
380        for(f = 0; f < cv->framecount; f++)
381        {
382            cv->frames[f].chars = realloc(cv->frames[f].chars,
383                                          new_size * sizeof(uint32_t));
384            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
385                                          new_size * sizeof(uint32_t));
386            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
387            {
388                seterrno(ENOMEM);
389                return -1;
390            }
391        }
392    }
393
394    /* Step 2: move line data if necessary. */
395    if(width == old_width)
396    {
397        /* Width did not change, which means we do not need to move data. */
398        ;
399    }
400    else if(width > old_width)
401    {
402        /* New width is bigger than old width, which means we need to
403         * copy lines starting from the bottom of the screen otherwise
404         * we will overwrite information. */
405        for(f = 0; f < cv->framecount; f++)
406        {
407            uint32_t *chars = cv->frames[f].chars;
408            uint32_t *attrs = cv->frames[f].attrs;
409
410            for(y = height < old_height ? height : old_height; y--; )
411            {
412                uint32_t attr = cv->frames[f].curattr;
413
414                for(x = old_width; x--; )
415                {
416                    chars[y * width + x] = chars[y * old_width + x];
417                    attrs[y * width + x] = attrs[y * old_width + x];
418                }
419
420                /* Zero the end of the line */
421                for(x = width - old_width; x--; )
422                {
423                    chars[y * width + old_width + x] = (uint32_t)' ';
424                    attrs[y * width + old_width + x] = attr;
425                }
426            }
427        }
428
429        caca_add_dirty_rectangle(cv, old_width, 0, width - 1, old_height - 1);
430    }
431    else
432    {
433        /* New width is smaller. Copy as many lines as possible. Ignore
434         * the first line, it is already in place. */
435        int lines = height < old_height ? height : old_height;
436
437        for(f = 0; f < cv->framecount; f++)
438        {
439            uint32_t *chars = cv->frames[f].chars;
440            uint32_t *attrs = cv->frames[f].attrs;
441
442            for(y = 1; y < lines; y++)
443            {
444                for(x = 0; x < width; x++)
445                {
446                    chars[y * width + x] = chars[y * old_width + x];
447                    attrs[y * width + x] = attrs[y * old_width + x];
448                }
449            }
450        }
451    }
452
453    /* Step 3: fill the bottom of the new screen if necessary. */
454    if(height > old_height)
455    {
456        for(f = 0; f < cv->framecount; f++)
457        {
458            uint32_t *chars = cv->frames[f].chars;
459            uint32_t *attrs = cv->frames[f].attrs;
460            uint32_t attr = cv->frames[f].curattr;
461
462            /* Zero the bottom of the screen */
463            for(x = (height - old_height) * width; x--; )
464            {
465                chars[old_height * width + x] = (uint32_t)' ';
466                attrs[old_height * width + x] = attr;
467            }
468        }
469
470        caca_add_dirty_rectangle(cv, 0, old_height, old_width - 1, height - 1);
471    }
472
473    /* XXX: technically we should not worry about the dirty rectangle in
474     * the bottom-right corner, because we only handle one dirty rectangle,
475     * but in case the API changes later, we make sure this is handled. */
476    if(width > old_width && height > old_height)
477        caca_add_dirty_rectangle(cv, old_width, old_height,
478                                 width - 1, height - 1);
479
480    /* Step 4: if new area is smaller, resize memory area now. */
481    if(new_size < old_size)
482    {
483        for(f = 0; f < cv->framecount; f++)
484        {
485            cv->frames[f].chars = realloc(cv->frames[f].chars,
486                                          new_size * sizeof(uint32_t));
487            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
488                                          new_size * sizeof(uint32_t));
489            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
490            {
491                seterrno(ENOMEM);
492                return -1;
493            }
494        }
495    }
496
497    /* Set new size */
498    for(f = 0; f < cv->framecount; f++)
499    {
500        if(cv->frames[f].x > (int)width)
501            cv->frames[f].x = width;
502        if(cv->frames[f].y > (int)height)
503            cv->frames[f].y = height;
504
505        cv->frames[f].width = width;
506        cv->frames[f].height = height;
507    }
508
509    /* Reset the current frame shortcuts */
510    _caca_load_frame_info(cv);
511
512    return 0;
513}
514
515/*
516 * XXX: The following functions are aliases.
517 */
518
519cucul_canvas_t * cucul_create_canvas(int, int) CACA_ALIAS(caca_create_canvas);
520int cucul_manage_canvas(cucul_canvas_t *, int (*)(void *), void *)
521    CACA_ALIAS(caca_manage_canvas);
522int cucul_unmanage_canvas(cucul_canvas_t *, int (*)(void *), void *)
523    CACA_ALIAS(caca_unmanage_canvas);
524int cucul_set_canvas_size(cucul_canvas_t *, int, int)
525    CACA_ALIAS(caca_set_canvas_size);
526int cucul_get_canvas_width(cucul_canvas_t const *)
527    CACA_ALIAS(caca_get_canvas_width);
528int cucul_get_canvas_height(cucul_canvas_t const *)
529    CACA_ALIAS(caca_get_canvas_height);
530uint8_t const * cucul_get_canvas_chars(cucul_canvas_t const *)
531    CACA_ALIAS(caca_get_canvas_chars);
532uint8_t const * cucul_get_canvas_attrs(cucul_canvas_t const *)
533    CACA_ALIAS(caca_get_canvas_attrs);
534int cucul_free_canvas(cucul_canvas_t *) CACA_ALIAS(caca_free_canvas);
535int cucul_rand(int, int) CACA_ALIAS(caca_rand);
536
Note: See TracBrowser for help on using the repository browser.