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

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

Allow to temporarily disable dirty rectangle handling. This allows for huge
speedups when the calling application knows the dirty rectangle covered by
a complex operation.

  • Property svn:keywords set to Id
File size: 15.8 KB
Line 
1/*
2 *  libcaca       Colour ASCII-Art library
3 *  Copyright (c) 2002-2009 Sam Hocevar <sam@hocevar.net>
4 *                All Rights Reserved
5 *
6 *  $Id: canvas.c 3583 2009-07-26 19:17:35Z 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    /* Preload new width and height values into the canvas to optimise
374     * dirty rectangle handling */
375    cv->width = width;
376    cv->height = height;
377    new_size = width * height;
378
379    /* If width or height is smaller (or both), we have the opportunity to
380     * reduce or even remove dirty rectangles */
381    if(width < old_width || height < old_height)
382        _caca_clip_dirty_rect_list(cv);
383
384    /* Step 1: if new area is bigger, resize the memory area now. */
385    if(new_size > old_size)
386    {
387        for(f = 0; f < cv->framecount; f++)
388        {
389            cv->frames[f].chars = realloc(cv->frames[f].chars,
390                                          new_size * sizeof(uint32_t));
391            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
392                                          new_size * sizeof(uint32_t));
393            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
394            {
395                seterrno(ENOMEM);
396                return -1;
397            }
398        }
399    }
400
401    /* Step 2: move line data if necessary. */
402    if(width == old_width)
403    {
404        /* Width did not change, which means we do not need to move data. */
405        ;
406    }
407    else if(width > old_width)
408    {
409        /* New width is bigger than old width, which means we need to
410         * copy lines starting from the bottom of the screen otherwise
411         * we will overwrite information. */
412        for(f = 0; f < cv->framecount; f++)
413        {
414            uint32_t *chars = cv->frames[f].chars;
415            uint32_t *attrs = cv->frames[f].attrs;
416
417            for(y = height < old_height ? height : old_height; y--; )
418            {
419                uint32_t attr = cv->frames[f].curattr;
420
421                for(x = old_width; x--; )
422                {
423                    chars[y * width + x] = chars[y * old_width + x];
424                    attrs[y * width + x] = attrs[y * old_width + x];
425                }
426
427                /* Zero the end of the line */
428                for(x = width - old_width; x--; )
429                {
430                    chars[y * width + old_width + x] = (uint32_t)' ';
431                    attrs[y * width + old_width + x] = attr;
432                }
433            }
434        }
435
436        if(!cv->dirty_disabled)
437            caca_add_dirty_rect(cv, old_width, 0,
438                                width - old_width, old_height);
439    }
440    else
441    {
442        /* New width is smaller. Copy as many lines as possible. Ignore
443         * the first line, it is already in place. */
444        int lines = height < old_height ? height : old_height;
445
446        for(f = 0; f < cv->framecount; f++)
447        {
448            uint32_t *chars = cv->frames[f].chars;
449            uint32_t *attrs = cv->frames[f].attrs;
450
451            for(y = 1; y < lines; y++)
452            {
453                for(x = 0; x < width; x++)
454                {
455                    chars[y * width + x] = chars[y * old_width + x];
456                    attrs[y * width + x] = attrs[y * old_width + x];
457                }
458            }
459        }
460    }
461
462    /* Step 3: fill the bottom of the new screen if necessary. */
463    if(height > old_height)
464    {
465        for(f = 0; f < cv->framecount; f++)
466        {
467            uint32_t *chars = cv->frames[f].chars;
468            uint32_t *attrs = cv->frames[f].attrs;
469            uint32_t attr = cv->frames[f].curattr;
470
471            /* Zero the bottom of the screen */
472            for(x = (height - old_height) * width; x--; )
473            {
474                chars[old_height * width + x] = (uint32_t)' ';
475                attrs[old_height * width + x] = attr;
476            }
477        }
478
479        if(!cv->dirty_disabled)
480            caca_add_dirty_rect(cv, 0, old_height,
481                                old_width, height - old_height);
482    }
483
484    /* If both width and height are larger, there is a new dirty rectangle
485     * that needs to be created in the lower right corner. */
486    if(!cv->dirty_disabled &&
487        width > old_width && height > old_height)
488        caca_add_dirty_rect(cv, old_width, old_height,
489                            width - old_width, height - old_height);
490
491    /* Step 4: if new area is smaller, resize memory area now. */
492    if(new_size < old_size)
493    {
494        for(f = 0; f < cv->framecount; f++)
495        {
496            cv->frames[f].chars = realloc(cv->frames[f].chars,
497                                          new_size * sizeof(uint32_t));
498            cv->frames[f].attrs = realloc(cv->frames[f].attrs,
499                                          new_size * sizeof(uint32_t));
500            if(new_size && (!cv->frames[f].chars || !cv->frames[f].attrs))
501            {
502                seterrno(ENOMEM);
503                return -1;
504            }
505        }
506    }
507
508    /* Set new size */
509    for(f = 0; f < cv->framecount; f++)
510    {
511        if(cv->frames[f].x > (int)width)
512            cv->frames[f].x = width;
513        if(cv->frames[f].y > (int)height)
514            cv->frames[f].y = height;
515
516        cv->frames[f].width = width;
517        cv->frames[f].height = height;
518    }
519
520    /* Reset the current frame shortcuts */
521    _caca_load_frame_info(cv);
522
523    return 0;
524}
525
526/*
527 * XXX: The following functions are aliases.
528 */
529
530cucul_canvas_t * cucul_create_canvas(int, int) CACA_ALIAS(caca_create_canvas);
531int cucul_manage_canvas(cucul_canvas_t *, int (*)(void *), void *)
532    CACA_ALIAS(caca_manage_canvas);
533int cucul_unmanage_canvas(cucul_canvas_t *, int (*)(void *), void *)
534    CACA_ALIAS(caca_unmanage_canvas);
535int cucul_set_canvas_size(cucul_canvas_t *, int, int)
536    CACA_ALIAS(caca_set_canvas_size);
537int cucul_get_canvas_width(cucul_canvas_t const *)
538    CACA_ALIAS(caca_get_canvas_width);
539int cucul_get_canvas_height(cucul_canvas_t const *)
540    CACA_ALIAS(caca_get_canvas_height);
541uint8_t const * cucul_get_canvas_chars(cucul_canvas_t const *)
542    CACA_ALIAS(caca_get_canvas_chars);
543uint8_t const * cucul_get_canvas_attrs(cucul_canvas_t const *)
544    CACA_ALIAS(caca_get_canvas_attrs);
545int cucul_free_canvas(cucul_canvas_t *) CACA_ALIAS(caca_free_canvas);
546int cucul_rand(int, int) CACA_ALIAS(caca_rand);
547
Note: See TracBrowser for help on using the repository browser.