~ubuntu-branches/ubuntu/raring/libcaca/raring

« back to all changes in this revision

Viewing changes to caca/dirty.c

  • Committer: Bazaar Package Importer
  • Author(s): Sam Hocevar
  • Date: 2010-02-08 01:40:59 UTC
  • mfrom: (1.1.8 upstream) (4.1.6 sid)
  • Revision ID: james.westby@ubuntu.com-20100208014059-9q4av8pze8p7uw3i
Tags: 0.99.beta17-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  libcaca       Colour ASCII-Art library
 
3
 *  Copyright (c) 2002-2009 Sam Hocevar <sam@hocevar.net>
 
4
 *                All Rights Reserved
 
5
 *
 
6
 *  This library is free software. It comes without any warranty, to
 
7
 *  the extent permitted by applicable law. You can redistribute it
 
8
 *  and/or modify it under the terms of the Do What The Fuck You Want
 
9
 *  To Public License, Version 2, as published by Sam Hocevar. See
 
10
 *  http://sam.zoy.org/wtfpl/COPYING for more details.
 
11
 */
 
12
 
 
13
/*
 
14
 *  This file contains the dirty rectangle handling functions.
 
15
 *
 
16
 *
 
17
 *  About dirty rectangles:
 
18
 *
 
19
 *  * Dirty rectangles MUST NOT be larger than the canvas. If the user
 
20
 *  provides a large rectangle through caca_add_dirty_rect(), or if the
 
21
 *  canvas changes size to become smaller, all dirty rectangles MUST
 
22
 *  immediately be clipped to the canvas size.
 
23
 */
 
24
 
 
25
#include "config.h"
 
26
 
 
27
#if !defined(__KERNEL__)
 
28
#   include <stdio.h>
 
29
#   include <string.h>
 
30
#endif
 
31
 
 
32
#include "caca.h"
 
33
#include "caca_internals.h"
 
34
 
 
35
static void merge_new_rect(caca_canvas_t *cv, int n);
 
36
 
 
37
/** \brief Disable dirty rectangles.
 
38
 *
 
39
 *  Disable dirty rectangle handling for all \e libcaca graphic calls. This
 
40
 *  is handy when the calling application needs to do slow operations within
 
41
 *  a known area. Just call caca_add_dirty_rect() afterwards.
 
42
 *
 
43
 *  This function is recursive. Dirty rectangles are only reenabled when
 
44
 *  caca_enable_dirty_rect() is called as many times.
 
45
 *
 
46
 *  This function never fails.
 
47
 *
 
48
 *  \param cv A libcaca canvas.
 
49
 *  \return This function always returns 0.
 
50
 */
 
51
int caca_disable_dirty_rect(caca_canvas_t *cv)
 
52
{
 
53
    cv->dirty_disabled++;
 
54
 
 
55
    return 0;
 
56
}
 
57
 
 
58
/** \brief Enable dirty rectangles.
 
59
 *
 
60
 *  This function can only be called after caca_disable_dirty_rect() was
 
61
 *  called.
 
62
 *
 
63
 *  If an error occurs, -1 is returned and \b errno is set accordingly:
 
64
 *  - \c EINVAL Dirty rectangles were not disabled.
 
65
 *
 
66
 *  \param cv A libcaca canvas.
 
67
 *  \return 0 in case of success, -1 if an error occurred.
 
68
 */
 
69
int caca_enable_dirty_rect(caca_canvas_t *cv)
 
70
{
 
71
    if(cv->dirty_disabled <= 0)
 
72
    {
 
73
        seterrno(EINVAL);
 
74
        return -1;
 
75
    }
 
76
 
 
77
    cv->dirty_disabled--;
 
78
 
 
79
    return 0;
 
80
}
 
81
 
 
82
/** \brief Get the number of dirty rectangles in the canvas.
 
83
 *
 
84
 *  Get the number of dirty rectangles in a canvas. Dirty rectangles are
 
85
 *  areas that contain cells that have changed since the last reset.
 
86
 *
 
87
 *  The dirty rectangles are used internally by display drivers to optimise
 
88
 *  rendering by avoiding to redraw the whole screen. Once the display driver
 
89
 *  has rendered the canvas, it resets the dirty rectangle list.
 
90
 *
 
91
 *  Dirty rectangles are guaranteed not to overlap.
 
92
 *
 
93
 *  This function never fails.
 
94
 *
 
95
 *  \param cv A libcaca canvas.
 
96
 *  \return The number of dirty rectangles in the given canvas.
 
97
 */
 
