25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 
 
 

384 satır
12 KiB

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