diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 842a6c4..26e2de4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,14 +1,14 @@ -message("inc: ${GD_INCLUDE_DIR}") include_directories (BEFORE ${GD_SOURCE_DIR}/src "${CMAKE_BINARY_DIR}") SET(TESTS_FILES - tiffread + tiffread + tgaread + crop ) FOREACH(test_name ${TESTS_FILES}) add_executable(${test_name} "${test_name}.c") target_link_libraries (${test_name} ${GD_LIB}) - ADD_TEST(${test_name} ${EXECUTABLE_OUTPUT_PATH}/${test_name}) ENDFOREACH(test_name) diff --git a/examples/crop.c b/examples/crop.c new file mode 100644 index 0000000..f4e8a99 --- /dev/null +++ b/examples/crop.c @@ -0,0 +1,88 @@ +/* $Id$ */ +/* + * You can fetch a set of samples TIFF images here: + * ftp://ftp.remotesensing.org/pub/libtiff/ + * (pics-x.y.z.tar.gz) + */ + +#include "gd.h" +#include +#include + +void save_png(gdImagePtr im, const char *filename) +{ + FILE *fp; + fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Can't save png image %s\n", filename); + return; + } + gdImagePng(im, fp); + fclose(fp); +} + +gdImagePtr read_png(const char *filename) +{ + FILE * fp; + gdImagePtr im; + + fp = fopen(filename, "rb"); + if (!fp) { + fprintf(stderr, "Can't read png image %s\n", filename); + return NULL; + } + im = gdImageCreateFromPng(fp); + fclose(fp); + return im; +} + +int main() +{ + gdImagePtr im, im2; + FILE *fp; + char path[2048]; + char dst[2048]; + + im = gdImageCreateTrueColor(400, 400); + + if (!im) { + fprintf(stderr, "Can't create 400x400 TC image\n", path); + return 1; + } + + gdImageFilledRectangle(im, 19, 29, 390, 390, 0xFFFFFF); + gdImageRectangle(im, 19, 29, 390, 390, 0xFF0000); + save_png(im, "a1.png"); + + im2 = gdImageAutoCrop(im, GD_CROP_SIDES); + if (im2) { + save_png(im2, "a2.png"); + gdImageDestroy(im2); + } + gdImageDestroy(im); + + im = read_png("test_crop_threshold.png"); + if (!im) { + return 1; + } + + im2 = gdImageThresholdCrop(im, 0xFFFFFF, 120); + if (im2) { + save_png(im2, "a3.png"); + gdImageDestroy(im2); + } + gdImageDestroy(im); + + im = read_png("test_crop_threshold.png"); + if (!im) { + return 1; + } + + im2 = gdImageThresholdCrop(im, 0xFFFFFF, 70); + if (im2) { + save_png(im2, "a4.png"); + gdImageDestroy(im2); + } + + gdImageDestroy(im); +} diff --git a/examples/test_crop_threshold.png b/examples/test_crop_threshold.png new file mode 100644 index 0000000..4a9dff1 Binary files /dev/null and b/examples/test_crop_threshold.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a49f1a9..cd17707 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,7 @@ SET (LIBGD_SRC_FILES gd.c gdfx.c + gd_crop.c gd_transform.c gd_security.c gd_gd.c diff --git a/src/gd.h b/src/gd.h index 13fcc00..6708008 100644 --- a/src/gd.h +++ b/src/gd.h @@ -130,6 +130,15 @@ extern "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. @@ -475,11 +484,19 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f 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); @@ -731,6 +748,12 @@ BGD_DECLARE(void) gdImageInterlace (gdImagePtr im, int interlaceArg); 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, diff --git a/src/gd_crop.c b/src/gd_crop.c new file mode 100644 index 0000000..af9c755 --- /dev/null +++ b/src/gd_crop.c @@ -0,0 +1,274 @@ +/* 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 + +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