xref: /PHP-7.3/ext/gd/libgd/gd_crop.c (revision a1aaec08)
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