You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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