- #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 heightmaster
parent
5583a4194a
commit
f67452e1f8
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
|
@ -2,6 +2,7 @@
|
|||
SET (LIBGD_SRC_FILES
|
||||
gd.c
|
||||
gdfx.c
|
||||
gd_crop.c
|
||||
gd_transform.c
|
||||
gd_security.c
|
||||
gd_gd.c
|
||||
|
|
33
src/gd.h
33
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,
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue