Problem/Motivation

RotateImageEffect::transformDimensions() only transforms image dimensions for rotation angles that are a multiple of 90°.

However, algorithms exist that can help calculating the dimensions for any angle.

Link where this is explained:
- http://stackoverflow.com/questions/622140/calculate-bounding-box-coordin...

There is a similar issue in the imagemagick toolkit, as this toolkit cannot retrieve dimensions of intermediate processing results (as can be done with GD): #1551262: Rotate should alter dimensions

This can be seen as a follow-up for #1129642: Populate HTML image tags with dimension attributes (like D6 imagefield) without re-introducing I/O

Proposed resolution

Implement an utility class that returns algorithmically the same results that can be expected by executing PHP GD imagerotate() function in PHP versions >5.5

Remaining tasks

  • review patch
  • commit

User interface changes

None

API changes

None

Data model changes

None

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug, because responsive images cannot output images in a srcset in combination with sizes, if the width is unknown. See #2420751: ImageEffectBase::transformDimensions() should have a sane default implementation, comments #9 and #13:

Responsive images cannot output images in a srcset in combination with sizes, if the width is unknown, it would cause problems in the browser. Responsive images can work with images without a known dimension when the mapping is using a plain image effect.

Issue priority Normal, because affects one piece of functionality and does not impact the overall functionality of the software.
Disruption Not disruptive.
CommentFileSizeAuthor
#52 1551686-52.patch162.66 KBmondrake
#52 interdiff_43-52.txt262.3 KBmondrake
#43 interdiff_37-43.txt1.75 KBmondrake
#43 1551686-43.patch162.59 KBmondrake
#37 1551686-37.patch162.41 KBmondrake
#37 interdiff_29-37.txt176.71 KBmondrake
#35 1551686-35.patch97.24 KBmondrake
#35 interdiff_29-35.txt143.82 KBmondrake
#29 1551686-29.patch55.61 KBmondrake
#29 interdiff_27-29.txt679 bytesmondrake
#27 interdiff_23-27.txt77.02 KBmondrake
#27 1551686-27.patch55.22 KBmondrake
#23 1551686-23.patch35.26 KBmondrake
#19 image_rotate_dimensions-1551686-19.patch22 KBadci_contributor
#19 1551686-17-19.txt3 KBadci_contributor
#17 image_rotate_dimensions-1551686-17.patch21.94 KBankitgarg
#14 image_rotate_dimensions-1551686-14.patch22.01 KBfietserwin
#13 image_rotate_dimensions-1551686-13.patch11.19 KBfietserwin
#11 image_rotate_dimensions-1551686-1.patch9.82 KBfietserwin
#10 1551686-10.patch6.39 KBfietserwin
#8 1551686-8.patch5.52 KBfietserwin
#3 drupal-1551686-3.patch5.88 KBfietserwin
#1 drupal-1551686-1.patch4.82 KBfietserwin
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

fietserwin’s picture

Status: Active » Needs review
FileSize
4.82 KB

Please find attached a patch that implements image_rotate_dimensions() for all angles including tests.

Note that the current code happens to have an error for angles like 90.5 as modulo (%) operates on integer values (thus values are first rounded, then the remainder is calculated).

Status: Needs review » Needs work

The last submitted patch, drupal-1551686-1.patch, failed testing.

fietserwin’s picture

Status: Needs work » Needs review
FileSize
5.88 KB

Removed the offending test as it tested the old "incorrect" situation: no dimensions returned for angles that are not a multiple of 90 degrees.

fietserwin’s picture

Issue summary: View changes

Updated issue summary.

fietserwin’s picture

fietserwin’s picture

cweagans’s picture

Issue tags: +Needs backport to D7
claudiu.cristea’s picture

Great! Can you rework the patch?

fietserwin’s picture

FileSize
5.52 KB

Reworked.

Status: Needs review » Needs work

The last submitted patch, 1551686-8.patch, failed testing.

fietserwin’s picture

