From 22c487616f132ee7ebfa838bce9d14c9243bd2d3 Mon Sep 17 00:00:00 2001 From: "Christoph M. Becker" Date: Tue, 24 Oct 2017 14:42:03 +0200 Subject: [PATCH] Fixed bug #65148 (imagerotate may alter image dimensions) We apply the respective patches from external libgd, work around the still missing `gdImageClone()`, and fix the special cased rotation routines according to Pierre's patch (https://gist.github.com/pierrejoye/59d72385ed1888cf8894a7ed437235ae). We also cater to bug73272.phpt whose result obviously changes a bit. --- NEWS | 3 + ext/gd/libgd/gd_interpolation.c | 87 ++++++++++++---- ext/gd/tests/bug65148.phpt | 179 ++++++++++++++++++++++++++++++++ ext/gd/tests/bug73272.png | Bin 739 -> 741 bytes 4 files changed, 247 insertions(+), 22 deletions(-) create mode 100644 ext/gd/tests/bug65148.phpt diff --git a/NEWS b/NEWS index a2ac9e5c34..8f89c9fd62 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,9 @@ PHP NEWS . Fixed bug #75301 (Exif extension has built in revision version). (Peter Kokot) +- GD: + . Fixed bug #65148 (imagerotate may alter image dimensions). (cmb) + - intl: . Fixed bug #75317 (UConverter::setDestinationEncoding changes source instead of destination). (andrewnester) diff --git a/ext/gd/libgd/gd_interpolation.c b/ext/gd/libgd/gd_interpolation.c index 4446ec772b..07eefd6488 100644 --- a/ext/gd/libgd/gd_interpolation.c +++ b/ext/gd/libgd/gd_interpolation.c @@ -1709,13 +1709,28 @@ gdImagePtr gdImageScale(const gdImagePtr src, const unsigned int new_width, cons return im_scaled; } +static int gdRotatedImageSize(gdImagePtr src, const float angle, gdRectPtr bbox) +{ + gdRect src_area; + double m[6]; + + gdAffineRotate(m, angle); + src_area.x = 0; + src_area.y = 0; + src_area.width = gdImageSX(src); + src_area.height = gdImageSY(src); + if (gdTransformAffineBoundingBox(&src_area, m, bbox) != GD_TRUE) { + return GD_FALSE; + } + + return GD_TRUE; +} + gdImagePtr gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, const int bgColor) { float _angle = ((float) (-degrees / 180.0f) * (float)M_PI); const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const unsigned int new_width = (unsigned int)(abs((int)(src_w * cos(_angle))) + abs((int)(src_h * sin(_angle))) + 0.5f); - const unsigned int new_height = (unsigned int)(abs((int)(src_w * sin(_angle))) + abs((int)(src_h * cos(_angle))) + 0.5f); const gdFixed f_0_5 = gd_ftofx(0.5f); const gdFixed f_H = gd_itofx(src_h/2); const gdFixed f_W = gd_itofx(src_w/2); @@ -1726,6 +1741,12 @@ gdImagePtr gdImageRotateNearestNeighbour(gdImagePtr src, const float degrees, co unsigned int dst_offset_y = 0; unsigned int i; gdImagePtr dst; + gdRect bbox; + int new_height, new_width; + + gdRotatedImageSize(src, degrees, &bbox); + new_width = bbox.width; + new_height = bbox.height; if (new_width == 0 || new_height == 0) { return NULL; @@ -1768,8 +1789,6 @@ gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees, const int b const int angle_rounded = (int)floor(degrees * 100); const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const unsigned int new_width = (unsigned int)(abs((int)(src_w * cos(_angle))) + abs((int)(src_h * sin(_angle))) + 0.5f); - const unsigned int new_height = (unsigned int)(abs((int)(src_w * sin(_angle))) + abs((int)(src_h * cos(_angle))) + 0.5f); const gdFixed f_0_5 = gd_ftofx(0.5f); const gdFixed f_H = gd_itofx(src_h/2); const gdFixed f_W = gd_itofx(src_w/2); @@ -1780,6 +1799,8 @@ gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees, const int b unsigned int dst_offset_y = 0; unsigned int i; gdImagePtr dst; + int new_width, new_height; + gdRect bbox; const gdFixed f_slop_y = f_sin; const gdFixed f_slop_x = f_cos; @@ -1792,6 +1813,10 @@ gdImagePtr gdImageRotateGeneric(gdImagePtr src, const float degrees, const int b return NULL; } + gdRotatedImageSize(src, degrees, &bbox); + new_width = bbox.width; + new_height = bbox.height; + dst = gdImageCreateTrueColor(new_width, new_height); if (!dst) { return NULL; @@ -1831,8 +1856,7 @@ gdImagePtr gdImageRotateBilinear(gdImagePtr src, const float degrees, const int float _angle = (float)((- degrees / 180.0f) * M_PI); const unsigned int src_w = gdImageSX(src); const unsigned int src_h = gdImageSY(src); - unsigned int new_width = abs((int)(src_w*cos(_angle))) + abs((int)(src_h*sin(_angle) + 0.5f)); - unsigned int new_height = abs((int)(src_w*sin(_angle))) + abs((int)(src_h*cos(_angle) + 0.5f)); + unsigned int new_width, new_height; const gdFixed f_0_5 = gd_ftofx(0.5f); const gdFixed f_H = gd_itofx(src_h/2); const gdFixed f_W = gd_itofx(src_w/2); @@ -1844,6 +1868,12 @@ gdImagePtr gdImageRotateBilinear(gdImagePtr src, const float degrees, const int unsigned int dst_offset_y = 0; unsigned int src_offset_x, src_offset_y; gdImagePtr dst; + gdRect bbox; + + gdRotatedImageSize(src, degrees, &bbox); + + new_width = bbox.width; + new_height = bbox.height; dst = gdImageCreateTrueColor(new_width, new_height); if (dst == NULL) { @@ -1863,7 +1893,7 @@ gdImagePtr gdImageRotateBilinear(gdImagePtr src, const float degrees, const int const unsigned int m = gd_fxtoi(f_m); const unsigned int n = gd_fxtoi(f_n); - if ((m > 0) && (m < src_h - 1) && (n > 0) && (n < src_w - 1)) { + if ((m >= 0) && (m < src_h - 1) && (n >= 0) && (n < src_w - 1)) { const gdFixed f_f = f_m - gd_itofx(m); const gdFixed f_g = f_n - gd_itofx(n); const gdFixed f_w1 = gd_mulfx(f_1-f_f, f_1-f_g); @@ -1871,11 +1901,6 @@ gdImagePtr gdImageRotateBilinear(gdImagePtr src, const float degrees, const int const gdFixed f_w3 = gd_mulfx(f_f, f_1-f_g); const gdFixed f_w4 = gd_mulfx(f_f, f_g); - if (n < src_w - 1) { - src_offset_x = n + 1; - src_offset_y = m; - } - if (m < src_h-1) { src_offset_x = n; src_offset_y = m + 1; @@ -1890,13 +1915,13 @@ gdImagePtr gdImageRotateBilinear(gdImagePtr src, const float degrees, const int register int pixel2, pixel3, pixel4; if (src_offset_y + 1 >= src_h) { - pixel2 = bgColor; - pixel3 = bgColor; - pixel4 = bgColor; + pixel2 = pixel1; + pixel3 = pixel1; + pixel4 = pixel1; } else if (src_offset_x + 1 >= src_w) { - pixel2 = bgColor; - pixel3 = bgColor; - pixel4 = bgColor; + pixel2 = pixel1; + pixel3 = pixel1; + pixel4 = pixel1; } else { pixel2 = src->tpixels[src_offset_y][src_offset_x + 1]; pixel3 = src->tpixels[src_offset_y + 1][src_offset_x]; @@ -1946,8 +1971,7 @@ gdImagePtr gdImageRotateBicubicFixed(gdImagePtr src, const float degrees, const const float _angle = (float)((- degrees / 180.0f) * M_PI); const int src_w = gdImageSX(src); const int src_h = gdImageSY(src); - const unsigned int new_width = abs((int)(src_w*cos(_angle))) + abs((int)(src_h*sin(_angle) + 0.5f)); - const unsigned int new_height = abs((int)(src_w*sin(_angle))) + abs((int)(src_h*cos(_angle) + 0.5f)); + unsigned int new_width, new_height; const gdFixed f_0_5 = gd_ftofx(0.5f); const gdFixed f_H = gd_itofx(src_h/2); const gdFixed f_W = gd_itofx(src_w/2); @@ -1963,7 +1987,11 @@ gdImagePtr gdImageRotateBicubicFixed(gdImagePtr src, const float degrees, const unsigned int dst_offset_y = 0; unsigned int i; gdImagePtr dst; + gdRect bbox; + gdRotatedImageSize(src, degrees, &bbox); + new_width = bbox.width; + new_height = bbox.height; dst = gdImageCreateTrueColor(new_width, new_height); if (dst == NULL) { @@ -2206,8 +2234,11 @@ gdImagePtr gdImageRotateBicubicFixed(gdImagePtr src, const float degrees, const gdImagePtr gdImageRotateInterpolated(const gdImagePtr src, const float angle, int bgcolor) { - const int angle_rounded = (int)floor(angle * 100); - + /* round to two decimals and keep the 100x multiplication to use it in the common square angles + case later. Keep the two decimal precisions so smaller rotation steps can be done, useful for + slow animations, f.e. */ + const int angle_rounded = fmod((int) floorf(angle * 100), 360 * 100); + if (bgcolor < 0) { return NULL; } @@ -2224,6 +2255,18 @@ gdImagePtr gdImageRotateInterpolated(const gdImagePtr src, const float angle, in /* no interpolation needed here */ switch (angle_rounded) { + case 0: { + gdImagePtr dst = gdImageCreateTrueColor(src->sx, src->sy); + if (dst == NULL) { + return NULL; + } + dst->transparent = src->transparent; + dst->saveAlphaFlag = 1; + dst->alphaBlendingFlag = gdEffectReplace; + + gdImageCopy(dst, src, 0,0,0,0,src->sx,src->sy); + return dst; + } case -27000: case 9000: return gdImageRotate90(src, 0); diff --git a/ext/gd/tests/bug65148.phpt b/ext/gd/tests/bug65148.phpt new file mode 100644 index 0000000000..9effae9714 --- /dev/null +++ b/ext/gd/tests/bug65148.phpt @@ -0,0 +1,179 @@ +--TEST-- +Bug #65148 (imagerotate may alter image dimensions) +--SKIPIF-- + +--FILE-- + IMG_BELL, + 'IMG_BESSEL' => IMG_BESSEL, + 'IMG_BICUBIC' => IMG_BICUBIC, + 'IMG_BICUBIC_FIXED' => IMG_BICUBIC_FIXED, + 'IMG_BILINEAR_FIXED' => IMG_BILINEAR_FIXED, + 'IMG_BLACKMAN' => IMG_BLACKMAN, + 'IMG_BOX' => IMG_BOX, + 'IMG_BSPLINE' => IMG_BSPLINE, + 'IMG_CATMULLROM' => IMG_CATMULLROM, + 'IMG_GAUSSIAN' => IMG_GAUSSIAN, + 'IMG_GENERALIZED_CUBIC' => IMG_GENERALIZED_CUBIC, + 'IMG_HERMITE' => IMG_HERMITE, + 'IMG_HAMMING' => IMG_HAMMING, + 'IMG_HANNING' => IMG_HANNING, + 'IMG_MITCHELL' => IMG_MITCHELL, + 'IMG_POWER' => IMG_POWER, + 'IMG_QUADRATIC' => IMG_QUADRATIC, + 'IMG_SINC' => IMG_SINC, + 'IMG_NEAREST_NEIGHBOUR' => IMG_NEAREST_NEIGHBOUR, + 'IMG_WEIGHTED4' => IMG_WEIGHTED4, + 'IMG_TRIANGLE' => IMG_TRIANGLE, +); + +$img = imagecreate(40, 20); +$results = array(); + +foreach ($interpolations as $name => $interpolation) { + imagesetinterpolation($img, $interpolation); + $t = imagecolorallocatealpha($img, 0, 0, 0, 127); + $imgr = imagerotate($img, -5, $t); + $results[$name] = array('x' => imagesx($imgr), 'y' => imagesy($imgr)); + imagedestroy($imgr); +} + +imagedestroy($img); +print_r($results); +?> +===DONE=== +--EXPECT-- +Array +( + [IMG_BELL] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BESSEL] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BICUBIC] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BICUBIC_FIXED] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BILINEAR_FIXED] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BLACKMAN] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BOX] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_BSPLINE] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_CATMULLROM] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_GAUSSIAN] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_GENERALIZED_CUBIC] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_HERMITE] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_HAMMING] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_HANNING] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_MITCHELL] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_POWER] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_QUADRATIC] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_SINC] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_NEAREST_NEIGHBOUR] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_WEIGHTED4] => Array + ( + [x] => 40 + [y] => 23 + ) + + [IMG_TRIANGLE] => Array + ( + [x] => 40 + [y] => 23 + ) + +) +===DONE=== diff --git a/ext/gd/tests/bug73272.png b/ext/gd/tests/bug73272.png index 97bfadf983e7cf9f96600b2a0bc09a105cb083f4..514e012a92983a86dd87692666e16ead7d3b8cd3 100644 GIT binary patch delta 475 zcmV<10VMw81?2^h7Y>LB1^@s6&9V6300084ktLsh#6fiHIF@C3c>DPGyZZg<1-R02 zqp#~amSq{+?-A{9Z}&ak=T4Tp{8H0?V}JKCo13hvzuHfZpG2`aKIWC@s;KScICEEU zmq^-8jw=o(yGOBU7snXaY`i;6k5+M9`CrBMI-MhF5yvMtP{$}XZBBxHdLu2%((dG0 z`N=VV%L%F(mf_vnOpY0Jn;fO%m}P40bqb{XZe=F3h5D+}lVe7mCWkoQZ2yUFb!Fq2 z@pz+~l5uo-XU?u<92F);hk#4QQDMbb2>6+jajfe){mQQp@G~Xj7-R00Um@PFC>zJj zx_k)d4Cx6vOpP&2j#G4*9A)E}{oOhH@_j{r**Ip^>CA2EIA$E(UBVcncpNjDG1#9f z9Y?26Kxb4sj?FBAbUNM`qrrKOP8(x3Z6?R3r$@J`F-DU(KDkk52=grEcPpFY1y`^wEbXd1^D@88AjpQn2in>Gg| zq>rRPUb$%8owSZ)^S=wf9WO1pe@yoS&S%*&f7Y>IA1^@s6Jm*RZ00082ktLshZXL(6EDvuV|9)4$KfM4~I&SoJ zUB|L4WBbXO{q60($NSvLa+e?D*>CLcK4x>1RrOc<$?=mYHpj=j@>~_Qog8QG3hoj~ zyUB6I!DRO+HtpgV(#iq?kuupHKWm(#tJS#sr zW;sEBHN!HzTbs!-gKm?fbR4rxjlE8Rl;5q)WVTRWReEyFsMF*S$D8dx(XFm*95Wto zbW<{pF7M3Qm5igp#OM%k$v7&k_zD3(Q!`xRy5m|2$( z;hZ5oL5Ha^hRJb?E|a5d9J9YWXJ5XrC>zIrtU8^!Egi>EkR z^a<#UO2@I8C6G?X8)Gy$&(UdP%%;uc`1JJXHZ{g*62~Vu$_!zirTlJXbG+cn)vw)A zi^;J$et-3B9d{*rt>PHt+Hcr**Dj7R{_SGU-iV#2#u!cG7~}oBnEmr~k7Cp2fb@|f z6v!(Vjk}Z9acur~;kV? -- 2.49.0