gd_crop.c 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. /**
  2. * Title: Crop
  3. *
  4. * A couple of functions to crop images, automatically (auto detection of
  5. * the borders color), using a given color (with or without tolerance)
  6. * or using a selection.
  7. *
  8. * The threshold method works relatively well but it can be improved.
  9. * Maybe L*a*b* and Delta-E will give better results (and a better
  10. * granularity).
  11. *
  12. * Example:
  13. * (start code)
  14. * im2 = gdImageAutoCrop(im, GD_CROP_SIDES);
  15. * if (im2) {
  16. * }
  17. * gdImageDestroy(im2);
  18. * (end code)
  19. **/
  20. #include <gd.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <math.h>
  24. static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color);
  25. static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold);
  26. /**
  27. * Function: gdImageCrop
  28. * Crops the src image using the area defined by the <crop> rectangle.
  29. * The result is returned as a new image.
  30. *
  31. *
  32. * Parameters:
  33. * src - Source image
  34. * crop - Rectangular region to crop
  35. *
  36. * Returns:
  37. * <gdImagePtr> on success or NULL
  38. */
  39. gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop)
  40. {
  41. gdImagePtr dst;
  42. int y;
  43. /* allocate the requested size (could be only partially filled) */
  44. if (src->trueColor) {
  45. dst = gdImageCreateTrueColor(crop->width, crop->height);
  46. if (dst == NULL) {
  47. return NULL;
  48. }
  49. gdImageSaveAlpha(dst, 1);
  50. } else {
  51. dst = gdImageCreate(crop->width, crop->height);
  52. if (dst == NULL) {
  53. return NULL;
  54. }
  55. gdImagePaletteCopy(dst, src);
  56. }
  57. dst->transparent = src->transparent;
  58. /* check position in the src image */
  59. if (crop->x < 0 || crop->x>=src->sx || crop->y<0 || crop->y>=src->sy) {
  60. return dst;
  61. }
  62. /* reduce size if needed */
  63. if ((src->sx - crop->width) < crop->x) {
  64. crop->width = src->sx - crop->x;
  65. }
  66. if ((src->sy - crop->height) < crop->y) {
  67. crop->height = src->sy - crop->y;
  68. }
  69. #if 0
  70. printf("rect->x: %i\nrect->y: %i\nrect->width: %i\nrect->height: %i\n", crop->x, crop->y, crop->width, crop->height);
  71. #endif
  72. y = crop->y;
  73. if (src->trueColor) {
  74. unsigned int dst_y = 0;
  75. while (y < (crop->y + crop->height)) {
  76. /* TODO: replace 4 w/byte per channel||pitch once available */
  77. memcpy(dst->tpixels[dst_y++], src->tpixels[y++] + crop->x, crop->width * 4);
  78. }
  79. } else {
  80. int x;
  81. for (y = crop->y; y < (crop->y + crop->height); y++) {
  82. for (x = crop->x; x < (crop->x + crop->width); x++) {
  83. dst->pixels[y - crop->y][x - crop->x] = src->pixels[y][x];
  84. }
  85. }
  86. }
  87. return dst;
  88. }
  89. /**
  90. * Function: gdImageAutoCrop
  91. * Automatic croping of the src image using the given mode
  92. * (see <gdCropMode>)
  93. *
  94. *
  95. * Parameters:
  96. * im - Source image
  97. * mode - crop mode
  98. *
  99. * Returns:
  100. * <gdImagePtr> on success or NULL
  101. *
  102. * See also:
  103. * <gdCropMode>
  104. */
  105. gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode)
  106. {
  107. const int width = gdImageSX(im);
  108. const int height = gdImageSY(im);
  109. int x,y;
  110. int color, corners, match;
  111. gdRect crop;
  112. crop.x = 0;
  113. crop.y = 0;
  114. crop.width = 0;
  115. crop.height = 0;
  116. switch (mode) {
  117. case GD_CROP_TRANSPARENT:
  118. color = gdImageGetTransparent(im);
  119. break;
  120. case GD_CROP_BLACK:
  121. color = gdImageColorClosestAlpha(im, 0, 0, 0, 0);
  122. break;
  123. case GD_CROP_WHITE:
  124. color = gdImageColorClosestAlpha(im, 255, 255, 255, 0);
  125. break;
  126. case GD_CROP_SIDES:
  127. corners = gdGuessBackgroundColorFromCorners(im, &color);
  128. break;
  129. case GD_CROP_DEFAULT:
  130. default:
  131. color = gdImageGetTransparent(im);
  132. if (color == -1) {
  133. corners = gdGuessBackgroundColorFromCorners(im, &color);
  134. }
  135. break;
  136. }
  137. /* TODO: Add gdImageGetRowPtr and works with ptr at the row level
  138. * for the true color and palette images
  139. * new formats will simply work with ptr
  140. */
  141. match = 1;
  142. for (y = 0; match && y < height; y++) {
  143. for (x = 0; match && x < width; x++) {
  144. int c2 = gdImageGetPixel(im, x, y);
  145. match = (color == c2);
  146. }
  147. }
  148. /* Nothing to do > bye
  149. * Duplicate the image?
  150. */
  151. if (y == height - 1) {
  152. return NULL;
  153. }
  154. crop.y = y -1;
  155. match = 1;
  156. for (y = height - 1; match && y >= 0; y--) {
  157. for (x = 0; match && x < width; x++) {
  158. match = (color == gdImageGetPixel(im, x,y));
  159. }
  160. }
  161. if (y == 0) {
  162. crop.height = height - crop.y + 1;
  163. } else {
  164. crop.height = y - crop.y + 2;
  165. }
  166. match = 1;
  167. for (x = 0; match && x < width; x++) {
  168. for (y = 0; match && y < crop.y + crop.height - 1; y++) {
  169. match = (color == gdImageGetPixel(im, x,y));
  170. }
  171. }
  172. crop.x = x - 1;
  173. match = 1;
  174. for (x = width - 1; match && x >= 0; x--) {
  175. for (y = 0; match && y < crop.y + crop.height - 1; y++) {
  176. match = (color == gdImageGetPixel(im, x,y));
  177. }
  178. }
  179. crop.width = x - crop.x + 2;
  180. if (crop.x <= 0 || crop.y <= 0 || crop.width <= 0 || crop.height <= 0) {
  181. return NULL;
  182. }
  183. return gdImageCrop(im, &crop);
  184. }
  185. /*TODOs: Implement DeltaE instead, way better perceptual differences */
  186. /**
  187. * Function: gdImageThresholdCrop
  188. * Crop an image using a given color. The threshold argument defines
  189. * the tolerance to be used while comparing the image color and the
  190. * color to crop. The method used to calculate the color difference
  191. * is based on the color distance in the RGB(a) cube.
  192. *
  193. *
  194. * Parameters:
  195. * im - Source image
  196. * color - color to crop
  197. * threshold - tolerance (0..100)
  198. *
  199. * Returns:
  200. * <gdImagePtr> on success or NULL
  201. *
  202. * See also:
  203. * <gdCropMode>, <gdImageAutoCrop> or <gdImageCrop>
  204. */
  205. gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold)
  206. {
  207. const int width = gdImageSX(im);
  208. const int height = gdImageSY(im);
  209. int x,y;
  210. int match;
  211. gdRect crop;
  212. crop.x = 0;
  213. crop.y = 0;
  214. crop.width = 0;
  215. crop.height = 0;
  216. /* Pierre: crop everything sounds bad */
  217. if (threshold > 1.0) {
  218. return NULL;
  219. }
  220. if (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im)) {
  221. return NULL;
  222. }
  223. /* TODO: Add gdImageGetRowPtr and works with ptr at the row level
  224. * for the true color and palette images
  225. * new formats will simply work with ptr
  226. */
  227. match = 1;
  228. for (y = 0; match && y < height; y++) {
  229. for (x = 0; match && x < width; x++) {
  230. match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
  231. }
  232. }
  233. /* Pierre
  234. * Nothing to do > bye
  235. * Duplicate the image?
  236. */
  237. if (y == height - 1) {
  238. return NULL;
  239. }
  240. crop.y = y -1;
  241. match = 1;
  242. for (y = height - 1; match && y >= 0; y--) {
  243. for (x = 0; match && x < width; x++) {
  244. match = (gdColorMatch(im, color, gdImageGetPixel(im, x, y), threshold)) > 0;
  245. }
  246. }
  247. if (y == 0) {
  248. crop.height = height - crop.y + 1;
  249. } else {
  250. crop.height = y - crop.y + 2;
  251. }
  252. match = 1;
  253. for (x = 0; match && x < width; x++) {
  254. for (y = 0; match && y < crop.y + crop.height - 1; y++) {
  255. match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
  256. }
  257. }
  258. crop.x = x - 1;
  259. match = 1;
  260. for (x = width - 1; match && x >= 0; x--) {
  261. for (y = 0; match && y < crop.y + crop.height - 1; y++) {
  262. match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
  263. }
  264. }
  265. crop.width = x - crop.x + 2;
  266. return gdImageCrop(im, &crop);
  267. }
  268. /* This algorithm comes from pnmcrop (http://netpbm.sourceforge.net/)
  269. * Three steps:
  270. * - if 3 corners are equal.
  271. * - if two are equal.
  272. * - Last solution: average the colors
  273. */
  274. static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color)
  275. {
  276. const int tl = gdImageGetPixel(im, 0, 0);
  277. const int tr = gdImageGetPixel(im, gdImageSX(im) - 1, 0);
  278. const int bl = gdImageGetPixel(im, 0, gdImageSY(im) -1);
  279. const int br = gdImageGetPixel(im, gdImageSX(im) - 1, gdImageSY(im) -1);
  280. if (tr == bl && tr == br) {
  281. *color = tr;
  282. return 3;
  283. } else if (tl == bl && tl == br) {
  284. *color = tl;
  285. return 3;
  286. } else if (tl == tr && tl == br) {
  287. *color = tl;
  288. return 3;
  289. } else if (tl == tr && tl == bl) {
  290. *color = tl;
  291. return 3;
  292. } else if (tl == tr || tl == bl || tl == br) {
  293. *color = tl;
  294. return 2;
  295. } else if (tr == bl) {
  296. *color = tr;
  297. return 2;
  298. } else if (br == bl) {
  299. *color = bl;
  300. return 2;
  301. } else {
  302. register int r,b,g,a;
  303. r = (int)(0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4);
  304. g = (int)(0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4);
  305. b = (int)(0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4);
  306. a = (int)(0.5f + (gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / 4);
  307. *color = gdImageColorClosestAlpha(im, r, g, b, a);
  308. return 0;
  309. }
  310. }
  311. static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold)
  312. {
  313. const int dr = gdImageRed(im, col1) - gdImageRed(im, col2);
  314. const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2);
  315. const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2);
  316. const int da = gdImageAlpha(im, col1) - gdImageAlpha(im, col2);
  317. const double dist = sqrt(dr * dr + dg * dg + db * db + da * da);
  318. const double dist_perc = sqrt(dist / (255^2 + 255^2 + 255^2));
  319. return (dist_perc <= threshold);
  320. //return (100.0 * dist / 195075) < threshold;
  321. }
  322. /*
  323. * To be implemented when we have more image formats.
  324. * Buffer like gray8 gray16 or rgb8 will require some tweak
  325. * and can be done in this function (called from the autocrop
  326. * function. (Pierre)
  327. */
  328. #if 0
  329. static int colors_equal (const int col1, const in col2)
  330. {
  331. }
  332. #endif