Pierre Joye avatar Pierre Joye committed 916d00c Merge

Merged in suetanvil/gd-libgd/suetanvil-scale-code-cleanup (pull request #14)

Major cleanup of FP scaling code

Comments (0)

Files changed (6)

 gdImagePtr gdImageScaleBicubic(gdImagePtr src_img, const unsigned int new_width, const unsigned int new_height);
 gdImagePtr gdImageScaleBicubicFixed(gdImagePtr src, const unsigned int width, const unsigned int height);
 gdImagePtr gdImageScaleNearestNeighbour(gdImagePtr im, const unsigned int width, const unsigned int height);
-gdImagePtr gdImageScaleTwoPass(const gdImagePtr pOrigImage, const unsigned int uOrigWidth, const unsigned int uOrigHeight, const unsigned int uNewWidth, const unsigned int uNewHeight);
 BGD_DECLARE(gdImagePtr) gdImageScale(const gdImagePtr src, const unsigned int new_width, const unsigned int new_height);
 
 gdImagePtr gdImageRotate90(gdImagePtr src, int ignoretransparent);

src/gd_interpolation.c

 #include <stdlib.h>
 #include <string.h>
 #include <math.h>
+#include <assert.h>
+
+#define NDEBUG 1 /* TODO: disable/enable assertions in configure. */
+#include <assert.h>
 
 #include "gd.h"
 #include "gdhelpers.h"
 	return gdTrueColorAlpha(((int)new_r), ((int)new_g), ((int)new_b), ((int)new_a));
 }
 
+
+typedef enum {
+    HORIZONTAL,
+    VERTICAL,
+} gdAxis;
+
+
 static inline LineContribType * _gdContributionsAlloc(unsigned int line_length, unsigned int windows_size)
 {
 	unsigned int u = 0;
 	return res;
 }
 
