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