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 alphaBlendingFlag;
47
48 if (gdImageTrueColor(src)) {
49 dst = gdImageCreateTrueColor(crop->width, crop->height);
50 } else {
51 dst = gdImageCreate(crop->width, crop->height);
52 }
53 if (!dst) return NULL;
54 alphaBlendingFlag = dst->alphaBlendingFlag;
55 gdImageAlphaBlending(dst, gdEffectReplace);
56 gdImageCopy(dst, src, 0, 0, crop->x, crop->y, crop->width, crop->height);
57 gdImageAlphaBlending(dst, alphaBlendingFlag);
58
59 return dst;
60 }
61
62 /**
63 * Function: gdImageAutoCrop
64 * Automatic croping of the src image using the given mode
65 * (see <gdCropMode>)
66 *
67 *
68 * Parameters:
69 * im - Source image
70 * mode - crop mode
71 *
72 * Returns:
73 * <gdImagePtr> on success or NULL
74 *
75 * See also:
76 * <gdCropMode>
77 */
gdImageCropAuto(gdImagePtr im,const unsigned int mode)78 gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode)
79 {
80 const int width = gdImageSX(im);
81 const int height = gdImageSY(im);
82
83 int x,y;
84 int color, match;
85 gdRect crop;
86
87 crop.x = 0;
88 crop.y = 0;
89 crop.width = 0;
90 crop.height = 0;
91
92 switch (mode) {
93 case GD_CROP_TRANSPARENT:
94 color = gdImageGetTransparent(im);
95 break;
96
97 case GD_CROP_BLACK:
98 color = gdImageColorClosestAlpha(im, 0, 0, 0, 0);
99 break;
100
101 case GD_CROP_WHITE:
102 color = gdImageColorClosestAlpha(im, 255, 255, 255, 0);
103 break;
104
105 case GD_CROP_SIDES:
106 gdGuessBackgroundColorFromCorners(im, &color);
107 break;
108
109 case GD_CROP_DEFAULT:
110 default:
111 color = gdImageGetTransparent(im);
112 if (color == -1) {
113 gdGuessBackgroundColorFromCorners(im, &color);
114 }
115 break;
116 }
117
118 /* TODO: Add gdImageGetRowPtr and works with ptr at the row level
119 * for the true color and palette images
120 * new formats will simply work with ptr
121 */
122 match = 1;
123 for (y = 0; match && y < height; y++) {
124 for (x = 0; match && x < width; x++) {
125 int c2 = gdImageGetPixel(im, x, y);
126 match = (color == c2);
127 }
128 }
129
130 /* Whole image would be cropped > bye */
131 if (match) {
132 return NULL;
133 }
134
135 crop.y = y - 1;
136
137 match = 1;
138 for (y = height - 1; match && y >= 0; y--) {
139 for (x = 0; match && x < width; x++) {
140 match = (color == gdImageGetPixel(im, x,y));
141 }
142 }
143 crop.height = y - crop.y + 2;
144
145 match = 1;
146 for (x = 0; match && x < width; x++) {
147 for (y = 0; match && y < crop.y + crop.height; y++) {
148 match = (color == gdImageGetPixel(im, x,y));
149 }
150 }
151 crop.x = x - 1;
152
153 match = 1;
154 for (x = width - 1; match && x >= 0; x--) {
155 for (y = 0; match && y < crop.y + crop.height; y++) {
156 match = (color == gdImageGetPixel(im, x,y));
157 }
158 }
159 crop.width = x - crop.x + 2;
160
161 if (crop.x < 0 || crop.y < 0 || crop.width <= 0 || crop.height <= 0) {
162 return NULL;
163 }
164 return gdImageCrop(im, &crop);
165 }
166 /*TODOs: Implement DeltaE instead, way better perceptual differences */
167 /**
168 * Function: gdImageThresholdCrop
169 * Crop an image using a given color. The threshold argument defines
170 * the tolerance to be used while comparing the image color and the
171 * color to crop. The method used to calculate the color difference
172 * is based on the color distance in the RGB(a) cube.
173 *
174 *
175 * Parameters:
176 * im - Source image
177 * color - color to crop
178 * threshold - tolerance (0..100)
179 *
180 * Returns:
181 * <gdImagePtr> on success or NULL
182 *
183 * See also:
184 * <gdCropMode>, <gdImageAutoCrop> or <gdImageCrop>
185 */
gdImageCropThreshold(gdImagePtr im,const unsigned int color,const float threshold)186 gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold)
187 {
188 const int width = gdImageSX(im);
189 const int height = gdImageSY(im);
190
191 int x,y;
192 int match;
193 gdRect crop;
194
195 crop.x = 0;
196 crop.y = 0;
197 crop.width = 0;
198 crop.height = 0;
199
200 /* Pierre: crop everything sounds bad */
201 if (threshold > 1.0) {
202 return NULL;
203 }
204
205 if (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im)) {
206 return NULL;
207 }
208
209 /* TODO: Add gdImageGetRowPtr and works with ptr at the row level
210 * for the true color and palette images
211 * new formats will simply work with ptr
212 */
213 match = 1;
214 for (y = 0; match && y < height; y++) {
215 for (x = 0; match && x < width; x++) {
216 match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
217 }
218 }
219
220 /* Whole image would be cropped > bye */
221 if (match) {
222 return NULL;
223 }
224
225 crop.y = y - 1;
226
227 match = 1;
228 for (y = height - 1; match && y >= 0; y--) {
229 for (x = 0; match && x < width; x++) {
230 match = (gdColorMatch(im, color, gdImageGetPixel(im, x, y), threshold)) > 0;
231 }
232 }
233 crop.height = y - crop.y + 2;
234
235 match = 1;
236 for (x = 0; match && x < width; x++) {
237 for (y = 0; match && y < crop.y + crop.height; y++) {
238 match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
239 }
240 }
241 crop.x = x - 1;
242
243 match = 1;
244 for (x = width - 1; match && x >= 0; x--) {
245 for (y = 0; match && y < crop.y + crop.height; y++) {
246 match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
247 }
248 }
249 crop.width = x - crop.x + 2;
250
251 return gdImageCrop(im, &crop);
252 }
253
254 /* This algorithm comes from pnmcrop (http://netpbm.sourceforge.net/)
255 * Three steps:
256 * - if 3 corners are equal.
257 * - if two are equal.
258 * - Last solution: average the colors
259 */
gdGuessBackgroundColorFromCorners(gdImagePtr im,int * color)260 static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color)
261 {
262 const int tl = gdImageGetPixel(im, 0, 0);
263 const int tr = gdImageGetPixel(im, gdImageSX(im) - 1, 0);
264 const int bl = gdImageGetPixel(im, 0, gdImageSY(im) -1);
265 const int br = gdImageGetPixel(im, gdImageSX(im) - 1, gdImageSY(im) -1);
266
267 if (tr == bl && tr == br) {
268 *color = tr;
269 return 3;
270 } else if (tl == bl && tl == br) {
271 *color = tl;
272 return 3;
273 } else if (tl == tr && tl == br) {
274 *color = tl;
275 return 3;
276 } else if (tl == tr && tl == bl) {
277 *color = tl;
278 return 3;
279 } else if (tl == tr || tl == bl || tl == br) {
280 *color = tl;
281 return 2;
282 } else if (tr == bl || tr == br) {
283 *color = tr;
284 return 2;
285 } else if (br == bl) {
286 *color = bl;
287 return 2;
288 } else {
289 register int r,b,g,a;
290
291 r = (int)(0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4);
292 g = (int)(0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4);
293 b = (int)(0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4);
294 a = (int)(0.5f + (gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / 4);
295 *color = gdImageColorClosestAlpha(im, r, g, b, a);
296 return 0;
297 }
298 }
299
gdColorMatch(gdImagePtr im,int col1,int col2,float threshold)300 static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold)
301 {
302 const int dr = gdImageRed(im, col1) - gdImageRed(im, col2);
303 const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2);
304 const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2);
305 const int da = gdImageAlpha(im, col1) - gdImageAlpha(im, col2);
306 const double dist = sqrt(dr * dr + dg * dg + db * db + da * da);
307 const double dist_perc = sqrt(dist / (255^2 + 255^2 + 255^2));
308 return (dist_perc <= threshold);
309 //return (100.0 * dist / 195075) < threshold;
310 }
311
312 /*
313 * To be implemented when we have more image formats.
314 * Buffer like gray8 gray16 or rgb8 will require some tweak
315 * and can be done in this function (called from the autocrop
316 * function. (Pierre)
317 */
318 #if 0
319 static int colors_equal (const int col1, const in col2)
320 {
321
322 }
323 #endif
324