98
int caca_get_dirty_rect_count(caca_canvas_t *cv)
 
99
{
 
100
    return cv->ndirty;
 
101
}
 
102
 
 
103
/** \brief Get a canvas's dirty rectangle.
 
104
 *
 
105
 *  Get the canvas's given dirty rectangle coordinates. The index must be
 
106
 *  within the dirty rectangle count. See caca_get_dirty_rect_count()
 
107
 *  for how to compute this count.
 
108
 *
 
109
 *  If an error occurs, no coordinates are written in the pointer arguments,
 
110
 *  -1 is returned and \b errno is set accordingly:
 
111
 *  - \c EINVAL Specified rectangle index is out of bounds.
 
112
 *
 
113
 *  \param cv A libcaca canvas.
 
114
 *  \param r The requested rectangle index.
 
115
 *  \param x A pointer to an integer where the leftmost edge of the
 
116
 *           dirty rectangle will be stored.
 
117
 *  \param y A pointer to an integer where the topmost edge of the
 
118
 *           dirty rectangle will be stored.
 
119
 *  \param width A pointer to an integer where the width of the
 
120
 *               dirty rectangle will be stored.
 
121
 *  \param height A pointer to an integer where the height of the
 
122
 *                dirty rectangle will be stored.
 
123
 *  \return 0 in case of success, -1 if an error occurred.
 
124
 */
 
125
int caca_get_dirty_rect(caca_canvas_t *cv, int r,
 
126
                        int *x, int *y, int *width, int *height)
 
127
{
 
128
    if(r < 0 || r >= cv->ndirty)
 
129
    {
 
130
        seterrno(EINVAL);
 
131
        return -1;
 
132
    }
 
133
 
 
134
    *x = cv->dirty[r].xmin;
 
135
    *y = cv->dirty[r].ymin;
 
136
    *width = cv->dirty[r].xmax - cv->dirty[r].xmin + 1;
 
137
    *height = cv->dirty[r].ymax - cv->dirty[r].ymin + 1;
 
138
 
 
139
    debug("dirty #%i: %ix%i at (%i,%i)", r, *width, *height, *x, *y);
 
140
 
 
141
    return 0;
 
142
}
 
143
 
 
144
/** \brief Add an area to the canvas's dirty rectangle list.
 
145
 *
 
146
 *  Add an invalidating zone to the canvas's dirty rectangle list. For more
 
147
 *  information about the dirty rectangles, see caca_get_dirty_rect().
 
148
 *
 
149
 *  This function may be useful to force refresh of a given zone of the
 
150
 *  canvas even if the dirty rectangle tracking indicates that it is
 
151
 *  unchanged. This may happen if the canvas contents were somewhat
 
152
 *  directly modified.
 
153
 *
 
154
 *  If an error occurs, -1 is returned and \b errno is set accordingly:
 
155
 *  - \c EINVAL Specified rectangle coordinates are out of bounds.
 
156
 *
 
157
 *  \param cv A libcaca canvas.
 
158
 *  \param x The leftmost edge of the additional dirty rectangle.
 
159
 *  \param y The topmost edge of the additional dirty rectangle.
 
160
 *  \param width The width of the additional dirty rectangle.
 
161
 *  \param height The height of the additional dirty rectangle.
 
162
 *  \return 0 in case of success, -1 if an error occurred.
 
163
 */
 
164
int caca_add_dirty_rect(caca_canvas_t *cv, int x, int y, int width, int height)
 
165
{
 
166
    debug("new dirty: %ix%i at (%i,%i)", width, height, x, y);
 
167
 
 
168
    /* Clip arguments to canvas */
 
169
    if(x < 0) { width += x; x = 0; }
 
170
 
 
171
    if(x + width > cv->width)
 
172
        width = cv->width - x;
 
173
 
 
174
    if(y < 0) { height += y; y = 0; }
 
175
 
 
176
    if(y + height > cv->height)
 
177
        height = cv->height - y;
 
178
 
 
179
    /* Ignore empty and out-of-canvas rectangles */
 
180
    if(width <= 0 || height <= 0)
 
181
    {
 
182
        seterrno(EINVAL);
 
183
        return -1;
 
184
    }
 
185
 
 
186
    /* Add the new rectangle to the list; it works even if cv->ndirty
 
187
     * is MAX_DIRTY_COUNT because there's an extra cell in the array. */
 
188
    cv->dirty[cv->ndirty].xmin = x;
 
189
    cv->dirty[cv->ndirty].ymin = y;
 
190
    cv->dirty[cv->ndirty].xmax = x + width - 1;
 
191
    cv->dirty[cv->ndirty].ymax = y + height - 1;
 
192
    cv->ndirty++;
 
193
 
 
194
    /* Try to merge the new rectangle with existing ones. This also ensures
 
195
     * that cv->ndirty is brought back below MAX_DIRTY_COUNT. */
 
196
    merge_new_rect(cv, cv->ndirty - 1);
 
197
 
 
198
    return 0;
 
199
}
 
