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