-static inline void _gdScaleRow(gdImagePtr pSrc,  unsigned int src_width, gdImagePtr dst, unsigned int dst_width, unsigned int row, LineContribType *contrib)
+
+static inline void
+_gdScaleOneAxis(gdImagePtr pSrc, gdImagePtr dst,
+                unsigned int dst_len, unsigned int row, LineContribType *contrib,
+                gdAxis axis)
 {
-	int *p_src_row = pSrc->tpixels[row];
-	int *p_dst_row = dst->tpixels[row];
-	unsigned int x;
+	unsigned int ndx;
 
-	for (x = 0; x < dst_width - 1; x++) {
+	for (ndx = 0; ndx < dst_len; ndx++) {
 		register unsigned char r = 0, g = 0, b = 0, a = 0;
-		const int left = contrib->ContribRow[x].Left;
-		const int right = contrib->ContribRow[x].Right;
+		const int left = contrib->ContribRow[ndx].Left;
+		const int right = contrib->ContribRow[ndx].Right;
+        int *dest = (axis == HORIZONTAL) ? 
+            &dst->tpixels[row][ndx] : 
+            &dst->tpixels[ndx][row];
+
 		int i;
 
 		/* Accumulate each channel */
 		for (i = left; i <= right; i++) {
 			const int left_channel = i - left;
-			r += (unsigned char)(contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetRed(p_src_row[i])));
-			g += (unsigned char)(contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetGreen(p_src_row[i])));
-			b += (unsigned char)(contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetBlue(p_src_row[i])));
-			a += (unsigned char)(contrib->ContribRow[x].Weights[left_channel] * (double)(gdTrueColorGetAlpha(p_src_row[i])));
-		}
-		p_dst_row[x] = gdTrueColorAlpha(r, g, b, a);
-	}
-}
-
-static inline void _gdScaleHoriz(gdImagePtr pSrc, unsigned int src_width, unsigned int src_height, gdImagePtr pDst,  unsigned int dst_width, unsigned int dst_height)
+            const int srcpx = (axis == HORIZONTAL) ?
+                pSrc->tpixels[row][i] : 
+                pSrc->tpixels[i][row];
+
+			r += (unsigned char)(contrib->ContribRow[ndx].Weights[left_channel] * (double)(gdTrueColorGetRed(srcpx)));
+			g += (unsigned char)(contrib->ContribRow[ndx].Weights[left_channel] * (double)(gdTrueColorGetGreen(srcpx)));
+			b += (unsigned char)(contrib->ContribRow[ndx].Weights[left_channel] * (double)(gdTrueColorGetBlue(srcpx)));
+			a += (unsigned char)(contrib->ContribRow[ndx].Weights[left_channel] * (double)(gdTrueColorGetAlpha(srcpx)));
+		}/* for */
+
+        *dest = gdTrueColorAlpha(r, g, b, a);
+	}/* for */
+}/* _gdScaleOneAxis*/
+
+
+static inline int
+_gdScalePass(const gdImagePtr pSrc, const unsigned int src_len,
+             const gdImagePtr pDst, const unsigned int dst_len,
+             const unsigned int num_lines,
+             const gdAxis axis)
 {
-	unsigned int u;
+	unsigned int line_ndx;
 	LineContribType * contrib;
 
-	/* same width, just copy it */
-	if (dst_width == src_width) {
-		unsigned int y;
-		for (y = 0; y < src_height - 1; ++y) {
-			memcpy(pDst->tpixels[y], pSrc->tpixels[y], src_width);
-		}
-	}
+    /* Same dim, just copy it. */
+    assert(dst_len != src_len); // TODO: caller should handle this.
 
-	contrib = _gdContributionsCalc(dst_width, src_width, (double)dst_width / (double)src_width, pSrc->interpolation);
+	contrib = _gdContributionsCalc(dst_len, src_len,
+                                   (double)dst_len / (double)src_len,
+                                   pSrc->interpolation);
 	if (contrib == NULL) {
-		return;
-	}
-	/* Scale each row */
-	for (u = 0; u < dst_height - 1; u++) {
-		_gdScaleRow(pSrc, src_width, pDst, dst_width, u, contrib);
+		return 0;
 	}
-	_gdContributionsFree (contrib);
-}
 
-static inline void _gdScaleCol (gdImagePtr pSrc,  unsigned int src_width, gdImagePtr pRes, unsigned int dst_width, unsigned int dst_height, unsigned int uCol, LineContribType *contrib)
-{
-	unsigned int y;
-	for (y = 0; y < dst_height - 1; y++) {
-		register unsigned char r = 0, g = 0, b = 0, a = 0;
-		const int iLeft = contrib->ContribRow[y].Left;
-		const int iRight = contrib->ContribRow[y].Right;
-		int i;
-
-		/* Accumulate each channel */
-		for (i = iLeft; i <= iRight; i++) {
-			const int pCurSrc = pSrc->tpixels[i][uCol];
-			const int i_iLeft = i - iLeft;
-			r += (unsigned char)(contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetRed(pCurSrc)));
-			g += (unsigned char)(contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetGreen(pCurSrc)));
-			b += (unsigned char)(contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetBlue(pCurSrc)));
-			a += (unsigned char)(contrib->ContribRow[y].Weights[i_iLeft] * (double)(gdTrueColorGetAlpha(pCurSrc)));
-		}
-		pRes->tpixels[y][uCol] = gdTrueColorAlpha(r, g, b, a);
+	/* Scale each line */
+    for (line_ndx = 0; line_ndx < num_lines; line_ndx++) {
+        _gdScaleOneAxis(pSrc, pDst, dst_len, line_ndx, contrib, axis);
 	}
