Bug #65148 [Com]: imagerotate may alter image dimensions
| From: | erwin at burorader dot com | Date: | Wed, 30 Jul 2014 12:24:24 +0000 |
| Subject: | Bug #65148 [Com]: imagerotate may alter image dimensions | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-186881@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=65148&edit=1
ID: 65148
Comment by: erwin at burorader dot com
Reported by: imprec at gmail dot com
Summary: imagerotate may alter image dimensions
Status: Open
Type: Bug
Package: GD related
Operating System: Linux
PHP Version: 5.5.0
Block user comment: N
Private report: N
New Comment:
I dove a bit further into the code of both PHP 5.4 and 5.5 and found the following differences:
1) Special cases (i.e multiples of 90 degrees):
- PHP5.4 does normalize angles first to be between 0 and 360 and then checks for equality on 0, 90,
180 and 270
- PHP5.5 does not normalize but checks on angles 0, 90 to 90.01, 180 to 180.01, and 270 to 270.01.
The check on not being exactly equal can, considering the imprecision of float arithmetic, be
considered a good feature.
2) Computation
- PHP5.4 uses arithmetic that comes down to (PHP syntax and after normalizing to an angle between
-45 and +45):
$rad = deg2rad($degrees);
$cos = abs(cos($rad)); // always >= 0 for angles between -45 and +45;
$sin = abs(sin($rad));
// Shear 1: no intermediate results to store.
// Shear 2: the width remains the same as after shear 1.
$newHeight = (int) ($width * $sin + $height * $cos) + 1;
// Shear 3: the height remains the same as after shear 2, the width is
// recalculated based on the original dimensions.
$newWidth = (int) ($height * $sin + $width * $cos) + 1;
- PHP 5.5 use arithmetic that comes down to (PHP syntax):
$rad = deg2rad($degrees);
$cos = cos($rad);
$sin = sin($rad);
// This is how GD on PHP5.5 calculates the new dimensions.
$newWidth = abs((int) ($width * $cos)) + abs((int)($height * $sin + 0.5));
$newHeight = abs((int) ($width * $sin)) + abs((int)($height * $cos + 0.5));
Errors in PHP 5.5 code:
ad1)
- PHP5.5 misses negative angles like -90, -180 and -270,
- It misses angles just below such a multiple like 89.995.
Why is this a bug?
For reasons of symmetry I would expect both cases to be treated as an 'exact multiple' of
90 degrees and therefore being treated specially.
ad 2)
- PHP5.4 is always rounding up, which is what I would expect. OTOH, PHP5.5 is basically rounding
down, though is asymmetrically and only partly compensating for that. It should at least be
symmetrical, i.e. the 0.5 should be used in all parts of the calculation.
- The compensation (the + 0.5) is making the situation even worse (i.e. worse than just rounding
down) in cases where the (co)sine is negative. So the 0.5 should have the same sign as the
accompanying sine or cosine.
Why are these bugs?
For reasons of symmetry I would expect
- The same dimensions if I rotate an image over d or -d degrees.
- The same dimensions for rotating an image of WxH over d degrees and rotating an image of HxW over
90+d degrees, as the latter can be seen as rotating over 90 degrees (swapping W and H) and
subsequently rotating over d degrees.
Both symmetries are satisfied by PHP5.4, both aren't by PHP5.5.
Proposal:
- Reintroduce the 'normalization' of the angle to better decide if it is a special case.
- Return to the old PHP 5.4 calculations to compute the new dimensions. I do consider these to be
"correct".
Previous Comments:
------------------------------------------------------------------------
[2014-07-18 13:09:56] erwin at burorader dot com
Correction to previous comment: on PHP5.4 it used to be 42 x 24 (and not 42 x 23).
------------------------------------------------------------------------
[2014-07-18 13:05:33] erwin at burorader dot com
Same on Zend Server CE on Windows with PHP Version 5.5.7 (GD Version: bundled (2.1.0 compatible)).
I used this as test script:
<?php
$interpolations = array(
'IMG_BELL' => 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 = imagecreatefrompng('image-test.png');
$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);
}
print_r($results);
imagedestroy($img);
?>
Results:
Array
(
[IMG_BELL] => Array
(
[x] => 40
[y] => 22
)
[IMG_BESSEL] => Array
(
[x] => 40
[y] => 22
)
[IMG_BICUBIC] => Array
(
[x] => 40
[y] => 22
)
[IMG_BICUBIC_FIXED] => Array
(
[x] => 41
[y] => 23
)
[IMG_BILINEAR_FIXED] => Array
(
[x] => 41
[y] => 23
)
[IMG_BLACKMAN] => Array
(
[x] => 40
[y] => 22
)
[IMG_BOX] => Array
(
[x] => 40
[y] => 22
)
[IMG_BSPLINE] => Array
(
[x] => 40
[y] => 22
)
[IMG_CATMULLROM] => Array
(
[x] => 40
[y] => 22
)
[IMG_GAUSSIAN] => Array
(
[x] => 40
[y] => 22
)
[IMG_GENERALIZED_CUBIC] => Array
(
[x] => 40
[y] => 22
)
[IMG_HERMITE] => Array
(
[x] => 40
[y] => 22
)
[IMG_HAMMING] => Array
(
[x] => 40
[y] => 22
)
[IMG_HANNING] => Array
(
[x] => 40
[y] => 22
)
[IMG_MITCHELL] => Array
(
[x] => 40
[y] => 22
)
[IMG_POWER] => Array
(
[x] => 40
[y] => 22
)
[IMG_QUADRATIC] => Array
(
[x] => 40
[y] => 22
)
[IMG_SINC] => Array
(
[x] => 40
[y] => 22
)
[IMG_NEAREST_NEIGHBOUR] => Array
(
[x] => 40
[y] => 22
)
[IMG_WEIGHTED4] => Array
(
[x] => 40
[y] => 22
)
[IMG_TRIANGLE] => Array
(
[x] => 40
[y] => 22
)
)
Notes:
1) On PHP 5.4 it used to be 42 x 23 (it is a test case for Drupal: https://www.drupal.org/node/2215369).
2) The size can even be 2 off, depending on the interpolation setting.
3) I found that positive multiples of 90 degrees work correct, negative multiples are off by 1.
------------------------------------------------------------------------
[2013-06-28 08:54:03] imprec at gmail dot com
I tested and reproduced the issue with PHP55-201306280830
------------------------------------------------------------------------
[2013-06-27 20:25:31] pajoye@php.net
Please try 5.5 snapshots, this bug should be gone.
------------------------------------------------------------------------
[2013-06-27 14:12:26] imprec at gmail dot com
Description:
------------
When rotating image with GD, image dimension may be different after the rotation.
For example, I expect a 320x240 image to be 240x320 after rotation. It may be
239x320.
Test script:
---------------
This bug happens with the following script :
(google.png file can be retrieved from https://raw.github.com/avalanche123/Imagine/develop/tests/Imagine/Fixtures/google.png)
$resource = imagecreatefrompng('google.png');
var_dump(imagesx($resource));
$resource = imagerotate($resource, -90, 0);
var_dump(imagesy($resource));
Expected result:
----------------
int(364)
int(364)
Actual result:
--------------
int(364)
int(363)
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=65148&edit=1