Commits

pajoye  committed c0f943e

- #136 add crop image support
- gdImageCrop, gdImageAutoCrop and gdImageThresholdCrop
- add gdRect struct to define a rectangle area at position (x,y) with
a given width and height

  • Participants
  • Parent commits 4c034b1
  • Branches GD-2.0

Comments (0)

Files changed (3)

File src/CMakeLists.txt

 SET (LIBGD_SRC_FILES
 	gd.c
 	gdfx.c
+	gd_crop.c
 	gd_transform.c
 	gd_security.c
 	gd_gd.c
 #define gdTrueColorGetGreen(c) (((c) & 0x00FF00) >> 8)
 #define gdTrueColorGetBlue(c) ((c) & 0x0000FF)
 
+enum gdCropMode {
+	GD_CROP_DEFAULT = 0,
+	GD_CROP_TRANSPARENT,
+	GD_CROP_BLACK,
+	GD_CROP_WHITE,
+	GD_CROP_SIDES
+};
+
+
 /* This function accepts truecolor pixel values only. The 
 	source color is composited with the destination color
 	based on the alpha channel value of the source color.
 			   char *string, gdFTStringExtraPtr strex);
 
 /* Point type for use in polygon drawing. */
-  typedef struct
-  {
-    int x, y;
-  }
-  gdPoint, *gdPointPtr;
+typedef struct
+{
+	int x, y;
+}
+gdPoint, *gdPointPtr;
+
+typedef struct
+{
+	int x, y;
+	int width, height;
+}
+gdRect, *gdRectPtr;
+
 
 BGD_DECLARE(void) gdImagePolygon (gdImagePtr im, gdPointPtr p, int n, int c);
 BGD_DECLARE(void) gdImageOpenPolygon (gdImagePtr im, gdPointPtr p, int n, int c);
 BGD_DECLARE(void) gdImageAlphaBlending (gdImagePtr im, int alphaBlendingArg);
 BGD_DECLARE(void) gdImageSaveAlpha (gdImagePtr im, int saveAlphaArg);
 
+
+BGD_DECLARE(gdImagePtr) gdImageCrop(gdImagePtr src, const gdRect *crop);
+BGD_DECLARE(gdImagePtr) gdImageAutoCrop(gdImagePtr im, const unsigned int mode);
+BGD_DECLARE(gdImagePtr) gdImageThresholdCrop(gdImagePtr im, const unsigned int color, const int threshold);
+
+
 /* Macros to access information about images. */
 
 /* Returns nonzero if the image is a truecolor image,

File src/gd_crop.c

+/* Crop support
+ * manual crop using a gdRect or automatic crop using a background
+ * color (automatic detections or using either the transparent color,
+ * black or white).
+ * An alternative method allows to crop using a given color and a
+ * threshold. It works relatively well but it can be improved.
+ * Maybe L*a*b* and Delta-E will give better results (and a better
+ * granularity).
+ */
+
+#include <gd.h>
+
+static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color);
+static int gdColorMatch(gdImagePtr im, int col1, int col2, int threshold);
+
+BGD_DECLARE(gdImagePtr) gdImageCrop(gdImagePtr src, const gdRect *crop)
+{
+	gdImagePtr dst;
+
+	dst = gdImageCreateTrueColor(crop->width, crop->height);
+	gdImageCopy(dst, src, 0, 0, crop->x, crop->y, crop->width, crop->height);
+
+	return dst;
+}
+
+BGD_DECLARE(gdImagePtr) gdImageAutoCrop(gdImagePtr im, const unsigned int mode)
+{
+	const int width = gdImageSX(im);
+	const int height = gdImageSY(im);
+
+	int x,y;
+	int color, corners, match;
+	gdRect crop;
+
+	crop.x = 0;
+	crop.y = 0;
+	crop.width = 0;
+	crop.height = 0;
+
+	switch (mode) {
+		case GD_CROP_TRANSPARENT:
+			color = gdImageGetTransparent(im);
+			break;
+
+		case GD_CROP_BLACK:
+			color = gdImageColorClosestAlpha(im, 0, 0, 0, 0);
+			break;
+
+		case GD_CROP_WHITE:
+			color = gdImageColorClosestAlpha(im, 255, 255, 255, 0);
+			break;
+
+		case GD_CROP_SIDES:
+			corners = gdGuessBackgroundColorFromCorners(im, &color);
+			break;
+
+		case GD_CROP_DEFAULT:
+		default:
+			color = gdImageGetTransparent(im);
+			break;
+	}
+
+	/* TODO: Add gdImageGetRowPtr and works with ptr at the row level
+	 * for the true color and palette images
+	 * new formats will simply work with ptr
+	 */
+	match = 1;
+	for (y = 0; match && y < height; y++) {
+		for (x = 0; match && x < width; x++) {
+			match = (color == gdImageGetPixel(im, x,y));
+		}
+	}
+
+	/* Nothing to do > bye */
+	if (y == height - 1) {
+		return;
+	}
+
+	crop.y = y -1;
+	match = 1;
+	for (y = height - 1; match && y >= 0; y--) {
+		for (x = 0; match && x < width; x++) {
+			match = (color == gdImageGetPixel(im, x,y));
+		}
+	}
+
+	if (y == 0) {
+		crop.height = height - crop.y + 1;
+	} else {
+		crop.height = y - crop.y + 2;
+	}
+
+	match = 1;
+	for (x = 0; match && x < width; x++) {
+		for (y = 0; match && y < crop.y + crop.height - 1; y++) {
+			match = (color == gdImageGetPixel(im, x,y));
+		}
+	}
+	crop.x = x - 1;
+
+	match = 1;
+	for (x = width - 1; match && x >= 0; x--) {
+		for (y = 0; match &&  y < crop.y + crop.height - 1; y++) {
+			match = (color == gdImageGetPixel(im, x,y));
+		}
+	}
+	crop.width = x - crop.x + 2;
+
+	return gdImageCrop(im, &crop);
+}
+
+BGD_DECLARE(gdImagePtr) gdImageThresholdCrop(gdImagePtr im, const unsigned int color, const int threshold)
+{
+	const int width = gdImageSX(im);
+	const int height = gdImageSY(im);
+
+	int x,y;
+	int corners, match;
+	gdRect crop;
+
+	crop.x = 0;
+	crop.y = 0;
+	crop.width = 0;
+	crop.height = 0;
+
+	if (threshold >= 255) {
+		return;
+	}
+
+	/* TODO: Add gdImageGetRowPtr and works with ptr at the row level
+	 * for the true color and palette images
+	 * new formats will simply work with ptr
+	 */
+	match = 1;
+	for (y = 0; match && y < height; y++) {
+		for (x = 0; match && x < width; x++) {
+			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
+		}
+	}
+
+	/* Nothing to do > bye */
+	if (y == height - 1) {
+		return;
+	}
+
+	crop.y = y -1;
+	match = 1;
+	for (y = height - 1; match && y >= 0; y--) {
+		for (x = 0; match && x < width; x++) {
+			match = (gdColorMatch(im, color, gdImageGetPixel(im, x, y), threshold)) > 0;
+		}
+	}
+
+	if (y == 0) {
+		crop.height = height - crop.y + 1;
+	} else {
+		crop.height = y - crop.y + 2;
+	}
+
+	match = 1;
+	for (x = 0; match && x < width; x++) {
+		for (y = 0; match && y < crop.y + crop.height - 1; y++) {
+			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
+		}
+	}
+	crop.x = x - 1;
+
+	match = 1;
+	for (x = width - 1; match && x >= 0; x--) {
+		for (y = 0; match &&  y < crop.y + crop.height - 1; y++) {
+			match = (gdColorMatch(im, color, gdImageGetPixel(im, x,y), threshold)) > 0;
+		}
+	}
+	crop.width = x - crop.x + 2;
+
+	return gdImageCrop(im, &crop);
+}
+
+/* This algorithm comes from pnmcrop (http://netpbm.sourceforge.net/)
+ * Three steps:
+ *  - if 3 corners are equal.
+ *  - if two are equal.
+ *  - Last solution: average the colors
+ */
+static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color)
+{
+	const int tl = gdImageGetPixel(im, 0, 0);
+	const int tr = gdImageGetPixel(im, gdImageSX(im) - 1, 0);
+	const int bl = gdImageGetPixel(im, 0, gdImageSY(im) -1);
+	const int br = gdImageGetPixel(im, gdImageSX(im) - 1, gdImageSY(im) -1);
+
+	if (tr == bl && tr == br) {
+		*color = tr;
+		return 3;
+	} else if (tl == bl && tl == br) {
+		*color = tl;
+		return 3;
+	} else if (tl == tr &&  tl == br) {
+		*color = tl;
+		return 3;
+	} else if (tl == tr &&  tl == bl) {
+		*color = tl;
+		return 3;
+	} else if (tl == tr  || tl == bl || tl == br) {
+		*color = tl;
+		return 2;
+	} else if (tr == bl || tr == bl) {
+		*color = tr;
+		return 2;
+	} else if (br == bl) {
+		*color = bl;
+		return 2;
+	} else {
+		int r,b,g,a;
+
+		r = (0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4);
+		g = (0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4);
+		b = (0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4);
+		a = (0.5f + (gdImageAlpha(im, tl) + gdImageAlpha(im, tr) + gdImageAlpha(im, bl) + gdImageAlpha(im, br)) / 4);
+		*color = gdImageColorClosestAlpha(im, r, g, b, a);
+		return 0;
+	}
+}
+
+static int gdColorMatch(gdImagePtr im, int col1, int col2, int threshold)
+{
+	int diff, max = 0;
+
+
+	/* alternative method would be to take the distance in the rgb cube
+	 * between the desired color and the current pixel:
+	 * (r2 - r1)^2 + (g2 -b1)^2 + (g1 -g2)^2
+	 *
+	 * but I did not see a difference in my results, that's why I kept
+	 * this faster implementation.
+	 */
+	diff = abs(gdImageRed(im, col2) - gdImageRed(im, col1));
+	if (diff > max) {
+		max = diff;
+	}
+
+	diff = abs(gdImageGreen(im, col2) - gdImageGreen(im, col1));
+	if (diff > max) {
+		max = diff;
+	}
+
+	diff = abs(gdImageBlue(im, col2) - gdImageBlue(im, col1));
+	if (diff > max) {
+		max = diff;
+	}
+/* do we need alpha here? We may detect full transparency and consider
+ * them as background
+ */
+/*
+	diff = abs(gdImageAlpha(im, col2) - gdImageAlpha(im, col1));
+	if (diff > max) {
+		max = diff;
+	}
+*/
+	return (max < threshold);
+}
+
+/*
+ * To be implemented when we have more image formats.
+ * Buffer like gray8 gray16 or rgb8 will require some tweak
+ * and can be done in this function (called from the autocrop
+ * function. (Pierre)
+ */
+#if 0
+static int colors_equal (const int col1, const in col2)
+{
+
+}
+#endif