-}
-
-static inline void _gdScaleVert (const gdImagePtr pSrc, const unsigned int src_width, const unsigned int src_height, const gdImagePtr pDst, const unsigned int dst_width, const unsigned int dst_height)
-{
-	unsigned int u;
-	LineContribType * contrib;
+	_gdContributionsFree (contrib);
 
-	/* same height, copy it */
-	if (src_height == dst_height) {
-		unsigned int y;
-		for (y = 0; y < src_height - 1; ++y) {
-			memcpy(pDst->tpixels[y], pSrc->tpixels[y], src_width);
-		}
-	}
+    return 1;
+}/* _gdScalePass*/
 
-	contrib = _gdContributionsCalc(dst_height, src_height, (double)(dst_height) / (double)(src_height), pSrc->interpolation);
-	/* scale each column */
-	for (u = 0; u < dst_width - 1; u++) {
-		_gdScaleCol(pSrc, src_width, pDst, dst_width, dst_height, u, contrib);
-	}
-	_gdContributionsFree(contrib);
-}
 
-gdImagePtr gdImageScaleTwoPass(const gdImagePtr src, const unsigned int src_width, const unsigned int src_height, const unsigned int new_width, const unsigned int new_height)
+static gdImagePtr
+gdImageScaleTwoPass(const gdImagePtr src, const unsigned int new_width,
+                    const unsigned int new_height)
 {
-	gdImagePtr tmp_im;
-	gdImagePtr dst;
-
-	tmp_im = gdImageCreateTrueColor(new_width, src_height);
-	if (tmp_im == NULL) {
-		return NULL;
-	}
-	gdImageSetInterpolationMethod(tmp_im, src->interpolation_id);
-	_gdScaleHoriz(src, src_width, src_height, tmp_im, new_width, src_height);
-
+    const unsigned int src_width = src->sx;
+    const unsigned int src_height = src->sy;
+	gdImagePtr tmp_im = NULL;;
+	gdImagePtr dst = NULL;
+
+    /* First, handle the trivial case. */
+    if (src_width == new_width && src_height == new_height) {
+        return gdImageClone(src);
+    }/* if */
+
+    /* Scale horizontally unless sizes are the same. */
+    if (src_width == new_width) {
+        tmp_im = src;
+    } else {
+        tmp_im = gdImageCreateTrueColor(new_width, src_height);
+        if (tmp_im == NULL) {
+            return NULL;
+        }
+        gdImageSetInterpolationMethod(tmp_im, src->interpolation_id);
+
+        _gdScalePass(src, src_width, tmp_im, new_width, src_height, HORIZONTAL);
+    }/* if .. else*/
+
+    /* If vertical sizes match, we're done. */
+    if (src_height == new_height) {
+        assert(tmp_im != src);
+        return tmp_im;
+    }/* if */
+
+    /* Otherwise, we need to scale vertically. */
 	dst = gdImageCreateTrueColor(new_width, new_height);
-	if (dst == NULL) {
-		gdFree(tmp_im);
-		return NULL;
-	}
-	_gdScaleVert(tmp_im, new_width, src_height, dst, new_width, new_height);
-	gdFree(tmp_im);
+	if (dst != NULL) {
+        gdImageSetInterpolationMethod(dst, src->interpolation_id);
+        _gdScalePass(tmp_im, src_height, dst, new_height, new_width, VERTICAL);
+    }/* if */
 
-	return dst;
-}
+    if (src != tmp_im) {
+        gdFree(tmp_im);
+    }/* if */
 
