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