Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

imagecopyresampled() produce artifacts on transparent PNG #661

Closed
jskp4 opened this issue Nov 12, 2020 · 22 comments
Closed

imagecopyresampled() produce artifacts on transparent PNG #661

jskp4 opened this issue Nov 12, 2020 · 22 comments
Milestone

Comments

@jskp4
Copy link

jskp4 commented Nov 12, 2020

imagecopyresampled() produce many artifacts on transparent PNG.
I added a yellow layer to the output image to make the artifacts easier to see.
In the test code below a small resolution (thumbnail) is used on the output image, but similar artifacts also appear in higher resolutions.
Everything worked well on previous GD version (2.0 I think), but after the upgrade on v 2.2.5, the artifacts began to appear.
I was looking for a solution on the internet, but I did not find anything functional, it is probably a bug in the function imagecopyresampled().

Test code:

$img_src = imagecreatefrompng('source.png');
$img_dest = imagecreatetruecolor(225, 225);
imagealphablending($img_dest, false);
imagesavealpha($img_dest, true);
imagecopyresampled($img_dest, $img_src, 0, 0, 0, 0, 225, 225, 2000, 2000);
imagepng($img_dest, 'imagecopyresampled1.png');

Input image:
source

Output image:
imagecopyresample1

@willson-chen
Copy link
Member

Hi @jskp4 ,

Everything worked well on previous GD version (2.0 I think), but after the upgrade on v 2.5, the artifacts began to appear.

The latest GD version is v2.3.0. There is no v2.5 right now. I think you mean v2.2.5? :)

@cmb69
Copy link
Contributor

cmb69 commented Nov 13, 2020

To clarify, you're complaining about the dark pixels in the corners of the image, and the "antialising" like effect around the pentagon shape? The latter is how gdImageCopyResampled() is supposed to work (more or less anyways), but the former looks like a bug (it seems that out of canvas pixels are assumed to be black, and taken into consideration).

@jskp4
Copy link
Author

jskp4 commented Nov 13, 2020

Hi @jskp4 ,

Everything worked well on previous GD version (2.0 I think), but after the upgrade on v 2.5, the artifacts began to appear.

The latest GD version is v2.3.0. There is no v2.5 right now. I think you mean v2.2.5? :)

Thank you @willson-chen, you're right, i meant version 2.2.5

@jskp4
Copy link
Author

jskp4 commented Nov 13, 2020

To clarify, you're complaining about the dark pixels in the corners of the image, and the "antialising" like effect around the pentagon shape? The latter is how gdImageCopyResampled() is supposed to work (more or less anyways), but the former looks like a bug (it seems that out of canvas pixels are assumed to be black, and taken into consideration).

Hi @cmb69, I meant gray lines over a transparent layer, it shouldn't be there. The transparent layer on the input image is white here and yellowed on the output image to make the lines easier to see.

@cmb69
Copy link
Contributor

cmb69 commented Nov 13, 2020

Regardless of whether there is a bug or the behavior is intended, I suggest to use imagescale() with a $mode that suits your needs.

@jskp4
Copy link
Author

jskp4 commented Nov 13, 2020

I tried imagescale() with the IMG_NEAREST_NEIGHBOR parameter and it works, but the image quality is much worse than with imagecopyresampled(), as you can see in the attached image. Such deterioration in exchange for removing artifact on the transparent layer is not appropriate. imagecopyresampled() in the previous version worked well, the quality was good and there were no elements on the transparent layer. Something must have changed in this function, maybe it would be enough to change it back.

comparison

@chrisdeeming
Copy link

We have an increasingly frequent occurrence of this issue as it seems that more and more web hosts start to include GD library 2.2.5 and above in their setups.

Here are some examples.

GD pre-2.2.5

image

GD 2.2.5 imagecopyresampled

image

GD 2.2.5 imagescale

image

You'll notice the second image has strange black artifacts between each image.

The images are produced from four separate images which are cropped and then copied onto a fresh image. The final step is what causes the artifacts which is to use imagecopyresampled to resize the entire composited image down to a specific width/height.

Clearly using imagescale is an option but it produces a noticeably worse image (regardless of mode).

This unfortunately affects version 2.3.0 as well:

image

@pierrejoye
Copy link
Contributor

Long time sorry
@chrisdeeming thanks for the detailed example!
That is most likely due to clamping and rounding issues in the alpha channel max values. Do you have a short script and the original picture please? Separately. I can test then and figure out where that rounding happens.

@pierrejoye
Copy link
Contributor

@chrisdeeming do you have the original image pls?

The 2.3.0, is that usuing copyresampled?

@pierrejoye
Copy link
Contributor

@jskp4 nearest neighbor uses no interpolation at all but the nearest pixel. So the result is expected.

@pierrejoye
Copy link
Contributor

pierrejoye commented Aug 16, 2021