200
 
 
201
/** \brief Remove an area from the dirty rectangle list.
 
202
 *
 
203
 *  Mark a cell area in the canvas as not dirty. For more information about
 
204
 *  the dirty rectangles, see caca_get_dirty_rect().
 
205
 *
 
206
 *  Values such that \b xmin > \b xmax or \b ymin > \b ymax indicate that
 
207
 *  the dirty rectangle is empty. They will be silently ignored.
 
208
 *
 
209
 *  If an error occurs, -1 is returned and \b errno is set accordingly:
 
210
 *  - \c EINVAL Specified rectangle coordinates are out of bounds.
 
211
 *
 
212
 *  \param cv A libcaca canvas.
 
213
 *  \param x The leftmost edge of the clean rectangle.
 
214
 *  \param y The topmost edge of the clean rectangle.
 
215
 *  \param width The width of the clean rectangle.
 
216
 *  \param height The height of the clean rectangle.
 
217
 *  \return 0 in case of success, -1 if an error occurred.
 
218
 */
 
219
int caca_remove_dirty_rect(caca_canvas_t *cv, int x, int y,
 
220
                           int width, int height)
 
221
{
 
222
    /* Clip arguments to canvas size */
 
223
    if(x < 0) { width += x; x = 0; }
 
224
 
 
225
    if(x + width > cv->width)
 
226
        width = cv->width - x;
 
227
 
 
228
    if(y < 0) { height += y; y = 0; }
 
229
 
 
230
    if(y + height > cv->height)
 
231
        height = cv->height - y;
 
232
 
 
233
    /* Ignore empty and out-of-canvas rectangles */
 
234
    if(width <= 0 || height <= 0)
 
235
    {
 
236
        seterrno(EINVAL);
 
237
        return -1;
 
238
    }
 
239
 
 
240
    /* FIXME: implement this function. It's OK to have it do nothing,
 
241
     * since we take a conservative approach in dirty rectangle handling,
 
242
     * but we ought to help the rendering eventually. */
 
243
 
 
244
    return 0;
 
245
}
 
246
 
 
247
/** \brief Clear a canvas's dirty rectangle list.
 
248
 *
 
249
 *  Empty the canvas's dirty rectangle list.
 
250
 *
 
251
 *  This function never fails.
 
252
 *
 
253
 *  \param cv A libcaca canvas.
 
254
 *  \return This function always returns 0.
 
255
 */
 
256
int caca_clear_dirty_rect_list(caca_canvas_t *cv)
 
257
{
 
258
    cv->ndirty = 0;
 
259
 
 
260
    return 0;
 
261
}
 
262
 
 
263
/*
 
264
 * XXX: the following functions are local.
 
265
 */
 
266
 
 
267
static inline int int_min(int a, int b) { return a < b ? a : b; }
 
268
static inline int int_max(int a, int b) { return a > b ? a : b; }
 
269
 
 
270
/* Merge a newly added rectangle, if necessary. */
 
271
static void merge_new_rect(caca_canvas_t *cv, int n)
 
