Woodgnome
Woodgnome

Reputation: 2391

imagecopyresampled() introduces artifacts in transparent PNG

I'm adding a transparent PNG watermark to an image. The watermark is high resolution so before overlaying it in the original image I resize it. This seems to introduce some artifacts in the watermark that I haven't found a way to avoid.

Original image: enter image description here

Resized image (look at the horizontal line-like "dirt" between letters"): enter image description here

Zoom of resized image (not transparent) to clarify what I mean by the "dirt" between the letters. The area between "s" and "t" I've cleaned with the select tool and delete (open in new tab to see full size where it's more clear): enter image description here

Here's the code I'm using:

function resizeImage($image_filename, $out_filename, $width, $height){
  // Get image info
  $image_info = @getimagesize($image_filename);
  if ($image_info == false) return false;
  $org_width = $image_info[0];
  $org_height = $image_info[1];
  $image_type = $image_info[2];

  // Open image
  if ($image_type == IMAGETYPE_JPEG) $org_image = @imagecreatefromjpeg($image_filename);
  else if ($image_type == IMAGETYPE_GIF) $org_image = @imagecreatefromgif($image_filename);
  else if ($image_type == IMAGETYPE_PNG) $org_image = @imagecreatefrompng($image_filename);
  else return false;

  // Open stream for resized image
  $resized_image = @imagecreatetruecolor($width, $height);
  if ($resized_image == false) return false;

  // Handle transparency in PNGs
  if ($image_type == IMAGETYPE_PNG){
    $transparent = imagecolorallocatealpha($resized_image, 255, 255, 255, 127);
    imagefilledrectangle($resized_image, 0, 0, $width, $height, $transparent);
    imagealphablending($resized_image, false);
    imagesavealpha($resized_image, true);
  }

  // Resize
  $resize_result = @imagecopyresampled($resized_image, $org_image, 0, 0, 0, 0, $width, $height, $org_width, $org_height);

  // Free original image
  @imagedestroy($org_image);

  // Save
  if ($image_type == IMAGETYPE_JPEG) $save_result = imagejpeg($resized_image, $out_filename, 90); // 90 = compression
  else if ($image_type == IMAGETYPE_GIF) $save_result = imagegif($resized_image, $out_filename);
  else if ($image_type == IMAGETYPE_PNG) $save_result =  imagepng($resized_image, $out_filename, 0);

  // Free resized image
  if ($resize_result) @imagedestroy($resized_image);

  return ($resize_result && $save_result);
}

Any idea on what is causing the artifacts?

Upvotes: 0

Views: 2338

Answers (4)

Kevin Vojičič
Kevin Vojičič

Reputation: 1

I ran into similar issue recently. What worked for me was after resampleing image was to re-setting alpha to pixles that were broken. I got the idea from zuegs on https://www.php.net/manual/en/function.imagecopyresampled.php . It is not full solution to a problem, just an idea that could help.

$transindex = imagecolorallocatealpha($dst_img, 0, 0, 0, 127);
imagefill($dst_img, 0, 0, $transindex);
imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $new_w, $new_h, $origw, $origh);
if ($transindex >= 0) {
    imagecolortransparent($dst_img, $transindex);
    for ($y = 0; $y < $new_h; ++$y) {
        for ($x = 0; $x < $new_w; ++$x) {
            if (((imagecolorat($dst_img, $x, $y) >> 24) & 0x7F) >= 100) {
                imagesetpixel($dst_img, $x, $y, $transindex);
            }
        }
    }
}

Upvotes: 0

Woodgnome
Woodgnome

Reputation: 2391

As I wrote in my comment in response to Søren Løvborgs answer it looks like it's simply [an issue with GD/imagecopyresampled()][1] that can not be easily avoided. GD Quality Issue with Transparent PNGs has the same issue.

It's possible to use Søren Løvborgs suggested workaround, just keep in mind that it may introduce noticable quality reduction due to resizing the original image twice.

I suggest using a photo editor to resize the watermark before overlaying instead. This isn't as flexible, but it will keep the image quality and not add any noise.

Upvotes: 3

ozys5000
ozys5000

Reputation: 53

This does not seem to occur on black backgrounds, so another hack would be to invert the image before resizing and do it again after

imagefilter($org_image, IMG_FILTER_NEGATE);
imagecopyresampled($resized_image, $org_image, 0, 0, 0, 0, $width, $height, $org_width, $org_height);
imagefilter($resized_image, IMG_FILTER_NEGATE);

Upvotes: 3

S&#248;ren L&#248;vborg
S&#248;ren L&#248;vborg

Reputation: 8751

Okay, first of all, here's your "dirty" test image with the RGB channels (above) and alpha channel (below) separated:

Dirty image split into channels

I've replaced pure white (#FFFFFF)/100% transparent with blue to highlight the speckles.

As you can see, GD adds speckles to both the RGB channels and the alpha channel.

Without having examined the GD code, I'm guessing that it's caused by a rounding error, causing some pixels to be off by 1 or 2.

I notice that you have an embedded color profile in the source PNG. Try removing that; the artefacts may be caused by GD doing color correction. (In Photoshop, you should use Save for Web.)

Otherwise, I can't suggest a way to solve this without fixing the GD code, however, I can suggest a dirty workaround, which might work:

Instead of resizing the watermark to the target image size, then overlaying it on the target image, you can resize the target image to the watermark size, overlay the watermark, drop the alpha channel and then resize to the old target image size. This way you avoid resizing any image while it has an alpha channel, which may avoid visible artifacts.

Upvotes: 1

Related Questions