Status: Needs work » Needs review
FileSize
6.39 KB

OK, there are tests to check the image dimensions by checking the width and height attributes on generated img tags. I prefer to test the algorithm directly, thus more like a unit test than a functional test. Therefore the added test stays in the ImageEffectsTest class.

Corrected the dimensions test with a rotation over a non-multiple of 90 degrees angle and added a negative angle in the new unit test.

fietserwin’s picture

Issue summary: View changes

Updated issue summary.

fietserwin’s picture

Rerolled and locally tested on both PHP5.4 and 5.5. the bug in PHP5.5 (https://bugs.php.net/bug.php?id=65148) is worse then I thought in #2215369: Various bugs with PHP 5.5 imagerotate(), including when incorrect color indices are passed in, for the tested angles and dimensions, I found that:

  • image width can be 0 to 3 pixels smaller than in 5.4.
  • image height can be 0 to 3 pixels smaller than in 5.4.
  • sum of these 2 differences can be 5 pixels.

This raises the question if, with the given bug, these calculations should be used to derive image dimensions or not? As these calculations are used to generate width and height attributes for an {img} tag, the images may become a bit overstretched on the web page. OTOH, this overstretching may also be seen as partially undoing the error in GD and thereby preventing design mess-ups.

Some more remarks:
- The calculations have been punt in ImageUtility and not in the rotate image effect, even if this will be the only usage in core. I have done this as to allow an IM toolkit in contrib access to this algorithm as well, and IM will need it to be able to return correct values for its getWidth() and getHeight() methods.
- I think that the test file should be moved to core\tests\Drupal\Tests\Component\Utility, what do others think?

Status: Needs review » Needs work

The last submitted patch, 11: image_rotate_dimensions-1551686-1.patch, failed testing.

fietserwin’s picture

Status: Needs work » Needs review
FileSize
11.19 KB

Oops, I only locally tested the added tests, not if existing tests would now fail.

As multiples of 90 degrees versus non-multiples are no longer separate cases, I decided to remove the failing test. IMO this is the correct decision, as it did test the fact that we had a bug (or missing feature if you prefer so) in the image dimensions callback of the rotate effect. Correct return values for this dimensions callback are now much more extensively tested with the added unit test.

fietserwin’s picture

A new patch. I dove into the actual GD code to find out why and how PHP5.4 and 5.5 differ. This lead to 2 ways of computing the resulting dimensions and the one actually called depends on the PHP version. If one actually matches the way that IM computes dimensions we can make that selection logic depend on the toolkit in use as well.

What does this patch do:

  • RotateImageEffect::transformDimensions() is altered so that it now also delivers dimensions for non 90 degree multiples.
  • A test in ImageDimensionsTest that tested that it did not do so, has been removed.
  • The actual implementation of the algorithm is done in Image Utility, and exists of 2 algorithms based on actual GD code. Which one is taken depends on the PHP version.
  • There's a 1 line refactoring in RotateImageEffect (-$degrees instead of -1 * $degrees)
  • ImageToolkit/Operation/gd/Rotate has been adapted to prevent some GD imagerotate quirks for angles that are 0 or a multiple of 90 degrees.
  • ImageToolkit/Operation/gd/Rotate contained an error in that it did not destroy the old image resource (imagerotate() results in a new resource).
  • ImagUtilityTest is extended with extensive testing of the new calculations and a few documentation lines have been refactored.

Please review and decide whether to move the Image Utility test file to core\tests\Drupal\Tests\Component\Utility.

mondrake’s picture

Well, thanks a lot @fietserwin for the extensive research on this issue.

IMHO, I think we should have 'our truth' for what we expect to be the dimensions resulting from a rotation. It's a tradeoff: is it preferable to go after each single toolkit / version to match their algorithms and adapt code and tests every time a change occurs, or rather have 'our truth' and always adapt the image to this truth which also means consistent image dimensions across toolkits/versions?

I vote for the latter: this would mean to:

  1. determine 'our truth' dimensions (having decided to use one of the three algorithms)
  2. execute the toolkit rotate operation
  3. execute on the resulitng image a resize operation to the 'our truth' dimensions

Some code review:

+++ b/core/lib/Drupal/Component/Utility/Image.php
@@ -63,4 +63,286 @@ public static function scaleDimensions(array &$dimensions, $width = NULL, $heigh
+  public static function rotateDimensions54(array $dimensions, $degrees) {

this method is never used

+++ b/core/modules/image/src/Plugin/ImageEffect/RotateImageEffect.php
@@ -7,6 +7,7 @@
+use Drupal\Component\Utility\Image;

I's suggest to alias it to ImageUtility to avoid confusion with the other Image class

+++ b/core/modules/image/src/Tests/ImageDimensionsTest.php
@@ -200,27 +200,6 @@ function testImageDimensions() {
-    // Rotate to a non-multiple of 90 degrees.
-    $effect = array(
-      'id' => 'image_rotate',
-      'data' => array(
-        'degrees' => 57,
-        'random' => FALSE,
-      ),
-      'weight' => 7,
-    );
-
-    $effect_id = $style->addImageEffect($effect);
-    $style->save();
-    $this->assertEqual($this->getImageTag($variables), '<img class="image-style-test" src="' . $url . '" alt="" />');
-    $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
-    $this->drupalGet($url);
-    $this->assertResponse(200, 'Image was generated at the URL.');
-    $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-
-    $effect_plugin = $style->getEffect($effect_id);
-    $style->deleteImageEffect($effect_plugin);
-

Instead of removing this, if we follow my general suggestion, we might keep this with its own expected width and height attributes on the img tag

+++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php
@@ -74,14 +74,40 @@ protected function execute(array $arguments) {
+        \Drupal::logger('image')->notice('The image %file could not be rotated because imagerotate() failed.', array('%file' => $this->getToolkit()->getImage()->getSource()));

$this->logger->notice(....

+++ b/core/tests/Drupal/Tests/Component/Image/ImageUtilityTest.php
@@ -161,4 +161,102 @@ public function providerTestScaleDimensions() {
+  public function providerTestRotateDimensions() {

I am dubious about using GD functions to determine the expected output of Image::rotateDimensions

In fact we would make the expected results dependent on the GD version - or am I missing something?

Leaving in NR status as I believe more eyes are needed here.

jhedstrom’s picture

Status: Needs review » Needs work
Issue tags: +Needs reroll

Needs a reroll, and the feedback in #15 should be addressed.

ankitgarg’s picture

Status: Needs work » Needs review
FileSize
21.94 KB

Rerolled

Status: Needs review » Needs work

The last submitted patch, 17: image_rotate_dimensions-1551686-17.patch, failed testing.

adci_contributor’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll
FileSize
3 KB
22 KB

Fixed the exceptions in #17.

attiks’s picture

Patch is looking good, some small code style issues.

  1. +++ b/core/lib/Drupal/Component/Utility/Image.php
    @@ -62,5 +62,285 @@ public static function scaleDimensions(array &$dimensions, $width = NULL, $heigh
    +        $newWidth = $width;
    +        $newHeight = $height;
    

    don't use camelCase for local variables.

  2. +++ b/core/lib/Drupal/Component/Utility/Image.php
    @@ -62,5 +62,285 @@ public static function scaleDimensions(array &$dimensions, $width = NULL, $heigh
    +        $sinImprecision = $sin < 0 ? -$imprecision : $imprecision;
    +        $cosImprecision = $cos < 0 ? -$imprecision : $imprecision;
    

    don't use camelCase for local variables.

Status: Needs review » Needs work

The last submitted patch, 19: image_rotate_dimensions-1551686-19.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
FileSize
35.26 KB

A recap and a new proposal (patch).

Recap:

  1. The resulting dimensions of an image rotation differ by different GD or other toolkit implementations. PHP 5.4 and PHP 5.5 show different results, and within PHP 5.5 we can have even have different results based on different interpolation algorithms that can be used. ImageMagick provides also different results from GD and they also can be influenced by setting different interpolation algorithms.
  2. We want in this issue the RotateImageEffect::transformDimension method to be able to calculate the expected dimensions resulting from a rotate operation for any angle, and at the image effect level we need such results to be implementation agnostic, i.e. independent from toolkit or from version. We do not take interpolation algorithms in the equation at the moment, so our only input variables are image dimensions and degrees of rotation.
  3. For consistency, we need to ensure that an toolkit rotate operation always delivers a rotated image whose dimensions are the same as the ones that RotateImageEffect::transformDimension would return.

Proposal:

  • This patch implements a Rectangle component class that calculates in abstraction from any implementation the bounding width and height of a rotated image. For the good and for the bad, it uses the PHP 5.4 GD algorithm – taken from @fietserwin excellent patch in #14. PHP 5.4 because its current results are in fact embedded in many tests.
  • Provides a PHPUnit test to ensure that the calculation performed by the Rectangle class yields the same results as PHP 5.4. A data sample of input/output values has been built with direct calls to PHP GD functions. More details in code.
  • RotateImageEffect::transformDimensions() is altered so that it now also delivers dimensions for non 90 degree multiples.
  • ImageDimensionsTest is altered so that it now checks that rotation for non 90 degree multiples ends up in the height and width attributes of the img tag.
  • ImageToolkit\Operation\gd\Rotate is altered so that if the dimensions of the image after rotation do not coincide with the expected ones from the Rectangle class calculation, the image is also resized to expected dimensions.
  • The PHP version dependent code in ToolkitGdTest has been removed as now we no longer need to care about it.

No interdiff as in fact it would not make much sense.

Thoughts?

fietserwin’s picture

Without reviewing the actual code, but reviewing the proposal:

The math is actually not that difficult. But the math works with real numbers while computer image rotation works with pixels. This may lead to differences in the resulting canvas size depending on rotation point (rotate around corner or rotate around the center point) and rounding (go for safe and always round up (or actually "away from 0") or accept that a small part of a corner pixel may fall outside the result canvas).

Another reason that the resulting canvases can differ is when there is an plain error in the bounding box calculation, like in PHP5.5+ GD. This bug does not result in distorted images but in images where the rotated original is partly cropped or where there is to much background.

The rotation algorithm itself will not influence resulting canvas size and will in principle result in the same image size, though, of course, different algorithms will have different sharpness, anti-aliasing and pixel interpolation.

Thus resizing the image to comply with the calculated bounding box is wrong, we should do a canvas resize!

Let's see how that would work out:

  • The actual toolkit operation returns an image that is larger than we calculated: the image will contain more background and thus can be cropped to the expected size. (TODO: determine the anchor of the cropping, that might differ per toolkit.)
  • The actual toolkit operation returns an image that is smaller than we calculated: the image will already miss part of the (original) corners and enlarging the image will not bring that back but will only distort the rest of the image. So, here as well, we should resize the canvas by adding background around the result.

What do you think?

mondrake’s picture

#24: thanks @fietserwin for review.

To be honest I did not give too much thought about 'what' to do if the actual rotated image dimensions differ from the expected ones. I was just thinking that differences would be minimal, and somehow consistent between widht and height, so that a resize would not distort the final result.

But your points are valid, so setting to NW to do that.

Two questions pop up to me now:
a) what if width is smaller and height is bigger than expected (or viceversa)?
b) #24 describes a situation in GD toolkit where we have the possibility to compare rotation results to expected, taking the resource's width and width after the rotation. But what if, like in ImageMagick, we can only determine the expected dimensions and have to force the image to be consistent? Would resize be OK in that case?

More in general - I think we need an update to the issue summary, and probably we need also screenshots to compare PHP 5.4 and PHP 5.5 results based on this patch.

fietserwin’s picture

It is not so much the distortion that I am afraid of, but I have seen images resized by 1 pixel (by the browser or by a qualitatively bad resize algorithm) that lost all sharpness there was and rotated images are already not among the sharpest. So IMO any (additional) resize operation on the image itself is bad. That was another reason to propose canvas resize and not image resize (and it's faster as well, but performance is not an issue for me in the case of derivative creation).

I don't see a problem with your point a), we jsut create a canvas with a solid given background color and place the rotated image on that canvas, automatically cropping or adding background.

Other options we have:

  • is to not resize the image/canvas at all, but just pass on the computed dimensions:
    • If the rotate is the last effect of the style, the browser will receive width and height attributes that may be a bit off and will do a resize itself (not so good, see above).
    • If the rotate is not the last effect, chances are that the next effect will resize/crop & resize the image to fit within some bounding box and the dimensions (according to the transformDimension method) will (most likely) be correct again.
  • Leave as is (return null for the width and height).
  • Only do so for GD on PHP5.4. But as we might even push requirements to PHP5.5 that does not make much sense.
  • Accept that it may differ per toolkit (version) and defer the computation to the toolkit, with a fallback to "just pass our truth", canvas resize to our truth, or to "Leave as is (return null)" if a toolkit does not implement this.

This is difficult indeed. I think I (still) prefer the latest option. Anyway, whatever we choose, I think we will have to wait for 8.1 (API addition) or 8.0.1.

mondrake’s picture

PHP requirements have been raised to 5.5, so it does not make sense to keep an algorithm that calculates rotated dimensions based on 5.4.

This patch now has an algorithm that mimicks the calculation of dimensions done in PHP 5.5+ (after some hair pulling to understand the imprecisions issue as pointed out in #14, thanks again @fietserwin for posting that patch!). Again the logic of that algorithm may be questionable, but I believe we need to be pragmatic and just adopt the one logic that will be most commonly used.

Here I have dropped the changes to the GD rotate operations discussed between #23 and #26 - AFAICS all the different interpolation methods used in GD 5.5 for image rotation rely on the same algorithm to determine the dimensions of the canvas before starting to copy the pixels at destination. We may want to explore in a spin-off issue if the potential use of the imagesetinterpolation() function in contrib could affect dimensions, but that seems quite an edge case.

Note: do not be scared by the size of the patch - most of it is test data for PHPUnit... :)

Status: Needs review » Needs work

The last submitted patch, 27: 1551686-27.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
FileSize
679 bytes
55.61 KB

Forgot to change another instance of rotate test :(

The last submitted patch, 27: 1551686-27.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 29: 1551686-29.patch, failed testing.

Status: Needs work » Needs review

mondrake queued 29: 1551686-29.patch for re-testing.

mondrake’s picture

#29 is green

mondrake’s picture

Assigned: Unassigned » mondrake
Status: Needs review » Needs work

Found some flaws in the algorithm matching PHP GD calculations. Working on a patch.

mondrake’s picture

I could not sort out how to deal with +/- 1 pixel differences in some cases, due to imprecision differences between the use of C floats internally in GD, and of C doubles by PHP.

Here I am taking a different approach - an utility class called by RotateImageEffect actually creates a new GD image via the GD toolkit, rotates it, and returns the dimensions after rotation. It's much simpler, but has a small performance impact and makes the calculation dependent on the installed PHP version.

Cannot do any better ATM, if we find an algorithm that can match in PHP the same results that GD provides we can go back to it. The PHP unit test data sample is taken from PHP 5.5 GD (I added more samples for float and negative angles); that one can be used to test any alternative algorithm.

EDIT: do not consider this.

Status: Needs review » Needs work

The last submitted patch, 35: 1551686-35.patch, failed testing.

mondrake’s picture

Status: Needs work » Needs review
FileSize
176.71 KB
162.41 KB

Fixed the Rectangle class to cope with negative and float rotation angles. Added more test data in the PHPUnit test class to deal with negative, float and multiple of 30 degrees rotations. The latter one are the trickiest to deal with as it's where imprecision correction kicks in, really. There are 2000 test samples just for these.

The last submitted patch, 35: 1551686-35.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 37: 1551686-37.patch, failed testing.

Status: Needs work » Needs review

mondrake queued 37: 1551686-37.patch for re-testing.

mondrake’s picture

#37 is green.

claudiu.cristea’s picture

Issue tags: +Needs beta evaluation
  1. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,221 @@
    + * Different versions of PHP for the GD toolkit, and alternative toolkits,
    + * use different algorithms to perform the rotation of an image and result
    + * in different dimensions of the output image.
    + * This prevents predictability of the final image size for instance by the
    + * image rotate effect, and by the image toolkit rotate operation.
    

    The 2 paragraphs need to be separated with a line OR merged into one paragraph?

  2. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,221 @@
    + * Here we are using an algorithm that produces the same results as PHP 5.5+
    

    Is this utility class needed only for PHP <5.5? If so, we can drop it because Drupal 8 requires >=5.5. Maybe I misunderstood the phrase.

  3. +++ b/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php
    @@ -0,0 +1,4299 @@
    +   * @return array
    

    @return array[]?

Looks good to me otherwise.

mondrake’s picture

FileSize
162.59 KB
1.75 KB

#42 - fixed all three points.

@claudiu.cristea - thanks for review. We need this utility class regardless of the PHP version. Its purpose is to calculate rotated image dimensions in abstract, without calling GD. This way, RotateImageEffect::transformDimensions can calculate expected dimensions after rotation on an algorithmic basis without calling GD primitives.

ImageMagick contrib also needs this (there's a copy of it in the contrib module), because since ImageMagick does not operate on an image in memory but produces a sequence of command line arguments for the convert binary, we need to be able to calculate dimensions without having the possibility to access an image resource. See #1551262: Rotate should alter dimensions.

mondrake’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

Updated IS.

claudiu.cristea’s picture

Status: Needs review » Reviewed & tested by the community

@mondrake, yes, that I understood. But if I remember well there's something related to precision in PHP 5.5 GD. So, based on the former comment, I thought, on first read, that the class is a 5.5 fix only. Now it makes more sense. Thank you.

+1 for the test :)

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 43: 1551686-43.patch, failed testing.

mondrake queued 43: 1551686-43.patch for re-testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

bot failure. back to RTBC per #45

mondrake’s picture

Issue summary: View changes
Issue tags: -Needs beta evaluation
mondrake’s picture

fietserwin’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,224 @@
    +    if ($width >= 0 && $height >= 0) {
    

    What if any of these is negative? Silent failures later on? Can't we throw an (invalid argument) exception?

  2. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,224 @@
    +    // Keep a copy of the signed input angle, reduced to the -360 to 360
    +    // degrees range.
    +    $degrees = $angle;
    +    while ($degrees > 360) {
    

    The GD rotate operation ensures that imagerotate() is never called with a negative angle (because of the errors that GD has). So I would like to propose to remove all checks for negative angles as well as all tests with negative angles. Also rephrase the class doc a bit so this is documented.

    This also means you can get rid of $degrees and use $angle everywhere $degrees is used now.

  3. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,224 @@
    +    if ((((int) $angle) === $angle) && ($angle % 90 === 0)) {
    

    If $angle indeed is a float, this can never be true: use == instead of ===.

  4. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,224 @@
    +    if ((((int) $degrees) === $degrees) && (in_array($degrees, [60, 300, -240, 150]))) {
    

    use ==; remove negative angle; personally I prefer less brackets when not needed and the in simple cases like here.

  5. +++ b/core/lib/Drupal/Component/Utility/Rectangle.php
    @@ -0,0 +1,224 @@
    +    elseif ((((int) $degrees) === $degrees) && (in_array($degrees, [-150, -330]))) {
    

    Whole section can be removed: only for negative angles, will never be used

  6. +++ b/core/tests/Drupal/Tests/Component/Utility/RectangleTest.php
    @@ -0,0 +1,4299 @@
    +  public function testRotateDimensions($width, $height, $angle, $exp_width, $exp_height) {
    

    Missing @param lines in documentation.

mondrake’s picture

#51 all done, thanks @fietserwin for review.

#51.2 - I missed that gd_rotate normalises angle to 0-360. This means that to have results for Rectangle that match gd_rotate, we should have test data that uses the GD toolkit instead of imagerotate() directly. This greatly simplifies the Rectangle class algorithm as most of the precision issues are related to negative angles multiple of 30° that are passed directly to imagerotate(). Still, our Rectangle utility class needs to return results for negative angles, so I kept test data for negative angles in the PHPUnit test.

mondrake’s picture

Status: Needs work » Needs review
fietserwin’s picture

Status: Needs review » Reviewed & tested by the community

Looks good, includes even tests for the exception now thrown by the constructor.

Note: I'm not sure about the D7 backport tag, but that's not for now.

mondrake’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 52: 1551686-52.patch, failed testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

Apparently, bot failure in #56

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 52: 1551686-52.patch, failed testing.

The last submitted patch, 52: 1551686-52.patch, failed testing.

The last submitted patch, 52: 1551686-52.patch, failed testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

bots're dizzy today

xjm’s picture

Version: 8.0.x-dev » 8.1.x-dev

Quite extensive test coverage in this patch!

Since this adds to the API and additional functionality, targeting against 8.1.x per https://www.drupal.org/core/d8-allowed-changes#minor. I'm also not entirely clear on how this is a bug (even after reading #2420751-9: ImageEffectBase::transformDimensions() should have a sane default implementation comments 9 and 12, but leaving that as-is for now).

Thanks everyone!

xjm’s picture

Also tagging for an issue summary update to help explain in more detail why this is a bug.

mondrake’s picture

Hidden old files.

mondrake’s picture

Issue summary: View changes
Issue tags: -Needs issue summary update

#63: in fact the most sensible description of the bug I can find is in #2420751-13: ImageEffectBase::transformDimensions() should have a sane default implementation:

Responsive images cannot output images in a srcset in combination with sizes, if the width is unknown, it would cause problems in the browser. Responsive images can work with images without a known dimension when the mapping is using a plain image effect.

Updated the IS accordingly. Whether this still qualifies as a bug I do not know, but it's definitely a feature needed by ImageMagick (see #2620786: Remove the Rectangle class and #2568619: Plan for ImageMagick 8) and potentially in core too (#2583041: GD toolkit & operations should catch \Throwable to fail gracefully in case of errors).

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Given that this is a behaviour change I think putting this only in 8.1.x makes sense. I've ummed and ahhed about whether a change record is required - given that the outcome of this change is that rotated images always have dimensions if the angle is not random and imagemagick does not need the class I'm not sure that it is required.

I think it is a shame we don't have Drupal\Component\Image but making that now does not make a lot of sense since Color and Image already exist in Utility...

I'm not sure this can be ported to Drupal 7 since image dimension will change (to be consistent with PHP 5.5 GD regardless of which version) - this does not affect Drupal 8 because it only supports PHP5.5 and up.

Committed 1a6baa4 and pushed to 8.1.x. Thanks!

  • alexpott committed 1a6baa4 on 8.1.x
    Issue #1551686 by mondrake, fietserwin, adci_contributor, ankitgarg,...
mondrake’s picture

@alexpott re #66:

I have created a draft CR https://www.drupal.org/node/2645822, please review and publish if it's OK.
It takes into account #2420789: Make RotateImageEffect's random rotation deterministic, so ::transformDimensions() can return the actual dimensions, which will remove the last case of missing width/height attributes for random rotation angles.

Theoretically porting to D7 is still possible, since in the patch in #23 the Rectangle class can provide dimension calculation for PHP <= 5.4 while the one that was committed is for PHP 5.5 and above. So an implementation in D7 could check the PHP version in use and then use one or the other version to make the calculation. Not sure it's worth the effort, though.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.