xref: /PHP-7.2/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 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 	/* Whole image would be cropped > bye */
167 	if (match) {
168 		return NULL;
169 	}
170 
171 	crop.y = y - 1;
172 
173 	match = 1;
174 	for (y = height - 1; match && y >= 0; y--) {
175 		for (x = 0; match && x < width; x++) {
176 			match = (color == gdImageGetPixel(im, x,y));
177 		}
178 	}
179 	crop.height = y - crop.y + 2;
180 
181 	match = 1;
182 	for (x = 0; match && x < width; x++) {
183 		for (y = 0; match && y < crop.y + crop.height; y++) {
184 			match = (color == gdImageGetPixel(im, x,y));
185 		}
186 	}
187 	crop.x = x - 1;
188 
189 	match = 1;
190 	for (x = width - 1; match && x >= 0; x--) {
191 		for (y = 0; match &&  y < crop.y + crop.height; y++) {
192 			match = (color == gdImageGetPixel(im, x,y));
193 		}
194 	}
195 	crop.width = x - crop.x + 2;
196 
197 	if (crop.x < 0 || crop.y < 0 || crop.width <= 0 || crop.height <= 0) {
198 		return NULL;
199 	}
200 	return gdImageCrop(im, &crop);
201 }
202 /*TODOs: Implement DeltaE instead, way better perceptual differences */
203 /**
204  * Function: gdImageThresholdCrop
205  *  Crop an image using a given color. The threshold argument defines
206  *  the tolerance to be used while comparing the image color and the
207  *  color to crop. The method used to calculate the color difference
208  *  is based on the color distance in the RGB(a) cube.
209  *
210  *
211  * Parameters:
212  * 	im - Source image
213  *  color - color to crop
214  *  threshold - tolerance (0..100)
215  *
216  * Returns:
217  *  <gdImagePtr> on success or NULL
218  *
219  * See also:
220  *  <gdCropMode>, <gdImageAutoCrop> or <gdImageCrop>
221  */
gdImageCropThreshold(gdImagePtr im,const unsigned int color,const float threshold)222 gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold)
223 {
224 	const int width = gdImageSX(im);
225 	const int height = gdImageSY(im);
226 
227 	int x,y;
228 	int match;
229 	gdRect crop;
230 
231 	crop.x = 0;
232 	crop.y = 0;
233 	crop.width = 0;
234 	crop.height = 0;
235 
236 	/* Pierre: crop everything sounds bad */
237 	if (threshold > 1.0) {
238 		return NULL;
239 	}
240 
241 	if (!gdImageTrueColor(im) && color >= gdImageColorsTotal(im)) {
242 		return NULL;
243 	}
244 
245 	/* TODO: Add gdImageGetRowPtr and works with ptr at the row level
246 	 * for the true color and palette images
247 	 * new formats will simply work with ptr
248 	 */
249 	match = 1;
250 	for (y = 0; match && y < height; y++) {
251 		for (x = 0; match && x < width; x++) {
252 			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
253 		}
254 	}
255 
256 	/* Whole image would be cropped > bye */
257 	if (match) {
258 		return NULL;
259 	}
260 
261 	crop.y = y - 1;
262 
263 	match = 1;
264 	for (y = height - 1; match && y >= 0; y--) {
265 		for (x = 0; match && x < width; x++) {
266 			match = (gdColorMatch(im, color, gdImageGetPixel(im, x, y), threshold)) > 0;
267 		}
268 	}
269 	crop.height = y - crop.y + 2;
270 
271 	match = 1;
272 	for (x = 0; match && x < width; x++) {
273 		for (y = 0; match && y < crop.y + crop.height; y++) {
274 			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
275 		}
276 	}
277 	crop.x = x - 1;
278 
279 	match = 1;
280 	for (x = width - 1; match && x >= 0; x--) {
281 		for (y = 0; match &&  y < crop.y + crop.height; y++) {
282 			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
283 		}
284 	}
285 	crop.width = x - crop.x + 2;
286 
287 	return gdImageCrop(im, &crop);
288 }
289 
290 /* This algorithm comes from pnmcrop (http://netpbm.sourceforge.net/)
291  * Three steps:
292  *  - if 3 corners are equal.
293  *  - if two are equal.
294  *  - Last solution: average the colors
295  */
gdGuessBackgroundColorFromCorners(gdImagePtr im,int * color)296 static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color)
297 {
298 	const int tl = gdImageGetPixel(im, 0, 0);
299 	const int tr = gdImageGetPixel(im, gdImageSX(im) - 1, 0);
300 	const int bl = gdImageGetPixel(im, 0, gdImageSY(im) -1);
301 	const int br = gdImageGetPixel(im, gdImageSX(im) - 1, gdImageSY(im) -1);
302 
303 	if (tr == bl && tr == br) {
304 		*color = tr;
305 		return 3;
306 	} else if (tl == bl && tl == br) {
307 		*color = tl;
308 		return 3;
309 	} else if (tl == tr &&  tl == br) {
310 		*color = tl;
311 		return 3;
312 	} else if (tl == tr &&  tl == bl) {
313 		*color = tl;
314 		return 3;
315 	} else if (tl == tr  || tl == bl || tl == br) {
316 		*color = tl;
317 		return 2;
318 	} else if (tr == bl || tr == br) {
319 		*color = tr;
320 		return 2;
321 	} else if (br == bl) {
322 		*color = bl;
323 		return 2;
324 	} else {
325 		register int r,b,g,a;
326 
327 		r = (int)(0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4);
328 		g = (int)(0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4);
329 		b = (int)(0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4);
330 		a = (int)(0.5f + (gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / 4);
331 		*color = gdImageColorClosestAlpha(im, r, g, b, a);
332 		return 0;
333 	}
334 }
335 
gdColorMatch(gdImagePtr im,int col1,int col2,float threshold)336 static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold)
337 {
338 	const int dr = gdImageRed(im, col1) - gdImageRed(im, col2);
339 	const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2);
340 	const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2);
341 	const int da = gdImageAlpha(im, col1) - gdImageAlpha(im, col2);
342 	const double dist = sqrt(dr * dr + dg * dg + db * db + da * da);
343 	const double dist_perc = sqrt(dist / (255^2 + 255^2 + 255^2));
344 	return (dist_perc <= threshold);
345 	//return (100.0 * dist / 195075) < threshold;
346 }
347 
348 /*
349  * To be implemented when we have more image formats.
350  * Buffer like gray8 gray16 or rgb8 will require some tweak
351  * and can be done in this function (called from the autocrop
352  * function. (Pierre)
353  */
354 #if 0
355 static int colors_equal (const int col1, const in col2)
356 {
357 
358 }
359 #endif
360