2
* libcaca Colour ASCII-Art library
3
* Copyright (c) 2002-2009 Sam Hocevar <sam@hocevar.net>
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.
14
* This file contains the dirty rectangle handling functions.
17
* About dirty rectangles:
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.
27
#if !defined(__KERNEL__)
33
#include "caca_internals.h"
35
static void merge_new_rect(caca_canvas_t *cv, int n);
37
/** \brief Disable dirty rectangles.
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.
43
* This function is recursive. Dirty rectangles are only reenabled when
44
* caca_enable_dirty_rect() is called as many times.
46
* This function never fails.
48
* \param cv A libcaca canvas.
49
* \return This function always returns 0.
51
int caca_disable_dirty_rect(caca_canvas_t *cv)
58
/** \brief Enable dirty rectangles.
60
* This function can only be called after caca_disable_dirty_rect() was
63
* If an error occurs, -1 is returned and \b errno is set accordingly:
64
* - \c EINVAL Dirty rectangles were not disabled.
66
* \param cv A libcaca canvas.
67
* \return 0 in case of success, -1 if an error occurred.
69
int caca_enable_dirty_rect(caca_canvas_t *cv)
71
if(cv->dirty_disabled <= 0)
82
/** \brief Get the number of dirty rectangles in the canvas.
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.
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.
91
* Dirty rectangles are guaranteed not to overlap.
93
* This function never fails.
95
* \param cv A libcaca canvas.
96
* \return The number of dirty rectangles in the given canvas.
98
int caca_get_dirty_rect_count(caca_canvas_t *cv)
103
/** \brief Get a canvas's dirty rectangle.
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.
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.
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.
125
int caca_get_dirty_rect(caca_canvas_t *cv, int r,
126
int *x, int *y, int *width, int *height)
128
if(r < 0 || r >= cv->ndirty)
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;
139
debug("dirty #%i: %ix%i at (%i,%i)", r, *width, *height, *x, *y);
144
/** \brief Add an area to the canvas's dirty rectangle list.
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().
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
154
* If an error occurs, -1 is returned and \b errno is set accordingly:
155
* - \c EINVAL Specified rectangle coordinates are out of bounds.
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.
164
int caca_add_dirty_rect(caca_canvas_t *cv, int x, int y, int width, int height)
166
debug("new dirty: %ix%i at (%i,%i)", width, height, x, y);
168
/* Clip arguments to canvas */
169
if(x < 0) { width += x; x = 0; }
171
if(x + width > cv->width)
172
width = cv->width - x;
174
if(y < 0) { height += y; y = 0; }
176
if(y + height > cv->height)
177
height = cv->height - y;
179
/* Ignore empty and out-of-canvas rectangles */
180
if(width <= 0 || height <= 0)
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;
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);
201
/** \brief Remove an area from the dirty rectangle list.
203
* Mark a cell area in the canvas as not dirty. For more information about
204
* the dirty rectangles, see caca_get_dirty_rect().
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.
209
* If an error occurs, -1 is returned and \b errno is set accordingly:
210
* - \c EINVAL Specified rectangle coordinates are out of bounds.
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.
219
int caca_remove_dirty_rect(caca_canvas_t *cv, int x, int y,
220
int width, int height)
222
/* Clip arguments to canvas size */
223
if(x < 0) { width += x; x = 0; }
225
if(x + width > cv->width)
226
width = cv->width - x;
228
if(y < 0) { height += y; y = 0; }
230
if(y + height > cv->height)
231
height = cv->height - y;
233
/* Ignore empty and out-of-canvas rectangles */
234
if(width <= 0 || height <= 0)
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. */
247
/** \brief Clear a canvas's dirty rectangle list.
249
* Empty the canvas's dirty rectangle list.
251
* This function never fails.
253
* \param cv A libcaca canvas.
254
* \return This function always returns 0.
256
int caca_clear_dirty_rect_list(caca_canvas_t *cv)
264
* XXX: the following functions are local.
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; }
270
/* Merge a newly added rectangle, if necessary. */
271
static void merge_new_rect(caca_canvas_t *cv, int n)
273
int wasted[MAX_DIRTY_COUNT + 1];
274
int i, sn, best, best_score;
277
best_score = cv->width * cv->height;
279
sn = (cv->dirty[n].xmax - cv->dirty[n].xmin + 1)
280
* (cv->dirty[n].ymax - cv->dirty[n].ymin + 1);
282
/* Check whether the new rectangle can be merged with an existing one. */
283
for(i = 0; i < cv->ndirty; i++)
285
int si, sf, xmin, ymin, xmax, ymax;
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);
295
sf = (xmax - xmin + 1) * (ymax - ymin + 1);
297
/* Shortcut: if the current rectangle is inside the new rectangle,
298
* we remove the current rectangle and continue trying merges. */
301
memmove(&cv->dirty[i], &cv->dirty[i + 1],
302
(cv->ndirty - i) * sizeof(cv->dirty[0]));
313
si = (cv->dirty[i].xmax - cv->dirty[i].xmin + 1)
314
* (cv->dirty[i].ymax - cv->dirty[i].ymin + 1);
316
/* Shortcut: if the new rectangle is inside the current rectangle,
317
* we get rid of the new rectangle and bail out. */
321
memmove(&cv->dirty[n], &cv->dirty[n + 1],
322
(cv->ndirty - n) * sizeof(cv->dirty[0]));
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;
330
if(wasted[i] < best_score)
333
best_score = wasted[i];
337
/* FIXME: we only try to merge the current rectangle, ignoring
338
* potentially better merges. */
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)
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);
351
memmove(&cv->dirty[n], &cv->dirty[n + 1],
352
(cv->ndirty - n) * sizeof(cv->dirty[0]));
356
merge_new_rect(cv, best);
358
merge_new_rect(cv, best - 1);
361
/* Clip all dirty rectangles in case they're larger than the canvas */
362
void _caca_clip_dirty_rect_list(caca_canvas_t *cv)
366
for(i = 0; i < cv->ndirty; i++)
368
if(cv->dirty[i].xmin < 0)
369
cv->dirty[i].xmin = 0;
371
if(cv->dirty[i].ymin < 0)
372
cv->dirty[i].ymin = 0;
374
if(cv->dirty[i].xmax >= cv->width)
375
cv->dirty[i].xmax = cv->width - 1;
377
if(cv->dirty[i].ymax >= cv->height)
378
cv->dirty[i].ymax = cv->height - 1;