Closing this one.

Please follow up here #93

@pierrejoye
Copy link
Contributor

@jskp4 could you try using the imagescale example please?

I tried using the src image you provided and I cannot see the artifacts.
Mitchell:
scale_15

Lanczos:
scale_23

Other filters as well.

That does not solve imagecopyresampled however that can gives you a better alternative. I also improved slightly the filters earlier, you can try them out today. Results are as accurate than with IM/GM or other references libraries.

@pierrejoye
Copy link
Contributor

@chrisdeeming I would need the source images please. If you have it.

@jskp4
Copy link
Author

jskp4 commented Aug 16, 2021

Thanks for the reply @pierrejoye, I'm a little busy now, I'll try it in a few days and I'll let you know.

@pierrejoye
Copy link
Contributor

pierrejoye commented Aug 16, 2021

diff --git a/src/gd.c b/src/gd.c
index aea8015..ca52e92 100644
--- a/src/gd.c
+++ b/src/gd.c
@@ -3539,20 +3539,14 @@ BGD_DECLARE(void) gdImageCopyResampled (gdImagePtr dst,
                                green /= alpha_sum;
                                blue /= alpha_sum;
                        }
-                       /* Clamping to allow for rounding errors above */
-                       if (red > 255.0) {
-                               red = 255.0;
-                       }
-                       if (green > 255.0) {
-                               green = 255.0;
-                       }
-                       if (blue > 255.0f) {
-                               blue = 255.0;
-                       }
-                       if (alpha > gdAlphaMax) {
-                               alpha = gdAlphaMax;
-                       }
-                       gdImageSetPixel(dst, x, y, gdTrueColorAlpha ((int) red, (int) green, (int) blue, (int) alpha));
+                       /* Round up closest next channel value and clamp to max channel value */
+                       red = red > 255.5 ? 255 : red+0.5;
+                       blue = blue > 255.5 ? 255 : blue+0.5;
+                       green = green > 255.5 ? 255 : green+0.5;
+                       alpha = alpha > gdAlphaMax+0.5 ? 255 : alpha+0.5;
+
+                       gdImageSetPixel(dst, x, y, gdTrueColorAlpha ((int)red, (int)green, (int)blue, (int)alpha));
+
                }
        }
 }

should do it.

I try with image with only transparency (a==127), random values, grids, gradients, etc. With surfaces of the images bigger from 2x2 to 20x20 transparency or partially, rounding to closest channel integer value works again.

@pierrejoye
Copy link
Contributor

pierrejoye commented Aug 16, 2021

@cmb69 some tests fail now for gdImageCopyResampled, however it is expected as the rounding is now correct (it was changed to truncate instead of an actual roundf). That could bring down the channel values to 1.5-2 levels down on each channel.

@chrisdeeming
Copy link

@pierrejoye

This is a simplified reproduction of the code within our application. It should probably work with any set of images in place of 1-4.jpeg; these were just from lorempixel.com.

libgd-661-example.zip

Results:

GD 2.1

image

GD 2.2.5+

image

@cmb69
Copy link
Contributor

cmb69 commented Aug 16, 2021

@chrisdeeming reports a regression in GD 2.2.5, but gdImageCopyResampled() hasn't been modified for more than 5 years, so the issue must be somewhere else.

@pierrejoye
Copy link
Contributor

@chrisdeeming thank you!

Here is the result with the above fix:

sample_resampled

I kept the alpha instead of white so it allows me to see if alpha would be affected as well. There is no non full alpha nor non zero rgb in the padding area. It seems that fixes it as well for your usecase.

@pierrejoye
Copy link
Contributor

pierrejoye commented Aug 16, 2021

@cmb69 distros do custom patches. They must have backported this only now. Also the issues @chrisdeeming are more "visible" due to the JPEG output. The background with alpha is black. JPEG cancels the alpha, so that creates the dark gray points in the padding area.

@jskp4
Copy link
Author

jskp4 commented Aug 21, 2021

@jskp4 could you try using the imagescale example please?

I tried using the src image you provided and I cannot see the artifacts.

Other filters as well.

That does not solve imagecopyresampled however that can gives you a better alternative. I also improved slightly the filters earlier, you can try them out today. Results are as accurate than with IM/GM or other references libraries.

But artifacts only appear when the output image is reduced, for example to 225 x 225 px. I don't know if there was a problem with the artifacts with imagescale(), but there was another problem with deteriorating image quality as can be seen in the post #661 (comment) . I use php-gd and I don't know how I can easily try your solution. Can you try it with the picture below, please? Reduce its size through imagescale() to 320 x 320 px.

glass

pierrejoye added a commit that referenced this issue Aug 24, 2021
@pierrejoye
Copy link
Contributor

#735

@pierrejoye pierrejoye added this to the GD 2.3.3 milestone Sep 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants