summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpajoye <none@none>2007-12-12 15:56:03 +0000
committerpajoye <none@none>2007-12-12 15:56:03 +0000
commitf67452e1f82f1c2496e0859d638172bee74b43a0 (patch)
treeb36302bc3d5b2a8a0af52e67970e1efb68dd5157
parent5583a4194a348870371f22cb9eb85d3eebfd372f (diff)
downloadlibgd-f67452e1f82f1c2496e0859d638172bee74b43a0.tar.gz
- #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
-rw-r--r--examples/CMakeLists.txt6
-rw-r--r--examples/crop.c88
-rw-r--r--examples/test_crop_threshold.pngbin0 -> 7919 bytes
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/gd.h33
-rw-r--r--src/gd_crop.c274
6 files changed, 394 insertions, 8 deletions
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 <stdio.h>
+#include <stdlib.h>
+
+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
--- /dev/null
+++ b/examples/test_crop_threshold.png
Binary files 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 <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