272
{
 
273
    int wasted[MAX_DIRTY_COUNT + 1];
 
274
    int i, sn, best, best_score;
 
275
 
 
276
    best = -1;
 
277
    best_score = cv->width * cv->height;
 
278
 
 
279
    sn = (cv->dirty[n].xmax - cv->dirty[n].xmin + 1)
 
280
           * (cv->dirty[n].ymax - cv->dirty[n].ymin + 1);
 
281
 
 
282
    /* Check whether the new rectangle can be merged with an existing one. */
 
283
    for(i = 0; i < cv->ndirty; i++)
 
284
    {
 
285
        int si, sf, xmin, ymin, xmax, ymax;
 
286
 
 
287
        if(i == n)
 
288
            continue;
 
289
 
 
290
        xmin = int_min(cv->dirty[i].xmin, cv->dirty[n].xmin);
 
291
        ymin = int_min(cv->dirty[i].ymin, cv->dirty[n].ymin);
 
292
        xmax = int_max(cv->dirty[i].xmax, cv->dirty[n].xmax);
 
293
        ymax = int_max(cv->dirty[i].ymax, cv->dirty[n].ymax);
 
294
 
 
295
        sf = (xmax - xmin + 1) * (ymax - ymin + 1);
 
296
 
 
297
        /* Shortcut: if the current rectangle is inside the new rectangle,
 
298
         * we remove the current rectangle and continue trying merges. */
 
299
        if(sf == sn)
 
300
        {
 
301
            memmove(&cv->dirty[i], &cv->dirty[i + 1],
 
302
                    (cv->ndirty - i) * sizeof(cv->dirty[0]));
 
303
            cv->ndirty--;
 
304
 
 
305
            if(i < n)
 
306
                n--;
 
307
            else
 
308
                i--;
 
309
 
 
310
            continue;
 
311
        }
 
312
 
 
313
        si = (cv->dirty[i].xmax - cv->dirty[i].xmin + 1)
 
314
               * (cv->dirty[i].ymax - cv->dirty[i].ymin + 1);
 
315
 
 
316
        /* Shortcut: if the new rectangle is inside the current rectangle,
 
317
         * we get rid of the new rectangle and bail out. */
 
318
        if(sf == si)
 
319
        {
 
320
            cv->ndirty--;
 
321
            memmove(&cv->dirty[n], &cv->dirty[n + 1],
 
322
                    (cv->ndirty - n) * sizeof(cv->dirty[0]));
 
323
            return;
 
324
        }
 
325
 
 
326
        /* We store approximately how many bytes were wasted. FIXME: this is
 
327
         * not an exact computation, we need to be more precise. */
 
328
        wasted[i] = sf - si - sn;
 
329
 
 
330
        if(wasted[i] < best_score)
 
331
        {
 
332
             best = i;
 
333
             best_score = wasted[i];
 
334
        }
 
335
    }
 
336
 
 
337
    /* FIXME: we only try to merge the current rectangle, ignoring
 
338
     * potentially better merges. */
 
339
 
 
340
    /* If no acceptable score was found and the dirty rectangle list is
 
341
     * not full, we bail out. */
 
342
    if(best_score > 0 && cv->ndirty < MAX_DIRTY_COUNT)
 
343
        return;
 
344
 
 
345
    /* Otherwise, merge the rectangle with the best candidate */
 
346
    cv->dirty[best].xmin = int_min(cv->dirty[best].xmin, cv->dirty[n].xmin);
 
347
    cv->dirty[best].ymin = int_min(cv->dirty[best].ymin, cv->dirty[n].ymin);
 
348
    cv->dirty[best].xmax = int_max(cv->dirty[best].xmax, cv->dirty[n].xmax);
 
349
    cv->dirty[best].ymax = int_max(cv->dirty[best].ymax, cv->dirty[n].ymax);
 
350
 
 
351
    memmove(&cv->dirty[n], &cv->dirty[n + 1],
 
352
            (cv->ndirty - n) * sizeof(cv->dirty[0]));
 
353
    cv->ndirty--;
 
354
 
 
355
    if(best < n)
 
356
        merge_new_rect(cv, best);
 
357
    else
 
358
        merge_new_rect(cv, best - 1);
 
359
}
 
360
 
 
361
/* Clip all dirty rectangles in case they're larger than the canvas */
 
362
void _caca_clip_dirty_rect_list(caca_canvas_t *cv)
 
363
{
 
364
    int i;
 
365
 
 
366
    for(i = 0; i < cv->ndirty; i++)
 
367
    {
 
368
        if(cv->dirty[i].xmin < 0)
 
369
            cv->dirty[i].xmin = 0;
 
370
 
 
371
        if(cv->dirty[i].ymin < 0)
 
372
            cv->dirty[i].ymin = 0;
 
373
 
 
374
        if(cv->dirty[i].xmax >= cv->width)
 
375
            cv->dirty[i].xmax = cv->width - 1;
 
376
 
 
377
        if(cv->dirty[i].ymax >= cv->height)
 
378
            cv->dirty[i].ymax = cv->height - 1;
 
379
    }
 
380
}
 
381