libgd / libgd

GD Graphics Library
http://libgd.org
Other
903 stars 271 forks source link

Resize Artifact #99

Closed imazen-bot closed 3 years ago

imazen-bot commented 9 years ago

Hi Pierre,

Following your recommandation on the mailing list I open a new issue : If I resize a picture to a size which is not a multiple of the original size I have artifacts on the white background.

I just had a test on php5.53 and the bug is still here :

Here is the code I used to test (test.jpg is a 1000x1000 jpeg)

#!php
<?php
$filename = 'test.jpg';
$percent = $_GET["scale"] ?  $_GET["scale"] : 1;

// Content type
header('Content-Type: image/jpeg');

// Get new dimensions
list($width, $height) = getimagesize($filename);
$new_width = $width * $percent;
$new_height = $height * $percent;

// Resample
$image_p = imagecreatetruecolor($new_width, $new_height);
$image = imagecreatefromjpeg($filename);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);

// Output
imagejpeg($image_p, null, 100);

You can find the resulting jpg in attachment (If you can't see the artifact just add contrast in photoshop)


imazen-bot commented 9 years ago

A better testcase:

#!php

<?php

// Request input parameters
$percent = isset($_GET["scale"]) ?  floatval($_GET["scale"]) : 1;

// Build original image
$width = $height = 1000;
$image = imagecreatetruecolor($width, $height);
$white = imagecolorallocate($image, 255, 255, 255);
imagefill($image, 0, 0, $white);

// Get new dimensions
$new_width = $width * $percent;
$new_height = $height * $percent;

// Resample
$image_p = imagecreatetruecolor($new_width, $new_height);
imagecopyresampled($image_p, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height);

// Output
header('Content-Type: image/png');
imagepng($image_p, null);

Originally posted by ChALkeR (ChALkeR) on Jan 02 2014 via Bitbucket

sotebuy commented 9 years ago

I can confirm the same bug on PHP 5.6.10 is somebody addressing the issue?

kaipiyann commented 8 years ago

Problem can be solved using imagescale : $image_p = imagescale($image, $new_width, $new_height, IMG_NEAREST_NEIGHBOUR);

Just tested and it works.

cmb69 commented 8 years ago

You can find the resulting jpg in attachment (If you can't see the artifact just add contrast in photoshop)

If I maximally decrease brightness and increase contrast, I can see artifacts, but these may be usual JPEG compression artifacts (note that JPEG quality 100 doesn't mean lossless compression). I suggest that you check with a PNG image (which is lossless).

I can confirm the same bug on PHP 5.6.10 is somebody addressing the issue?

I would need a good sample (original and resampled plus code) to reproduce the issue.

$image_p = imagescale($image, $new_width, $new_height, IMG_NEAREST_NEIGHBOUR);

Hm, IMG_NEAREST_NEIGHBOUR is not everybody's favorite. If I'm not mistaken, that is the same as imagecopyresized().

pierrejoye commented 8 years ago

Also the artifacts may be gone too with libgd from.here. I change the sampling for q>=90 so almost no artefacts should appear.

pierrejoye commented 8 years ago

2.2 or master branches. This charge will be in 2.2.2 or next 5.6/7.0/7.1

pierrejoye commented 8 years ago

@cmb69 for the record here, imagecopyresanpled uses a kind of bicubic interpolation, not nearest.

cmb69 commented 8 years ago

imagecopyresanpled uses a kind of bicubic interpolation, not nearest.

Yes, I know. :) Therefore I'm preferring imagecopyresampled() over imagecopyresized() (even though the latter is a bit faster).

cmb69 commented 8 years ago

Well, actually this is the same issue that has been reported as https://bugs.php.net/bug.php?id=53580, including a rather terse analysis. The culprit appears that we cast float/double to int.

Reproduce script:

<?php

$src = imagecreatetruecolor(4, 4);
$white = imagecolorallocate($src, 255, 255, 255);
imagefilledrectangle($src, 0,0, 3,3, $white);

for ($i = 0; $i < 4; $i++) {
    for ($j = 0; $j < 4; $j++) {
        assertColorAt($src, $i, $j, $white);
    }
}

$dst = imagecreatetruecolor(7, 7);

imagecopyresampled($dst, $src, 0,0, 0,0, 7,7, 4,4);

for ($i = 0; $i < 7; $i++) {
    for ($j = 0; $j < 7; $j++) {
        assertColorAt($dst, $i, $j, $white);
    }
}

function assertColorAt($im, $x, $y, $exp_color) {
    $act_color = imagecolorat($im, $x, $y);
    if ($act_color !== $exp_color) {
        printf("color of %2d,%2d is %x, expected %x\n", $x, $y, $act_color, $exp_color);
    }
}

The result is likely system dependent, but for me it's (current PHP master with bundled GD):

color of  1, 5 is fefefe, expected ffffff
color of  5, 1 is fefefe, expected ffffff
color of  5, 5 is fefefe, expected ffffff

And (current PHP master with current GD master):

color of  0, 3 is fefefe, expected ffffff
color of  3, 0 is fefefe, expected ffffff

gdImageScale() has similar issues depending on the algorithm. For instance, GD_BILINEAR_FIXED works as expected, while GD_BICUBIC and GD_BICUBIC_FIXED even produce several fcfcfc pixels.

pierrejoye commented 3 years ago

I cleanup/refactor a bit the new scaling functions and added an example here.

It fixes an issue about the support (radius) used by each filter and ensure correct filters being used.

I would GD_LANCZOS3 for downscale and mitchell for upscale, to begin width.

@cmb69 could you give a try?

pierrejoye commented 3 years ago

gdImageCopyResampled is back to working state from a rounding point of views. Artifacts in such situations are gone. see #661