-gdImagePtr Scale(const gdImagePtr src, const unsigned int src_width, const unsigned int src_height, const gdImagePtr dst, const unsigned int new_width, const unsigned int new_height)
-{
-	gdImagePtr tmp_im;
-
-	tmp_im = gdImageCreateTrueColor(new_width, src_height);
-	if (tmp_im == NULL) {
-		return NULL;
-	}
-	_gdScaleHoriz(src, src_width, src_height, tmp_im, new_width, src_height);
-
-	_gdScaleVert(tmp_im, new_width, src_height, dst, new_width, new_height);
-
-	gdFree(tmp_im);
 	return dst;
-}
+}/* gdImageScaleTwoPass*/
+
 
 /*
 	BilinearFixed, BicubicFixed and nearest implementations are rewamped versions of the implementation in CBitmapEx
 			if (src->interpolation == NULL) {
 				return NULL;
 			}
-			im_scaled = gdImageScaleTwoPass(src, src->sx, src->sy, new_width, new_height);
+			im_scaled = gdImageScaleTwoPass(src, new_width, new_height);
 			break;
 	}
+
 	return im_scaled;
 }
 

tests/Makefile.am

 	gif/gif_null \
 	gif/bug00181 \
 	gif/bug00227 \
-	bmp/bmp_null
+	bmp/bmp_null \
+	gdinterpolatedscale/gdTrivialResize
 
 EXTRA_PROGRAMS = \
 	gdimagestringft/gdimagestringft_bbox \

tests/gdinterpolatedscale/gdTrivialResize.c

+#include <stdio.h>
+
+#include "gd.h"
+#include "gdtest.h"
+
+/* Test gdImageScale() with bicubic interpolation on a simple
+ * all-white image. */
+
+gdImagePtr mkwhite(int x, int y)
+{
+    gdImagePtr im;
+
+	im = gdImageCreateTrueColor(x, y);
+	gdImageFilledRectangle(im, 0, 0, x-1, y-1,
+                           gdImageColorExactAlpha(im, 255, 255, 255, 0));
+
+    gdTestAssert(im != NULL);
+
+    gdImageSetInterpolationMethod(im, GD_BICUBIC);    // FP interp'n
+
+    return im;
+}/* mkwhite*/
+
+
+/* Fill with almost-black. */
+void mkblack(gdImagePtr ptr)
+{
+    gdImageFilledRectangle(ptr, 0, 0, ptr->sx - 1, ptr->sy - 1,
+                           gdImageColorExactAlpha(ptr, 2, 2, 2, 0));
+}/* mkblack*/
+
+
+#define CLOSE_ENOUGH 15
+
+void scaletest(int x, int y, int nx, int ny)
+{
+    gdImagePtr im, imref, tmp, same;
+
+	imref = mkwhite(x, y);
+    im = mkwhite(x, y);
+    tmp = gdImageScale(im, nx, ny);
+    same = gdImageScale(tmp, x, y);
+
+    /* Test the result to insure that it's close enough to the
+     * original. */
+    gdTestAssert(gdMaxPixelDiff(im, same) < CLOSE_ENOUGH);
+
+    /* Modify the original and test for a change again.  (I.e. test
+     * for accidentally shared memory.) */
+    mkblack(tmp);
+    gdTestAssert(gdMaxPixelDiff(imref, same) < CLOSE_ENOUGH);
+
+    gdImageDestroy(im);
+    gdImageDestroy(tmp);
+    gdImageDestroy(same);
+}/* scaletest*/
+
+void do_test(int x, int y, int nx, int ny)
+{
+	gdImagePtr im, imref, tmp;
+    gdImagePtr same, same2;
+
+	im = mkwhite(x, y);
+    imref = mkwhite(x, y);
+
+    same = gdImageScale(im, x, y);
+
+    /* Trivial resize should be a straight copy. */
+    gdTestAssert(im != same);
+    gdTestAssert(gdMaxPixelDiff(im, same) == 0);
+    gdTestAssert(gdMaxPixelDiff(imref, same) == 0);
+
+    /* Ensure that modifying im doesn't modify same (i.e. see if we
+     * can catch them accidentally sharing the same pixel buffer.) */
+    mkblack(im);
+    gdTestAssert(gdMaxPixelDiff(imref, same) == 0);
+
+    gdImageDestroy(same);
+    gdImageDestroy(im);
+
+    /* Scale horizontally, vertically and both. */
+    scaletest(x, y, nx, y);
+    scaletest(x, y, x, ny);
+    scaletest(x, y, nx, ny);
+}
+
+int main(int argc, char **argv)
+{
+
+    do_test(300, 300, 600, 600);
+    do_test(3200, 2133, 640, 427);
+
+    return gdNumFailures();
+}

