From: Pierre Joye Date: Thu, 28 Feb 2013 16:24:23 +0000 (+0100) Subject: - add image crop support X-Git-Tag: php-5.5.0alpha6~19 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a991360344ed5bca7c20f74a10891d0fc52f0c9f;p=php - add image crop support --- diff --git a/ext/gd/config.m4 b/ext/gd/config.m4 index 00e7c68112..2f71705862 100644 --- a/ext/gd/config.m4 +++ b/ext/gd/config.m4 @@ -297,7 +297,8 @@ if test "$PHP_GD" = "yes"; then libgd/gdfontmb.c libgd/gdfontl.c libgd/gdfontg.c libgd/gdtables.c libgd/gdft.c \ libgd/gdcache.c libgd/gdkanji.c libgd/wbmp.c libgd/gd_wbmp.c libgd/gdhelpers.c \ libgd/gd_topal.c libgd/gd_gif_in.c libgd/xbm.c libgd/gd_gif_out.c libgd/gd_security.c \ - libgd/gd_filter.c libgd/gd_pixelate.c libgd/gd_arc.c libgd/gd_rotate.c libgd/gd_color.c libgd/gd_transform.c" + libgd/gd_filter.c libgd/gd_pixelate.c libgd/gd_arc.c libgd/gd_rotate.c libgd/gd_color.c \ + libgd/gd_transform.c libgd/gd_crop.c" dnl check for fabsf and floorf which are available since C99 AC_CHECK_FUNCS(fabsf floorf) diff --git a/ext/gd/config.w32 b/ext/gd/config.w32 index ed3eab6c09..b25a0e2d9f 100644 --- a/ext/gd/config.w32 +++ b/ext/gd/config.w32 @@ -47,7 +47,8 @@ if (PHP_GD != "no") { gdft.c gd_gd2.c gd_gd.c gd_gif_in.c gd_gif_out.c gdhelpers.c gd_io.c gd_io_dp.c \ gd_io_file.c gd_io_ss.c gd_jpeg.c gdkanji.c gd_png.c gd_ss.c \ gdtables.c gd_topal.c gd_wbmp.c gdxpm.c wbmp.c xbm.c gd_security.c gd_transform.c \ - gd_filter.c gd_pixelate.c gd_arc.c gd_rotate.c gd_color.c webpimg.c gd_webp.c", "gd"); + gd_filter.c gd_pixelate.c gd_arc.c gd_rotate.c gd_color.c webpimg.c gd_webp.c \ + gd_crop.c", "gd"); AC_DEFINE('HAVE_LIBGD', 1, 'GD support'); ADD_FLAG("CFLAGS_GD", " \ /D HAVE_GD_DYNAMIC_CTX_EX=1 \ diff --git a/ext/gd/gd.c b/ext/gd/gd.c index 2bd0a2d23d..83733d1a70 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -890,6 +890,11 @@ ZEND_BEGIN_ARG_INFO(arginfo_imageflip, 0) ZEND_ARG_INFO(0, im) ZEND_ARG_INFO(0, mode) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO(arginfo_imagecropauto, 0) + ZEND_ARG_INFO(0, im) + ZEND_ARG_INFO(0, mode) +ZEND_END_ARG_INFO() #endif /* }}} */ @@ -950,6 +955,7 @@ const zend_function_entry gd_functions[] = { #ifdef HAVE_GD_BUNDLED PHP_FE(imageantialias, arginfo_imageantialias) PHP_FE(imageflip, arginfo_imageflip) + PHP_FE(imagecropauto, arginfo_imagecropauto) #endif #if HAVE_GD_IMAGESETTILE @@ -1204,6 +1210,12 @@ PHP_MINIT_FUNCTION(gd) REGISTER_LONG_CONSTANT("IMG_FLIP_HORIZONTAL", GD_FLIP_HORINZONTAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IMG_FLIP_VERTICAL", GD_FLIP_VERTICAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("IMG_FLIP_BOTH", GD_FLIP_BOTH, CONST_CS | CONST_PERSISTENT); + + REGISTER_LONG_CONSTANT("IMG_CROP_DEFAULT", GD_CROP_DEFAULT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_TRANSPARENT", GD_CROP_TRANSPARENT, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_BLACK", GD_CROP_BLACK, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_WHITE", GD_CROP_WHITE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("IMG_CROP_SIDES", GD_CROP_SIDES, CONST_CS | CONST_PERSISTENT); #else REGISTER_LONG_CONSTANT("GD_BUNDLED", 0, CONST_CS | CONST_PERSISTENT); #endif @@ -5125,6 +5137,44 @@ PHP_FUNCTION(imageflip) RETURN_TRUE; } /* }}} */ + + +/* {{{ proto void imageflip(resource im, int mode) + Flip an image (in place) horizontally, vertically or both directions. */ +PHP_FUNCTION(imagecropauto) +{ + zval *IM; + long mode = -1; + gdImagePtr im; + gdImagePtr im_crop; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|l", &IM, &mode) == FAILURE) { + return; + } + + ZEND_FETCH_RESOURCE(im, gdImagePtr, &IM, -1, "Image", le_gd); + + switch (mode) { + case -1: + mode = GD_CROP_DEFAULT; + case GD_CROP_DEFAULT: + case GD_CROP_TRANSPARENT: + case GD_CROP_BLACK: + case GD_CROP_WHITE: + case GD_CROP_SIDES: + im_crop = gdImageCropAuto(im, mode); + break; + default: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown flip mode"); + RETURN_FALSE; + } + if (im_crop == NULL) { + RETURN_FALSE; + } else { + ZEND_REGISTER_RESOURCE(return_value, im_crop, le_gd); + } +} +/* }}} */ #endif diff --git a/ext/gd/gdcache.c b/ext/gd/gdcache.c index 2349e38b93..231a1f791f 100644 --- a/ext/gd/gdcache.c +++ b/ext/gd/gdcache.c @@ -95,6 +95,11 @@ gdCacheGet( gdCache_head_t *head, void *keydata ) void *userdata; elem = head->mru; + if (elem == NULL) { + return NULL; + + } + while(elem) { if ((*(head->gdCacheTest))(elem->userdata, keydata)) { if (i) { /* if not already most-recently-used */ diff --git a/ext/gd/libgd/gd.h b/ext/gd/libgd/gd.h index c4a47af7fd..adef026b6f 100644 --- a/ext/gd/libgd/gd.h +++ b/ext/gd/libgd/gd.h @@ -209,6 +209,31 @@ typedef struct { /* Text functions take these. */ typedef gdFont *gdFontPtr; + +/** + * Group: Types + * + * typedef: gdRect + * Defines a rectilinear region. + * + * x - left position + * y - right position + * width - Rectangle width + * height - Rectangle height + * + * typedef: gdRectPtr + * Pointer to a + * + * See also: + * + **/ +typedef struct +{ + int x, y; + int width, height; +} +gdRect, *gdRectPtr; + /* For backwards compatibility only. Use gdImageSetStyle() for MUCH more flexible line drawing. Also see gdImageSetBrush(). */ @@ -690,6 +715,31 @@ void gdImageFlipBoth(gdImagePtr im); #define GD_FLIP_VERTICAL 2 #define GD_FLIP_BOTH 3 +/** + * Group: Crop + * + * Constants: gdCropMode + * GD_CROP_DEFAULT - Default crop mode (4 corners or background) + * GD_CROP_TRANSPARENT - Crop using the transparent color + * GD_CROP_BLACK - Crop black borders + * GD_CROP_WHITE - Crop white borders + * GD_CROP_SIDES - Crop using colors of the 4 corners + * + * See also: + * + **/ +enum gdCropMode { + GD_CROP_DEFAULT = 0, + GD_CROP_TRANSPARENT, + GD_CROP_BLACK, + GD_CROP_WHITE, + GD_CROP_SIDES +}; + +gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop); +gdImagePtr gdImageCropAuto(gdImagePtr im, const unsigned int mode); +gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold); + #define GD_CMP_IMAGE 1 /* Actual image IS different */ #define GD_CMP_NUM_COLORS 2 /* Number of Colours in pallette differ */ #define GD_CMP_COLOR 4 /* Image colours differ */ diff --git a/ext/gd/libgd/gd_crop.c b/ext/gd/libgd/gd_crop.c new file mode 100644 index 0000000000..d5fd762780 --- /dev/null +++ b/ext/gd/libgd/gd_crop.c @@ -0,0 +1,355 @@ +/** + * Title: Crop + * + * A couple of functions to crop images, automatically (auto detection of + * the borders color), using a given color (with or without tolerance) + * or using a selection. + * + * The threshold method works relatively well but it can be improved. + * Maybe L*a*b* and Delta-E will give better results (and a better + * granularity). + * + * Example: + * (start code) + * im2 = gdImageAutoCrop(im, GD_CROP_SIDES); + * if (im2) { + + * } + * gdImageDestroy(im2); + * (end code) + **/ + +#include +#include +#include + +static int gdGuessBackgroundColorFromCorners(gdImagePtr im, int *color); +static int gdColorMatch(gdImagePtr im, int col1, int col2, float threshold); + +/** + * Function: gdImageCrop + * Crops the src image using the area defined by the rectangle. + * The result is returned as a new image. + * + * + * Parameters: + * src - Source image + * crop - Rectangular region to crop + * + * Returns: + * on success or NULL + */ +gdImagePtr gdImageCrop(gdImagePtr src, const gdRectPtr crop) +{ + gdImagePtr dst; + + if (src->trueColor) { + dst = gdImageCreateTrueColor(crop->width, crop->height); + gdImageSaveAlpha(dst, 1); + } else { + dst = gdImageCreate(crop->width, crop->height); + gdImagePaletteCopy(dst, src); + } + dst->transparent = src->transparent; + + if (src->sx < (crop->x + crop->width -1)) { + crop->width = src->sx - crop->x + 1; + } + if (src->sy < (crop->y + crop->height -1)) { + crop->height = src->sy - crop->y + 1; + } +#ifdef 0 +printf("rect->x: %i\nrect->y: %i\nrect->width: %i\nrect->height: %i\n", crop->x, crop->y, crop->width, crop->height); +#endif + if (dst == NULL) { + return NULL; + } else { + int y = crop->y; + unsigned int dst_y = 0; + if (src->trueColor) { + unsigned int dst_y = 0; + while (y < (crop->y + (crop->height - 1))) { + /* TODO: replace 4 w/byte per channel||pitch once avaiable */ + memcpy(dst->tpixels[dst_y++], src->tpixels[y++] + crop->x, crop->width * 4); + } + } else { + int x; + for (y = crop->y; y < (crop->y + (crop->height - 1)); y++) { + for (x = crop->x; x < (crop->x + (crop->width - 1)); x++) { + dst->pixels[y - crop->y][x - crop->x] = src->pixels[y][x]; + } + } + } + return dst; + } +} + +/** + * Function: gdImageAutoCrop + * Automatic croping of the src image using the given mode + * (see ) + * + * + * Parameters: + * im - Source image + * mode - crop mode + * + * Returns: + * on success or NULL + * + * See also: + * + */ +gdImagePtr gdImageCropAuto(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); + if (color == -1) { + corners = gdGuessBackgroundColorFromCorners(im, &color); + } + 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++) { + int c2 = gdImageGetPixel(im, x, y); + match = (color == c2); + } + } + + /* Nothing to do > bye + * Duplicate the image? + */ + if (y == height - 1) { + return NULL; + } + + 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; + if (crop.x <= 0 || crop.y <= 0 || crop.width <= 0 || crop.height <= 0) { + return NULL; + } + return gdImageCrop(im, &crop); +} + +/** + * Function: gdImageThresholdCrop + * Crop an image using a given color. The threshold argument defines + * the tolerance to be used while comparing the image color and the + * color to crop. The method used to calculate the color difference + * is based on the color distance in the RGB(a) cube. + * + * + * Parameters: + * im - Source image + * color - color to crop + * threshold - tolerance (0..100) + * + * Returns: + * on success or NULL + * + * See also: + * , or + */ +gdImagePtr gdImageCropThreshold(gdImagePtr im, const unsigned int color, const float threshold) +{ + const int width = gdImageSX(im); + const int height = gdImageSY(im); + + int x,y; + int match; + gdRect crop; + + crop.x = 0; + crop.y = 0; + crop.width = 0; + crop.height = 0; + + /* Pierre: crop everything sounds bad */ + if (threshold > 1.0) { + return NULL; + } + + /* 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; + } + } + + /* Pierre + * Nothing to do > bye + * Duplicate the image? + */ + if (y == height - 1) { + return NULL; + } + + 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) { + *color = tr; + return 2; + } else if (br == bl) { + *color = bl; + return 2; + } else { + register int r,b,g,a; + + r = (int)(0.5f + (gdImageRed(im, tl) + gdImageRed(im, tr) + gdImageRed(im, bl) + gdImageRed(im, br)) / 4); + g = (int)(0.5f + (gdImageGreen(im, tl) + gdImageGreen(im, tr) + gdImageGreen(im, bl) + gdImageGreen(im, br)) / 4); + b = (int)(0.5f + (gdImageBlue(im, tl) + gdImageBlue(im, tr) + gdImageBlue(im, bl) + gdImageBlue(im, br)) / 4); + a = (int)(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, float threshold) +{ + const int dr = gdImageRed(im, col1) - gdImageRed(im, col2); + const int dg = gdImageGreen(im, col1) - gdImageGreen(im, col2); + const int db = gdImageBlue(im, col1) - gdImageBlue(im, col2); + const int da = gdImageAlpha(im, col1) - gdImageAlpha(im, col2); + const int dist = dr * dr + dg * dg + db * db + da * da; + + return (100.0 * dist / 195075) < 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 diff --git a/ext/gd/libgd/gd_png.c b/ext/gd/libgd/gd_png.c index bdbb7ee7d3..a012cc63b8 100644 --- a/ext/gd/libgd/gd_png.c +++ b/ext/gd/libgd/gd_png.c @@ -134,6 +134,7 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) volatile int transparent = -1; volatile int palette_allocated = FALSE; + /* Make sure the signature can't match by dumb luck -- TBB */ /* GRR: isn't sizeof(infile) equal to the size of the pointer? */ memset (sig, 0, sizeof(sig)); @@ -345,6 +346,7 @@ gdImagePtr gdImageCreateFromPngCtx (gdIOCtx * infile) open[i] = 1; } } + /* 2.0.12: Slaven Rezic: palette images are not the only images * with a simple transparent color setting. */ diff --git a/ext/gd/php_gd.h b/ext/gd/php_gd.h index a50a631b52..537efc44b2 100644 --- a/ext/gd/php_gd.h +++ b/ext/gd/php_gd.h @@ -125,6 +125,7 @@ PHP_FUNCTION(imagerotate); #ifdef HAVE_GD_BUNDLED PHP_FUNCTION(imageantialias); PHP_FUNCTION(imageflip); +PHP_FUNCTION(imagecropauto); #endif PHP_FUNCTION(imagesetthickness);