tests/gdtest/gdtest.c

 #include <stdio.h>
 #include <string.h>
 #include <math.h>
+#include <limits.h>
 #include "gd.h"
 
 #include "gdtest.h"
 #include "test_config.h"
 
+static inline int max(int a, int b) {return a > b ? a : b;}
+
 void gdSilence(int priority, const char *format, va_list args)
 {
 	(void)priority;
 {
 	int x, y;
 	int c1, c2;
+#   define UP_DIFF(var) result_ret->max_diff = max((var), result_ret->max_diff)
 
 	for (y = 0; y < gdImageSY(buf_a); y++) {
 		for (x = 0; x < gdImageSX(buf_a); x++) {
 				g1 = gdTrueColorGetGreen(c1);
 				g2 = gdTrueColorGetGreen(c2);
 				diff_g = abs (g1 - g2);
+
 				diff_g *= 4;  /* emphasize */
 				if (diff_g) {
 					diff_g += gdGreenMax/2; /* make sure it's visible */
 			}
 		}
 	}
+#   undef UP_DIFF
+}
+
+
+/* Return the largest difference between two corresponding pixels and
+ * channels. */
+unsigned int gdMaxPixelDiff(gdImagePtr a, gdImagePtr b)
+{
+    int diff = 0;
+    int x, y;
+
+    if (a == NULL || b == NULL || a->sx != b->sx || a->sy != b->sy)
+        return UINT_MAX;
+
+    for (x = 0; x < a->sx; x++) {
+        for (y = 0; y < a->sy; y++) {
+            int c1, c2;
+
+			c1 = gdImageGetTrueColorPixel(a, x, y);
+			c2 = gdImageGetTrueColorPixel(b, x, y);
+            if (c1 == c2) continue;
+
+            diff = max(diff, abs(gdTrueColorGetAlpha(c1) - gdTrueColorGetAlpha(c2)));
+            diff = max(diff, abs(gdTrueColorGetRed(c1)   - gdTrueColorGetRed(c2)));
+            diff = max(diff, abs(gdTrueColorGetGreen(c1) - gdTrueColorGetGreen(c2)));
+            diff = max(diff, abs(gdTrueColorGetBlue(c1)  - gdTrueColorGetBlue(c2)));
+        }/* for */
+    }/* for */
+
+    return diff;
 }
 
+
 int gdTestImageCompareToImage(const char* file, unsigned int line, const char* message,
                               gdImagePtr expected, gdImagePtr actual)
 {
 	return res;
 }
 
+
+static int failureCount = 0;
+
+int gdNumFailures() {
+    return failureCount;
+}
+
 int _gdTestAssert(const char* file, unsigned int line, const char* message, int condition)
 {
 	if (condition) return 1;
 	_gdTestErrorMsg(file, line, "%s", message);
+
+    ++failureCount;
+
 	return 0;
 }
 
 	va_end(args);
 	fprintf(stderr, "%s:%u: %s", file, line, output_buf);
 	fflush(stderr);
+
+    ++failureCount;
+
 	return 0;
 }
 /* }}} */

tests/gdtest/gdtest.h

 int gdTestImageCompareToFile(const char* file, unsigned int line, const char* message,
                              const char *expected_file, gdImagePtr actual);
 
+unsigned int gdMaxPixelDiff(gdImagePtr a, gdImagePtr b);
+
 int _gdTestAssert(const char* file, unsigned int line, const char* message, int condition);
 
 int _gdTestErrorMsg(const char* file, unsigned int line, const char* string, ...);
 
 void gdSilence(int priority, const char *format, va_list args);
 
+int gdNumFailures(void);
+
 #endif /* GD_TEST_H */
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.