14

I need a script which makes rounded transparent corners on supplied image. I've found one and it works good except the one thing: the applied corners do not look smooth. The imageantialias() throws Fatal Error since PHP is running on Debian and re-compiling it is not an option.

The trick I've found to make those corners look smooth is resizing the image with imagecopyresampled() as the following:

  1. prepare the image;
  2. imagecopyresample it to 10x size;
  3. draw corners with a special colour;
  4. make that color transparent;
  5. decrease the image to it's original size

But here comes the problem: the corners of the result image (after step 5) are smooth, but not transparent. When sending to output the image after step 4 (i.e. before decreasing it's size) – everything's as it should be.

Here's the part of the code responsible for making corners rounded:

    //    $dest = image resource


        $q=10;
        // making everything 10x bigger
        $new_width=$width*$q;
        $new_height=$height*$q;
        $radius=$radius*$q;

        $magnified=imagecreatetruecolor($new_width, $new_height);
        imagecopyresampled($magnified, $dest, 0,0, 0,0, $new_width,$new_height, ($new_width/$q),($new_height/$q));

        // picking the unique colour
        $found = false;
        while($found == false) {
            $r = rand(0, 255);
            $g = rand(0, 255);
            $b = rand(0, 255);
            if(imagecolorexact($magnified, $r, $g, $b) != (-1)) {
                $found = true;
            }
        }
        $colorcode = imagecolorallocate($magnified, $r, $g, $b);

            // drawing corners
            imagearc($magnified, $radius-1, $radius-1, $radius*2, $radius*2, 180, 270, $colorcode);
            imagefilltoborder($magnified, 0, 0, $colorcode, $colorcode);
            imagearc($magnified, $new_width-$radius, $radius-1, $radius*2, $radius*2, 270, 0, $colorcode);
            imagefilltoborder($magnified, $new_width-1, 0, $colorcode, $colorcode);
            imagearc($magnified, $radius-1, $new_height-$radius, $radius*2, $radius*2, 90, 180, $colorcode);
            imagefilltoborder($magnified, 0, $new_height-1, $colorcode, $colorcode);
            imagearc($magnified, $new_width-$radius, $new_height-$radius, $radius*2, $radius*2, 0, 90, $colorcode);
            imagefilltoborder($magnified, $new_width-1, $new_height-1, $colorcode, $colorcode);

        // making the unique colour transparent
        imagecolortransparent($magnified, $colorcode);

        // scaling down the enlarged image to it's original size
        // expecting corners to remain transparent
        imagecopyresampled($dest, $magnified, 0,0, 0,0, ($new_width/$q),($new_height/$q), $new_width,$new_height);
        // but they're not
        // sending $magnified to output for testing purposes
        $dest=$magnified;

    //    outputting $dest as image/png

So as you can see, the problem occurs when enlarged image is being imagecopyresampled to it's original size. The transparent corners get filled with the $colorcode colour. I've been playing with imagesavealpha() and imagealphablending() as advised, but no result.

Please help me to make this work.

P.S. This may be useful: when uploaded the large PNG to imgur.com it had it converted to JPG and as you can see all corners got filled with that very restored $colorcode.

P.S. Hope I won't get banned for overusing the word "enlargement" :)

Community
  • 1
  • 1
ᴍᴇʜᴏᴠ
  • 4,804
  • 4
  • 44
  • 57
  • This line causes fatal error if you set large W/H (don't forget that you're multiplying original W/H with 10)... `$magnified=imagecreatetruecolor($new_width, $new_height);`. You can hit memory limits (or limits you have set in php.ini). – Wh1T3h4Ck5 Apr 23 '11 at 21:14
  • @Wh1T3h4Ck5 I have a predefined array of image types I may need that contains information about width, height and corners. I kept getting `imageantialias()` fatal error before tricks with image size, so I'm sure that's the Debian issue. – ᴍᴇʜᴏᴠ Apr 24 '11 at 05:19

3 Answers3

24

After couple of hours of testing and kicking my head against the wall, I think I've found solution. Problem was about allocating transparent color using imagecolorallocate(). I did not get it at first sight. It was totally wrong approach. However, imagecolorallocatealpha() has helped me alot.

Also, alpha blending must be off before saving alpha channel on working layer. However, it must be done right after creating blank true-color image, like

  $im = imagecreatetruecolor($w, $h);
  $alphacolor = imagecolorallocatealpha($img, $r, $g, $b, 127);
  imagealphablending($im, false);
  imagesavealpha($im, true);

This code is a key for getting smooth corners in transparent area after resize-down.

After all, I've wrote this function

  function imageCreateCorners($sourceImageFile, $radius) {
  # function body
  }

I've tested it with couple of images and it returned image with smooth corners for every bg color.

  imagepng(imageCreateCorners('jan_vesely_and_james_gist.jpg', 9), 'test.png');

Output

Original image

enter image description here

IN BROWSER (Same png file 'test.png')

enter image description here

It finally returns fully transparent alpha channel so you can use that image on every background you want.

I almost forgot to post function code :)

function imageCreateCorners($sourceImageFile, $radius)

  function imageCreateCorners($sourceImageFile, $radius) {
    # test source image
    if (file_exists($sourceImageFile)) {
      $res = is_array($info = getimagesize($sourceImageFile));
      } 
    else $res = false;

    # open image
    if ($res) {
      $w = $info[0];
      $h = $info[1];
      switch ($info['mime']) {
        case 'image/jpeg': $src = imagecreatefromjpeg($sourceImageFile);
          break;
        case 'image/gif': $src = imagecreatefromgif($sourceImageFile);
          break;
        case 'image/png': $src = imagecreatefrompng($sourceImageFile);
          break;
        default: 
          $res = false;
        }
      }

    # create corners
    if ($res) {

      $q = 10; # change this if you want
      $radius *= $q;

      # find unique color
      do {
        $r = rand(0, 255);
        $g = rand(0, 255);
        $b = rand(0, 255);
        }
      while (imagecolorexact($src, $r, $g, $b) < 0);

      $nw = $w*$q;
      $nh = $h*$q;

      $img = imagecreatetruecolor($nw, $nh);
      $alphacolor = imagecolorallocatealpha($img, $r, $g, $b, 127);
      imagealphablending($img, false);
      imagesavealpha($img, true);
      imagefilledrectangle($img, 0, 0, $nw, $nh, $alphacolor);

      imagefill($img, 0, 0, $alphacolor);
      imagecopyresampled($img, $src, 0, 0, 0, 0, $nw, $nh, $w, $h);

      imagearc($img, $radius-1, $radius-1, $radius*2, $radius*2, 180, 270, $alphacolor);
      imagefilltoborder($img, 0, 0, $alphacolor, $alphacolor);
      imagearc($img, $nw-$radius, $radius-1, $radius*2, $radius*2, 270, 0, $alphacolor);
      imagefilltoborder($img, $nw-1, 0, $alphacolor, $alphacolor);
      imagearc($img, $radius-1, $nh-$radius, $radius*2, $radius*2, 90, 180, $alphacolor);
      imagefilltoborder($img, 0, $nh-1, $alphacolor, $alphacolor);
      imagearc($img, $nw-$radius, $nh-$radius, $radius*2, $radius*2, 0, 90, $alphacolor);
      imagefilltoborder($img, $nw-1, $nh-1, $alphacolor, $alphacolor);
      imagealphablending($img, true);
      imagecolortransparent($img, $alphacolor);

      # resize image down
      $dest = imagecreatetruecolor($w, $h);
      imagealphablending($dest, false);
      imagesavealpha($dest, true);
      imagefilledrectangle($dest, 0, 0, $w, $h, $alphacolor);
      imagecopyresampled($dest, $img, 0, 0, 0, 0, $w, $h, $nw, $nh);

      # output image
      $res = $dest;
      imagedestroy($src);
      imagedestroy($img);
      }

    return $res;
    }

Function returns GD object or false.


Function works with solid JPEG, GIF and PNG images. Also, it works great with transparent PNGs and GIFs.

Wh1T3h4Ck5
  • 8,399
  • 9
  • 59
  • 79
  • @Wh1T3h4Ck5 thank you for your answer. 1). I have to admit that fixing the background color is not an option... I mean images may be used over div's filled with gradient background, so I still need transparent corners. 3). Yes, I'm scaling down `$magnified` to `$dest` and exporting the latter. I've tried `imagecolortransparent($dest, $colorcode);` but the point is that I need to remove `$colorcode` **before** scaling the image down in order to keep corners clear. 4). I used that for _test_ purposes – just to export `$magnified` and to see what it looks like. – ᴍᴇʜᴏᴠ Apr 24 '11 at 05:29
  • 5). Yes, I have `header();` at export stage with content-type and cache control settings. For rounded images I send image/png headers. I can use `$q=5;` instead of 10 in order to save memory, that's fine, but I need this script to work as expected first. 6). Are corners in your example transparent or with fixed background? I can see black "noise" on the guy's hand, did you remove `$colorcode` after scaling down? – ᴍᴇʜᴏᴠ Apr 24 '11 at 05:38
  • @Wh1T3h4Ck5 yeah that's because [`imagecolortransparent($dest, $colorcode);` removes `$colorcode` from the already scaled down image](http://i.imgur.com/qwLLF.png), and keeps semi-transparent parts of it. I need to make `$colorcode` transparent when the image is still big and rounded borders are sharp. – ᴍᴇʜᴏᴠ Apr 24 '11 at 07:03
  • 2
    @The - Finally it works :) Cheers mate. To be honest, that was real challenge for me. – Wh1T3h4Ck5 Apr 24 '11 at 14:37
  • 3
    @Wh1T3h4Ck5 I'm a web-dev since early 2006, and this is the first time I got such a dedicated, helpful and complete answer on the internet ever. Thank you very much! It works now. – ᴍᴇʜᴏᴠ Apr 24 '11 at 15:47
  • Very expensive solution for memory. Need copy 4 corners to one single image (of course with some gap). Then make increased version of this image, place round corners here, reduce and copy corners back. It is use less memory. Much less. For example if you have image 1024*1024 and want round to 10 px. Original code create temp image of 400 MB. Reduced version use only ~230 KB. Need more code but it worth for it. – Enyby Oct 03 '18 at 11:45
  • @Enyby You're 100% right. Doing this on large images is memory overkill. Point of this code was to show that getting smooth rounded corners (also transparent) is possible with vanilla GD in PHP. Using corner areas as separate images while processing them is probably the best way if we go with this approach. Another way is to use some math algorithm on pixel level to do rounding and smoothing (average color + alpha) which is probably even faster (have not tested that yet - `sin` function to find inner and outer pixels and math to do smooth against bounding pixels) – Wh1T3h4Ck5 Aug 13 '22 at 16:44
7

Improved code from @Wh1T3h4Ck5. This function take image and make round corners for it without waste memory. Also it must work faster on huge images. For example 1024*1024 image need 400MB temp image in original code. Now only 230 KB. (if you use radius 10 px).

Function take GD image object, radius in px and return GD image object. Currently it is same as original GD image object.

Function assume size of your image is bigger from radius. Exactly it need to be greater (or equal) from ($radius + 2)*2 on any side.

Also function set imagealphablending to true for this image. If you need save into png, do not forgot set imagesavealpha to true.

function roundCorners($source, $radius) {
    $ws = imagesx($source);
    $hs = imagesy($source);

    $corner = $radius + 2;
    $s = $corner*2;

    $src = imagecreatetruecolor($s, $s);
    imagecopy($src, $source, 0, 0, 0, 0, $corner, $corner);
    imagecopy($src, $source, $corner, 0, $ws - $corner, 0, $corner, $corner);
    imagecopy($src, $source, $corner, $corner, $ws - $corner, $hs - $corner, $corner, $corner);
    imagecopy($src, $source, 0, $corner, 0, $hs - $corner, $corner, $corner);

    $q = 8; # change this if you want
    $radius *= $q;

    # find unique color
    do {
        $r = rand(0, 255);
        $g = rand(0, 255);
        $b = rand(0, 255);
    } while (imagecolorexact($src, $r, $g, $b) < 0);

    $ns = $s * $q;

    $img = imagecreatetruecolor($ns, $ns);
    $alphacolor = imagecolorallocatealpha($img, $r, $g, $b, 127);
    imagealphablending($img, false);
    imagefilledrectangle($img, 0, 0, $ns, $ns, $alphacolor);

    imagefill($img, 0, 0, $alphacolor);
    imagecopyresampled($img, $src, 0, 0, 0, 0, $ns, $ns, $s, $s);
    imagedestroy($src);

    imagearc($img, $radius - 1, $radius - 1, $radius * 2, $radius * 2, 180, 270, $alphacolor);
    imagefilltoborder($img, 0, 0, $alphacolor, $alphacolor);
    imagearc($img, $ns - $radius, $radius - 1, $radius * 2, $radius * 2, 270, 0, $alphacolor);
    imagefilltoborder($img, $ns - 1, 0, $alphacolor, $alphacolor);
    imagearc($img, $radius - 1, $ns - $radius, $radius * 2, $radius * 2, 90, 180, $alphacolor);
    imagefilltoborder($img, 0, $ns - 1, $alphacolor, $alphacolor);
    imagearc($img, $ns - $radius, $ns - $radius, $radius * 2, $radius * 2, 0, 90, $alphacolor);
    imagefilltoborder($img, $ns - 1, $ns - 1, $alphacolor, $alphacolor);
    imagealphablending($img, true);
    imagecolortransparent($img, $alphacolor);

    # resize image down
    $dest = imagecreatetruecolor($s, $s);
    imagealphablending($dest, false);
    imagefilledrectangle($dest, 0, 0, $s, $s, $alphacolor);
    imagecopyresampled($dest, $img, 0, 0, 0, 0, $s, $s, $ns, $ns);
    imagedestroy($img);

    # output image
    imagealphablending($source, false);
    imagecopy($source, $dest, 0, 0, 0, 0, $corner, $corner);
    imagecopy($source, $dest, $ws - $corner, 0, $corner, 0, $corner, $corner);
    imagecopy($source, $dest, $ws - $corner, $hs - $corner, $corner, $corner, $corner, $corner);
    imagecopy($source, $dest, 0, $hs - $corner, 0, $corner, $corner, $corner);
    imagealphablending($source, true);
    imagedestroy($dest);

    return $source;
}
Enyby
  • 4,162
  • 2
  • 33
  • 42
  • Haven't tested as I no longer have access to that code (it's been 7 years after all), but upvoting for the effort. Thank you! – ᴍᴇʜᴏᴠ Oct 04 '18 at 09:25
  • [ATB - Seven Years](https://www.youtube.com/watch?v=qSHZ5WflzKI) ¯\_(ツ)_/¯ – ᴍᴇʜᴏᴠ Oct 04 '18 at 09:29
  • Hi @Enyby , i know it late but i need your code. I got those errors with your code: `Warning: imagecopy() expects parameter 2 to be resource, string given in` on line 10, 11, 12, 13. And `imagealphablending() expects parameter 1 to be resource, string given in` on line 55. – Manh Apr 23 '23 at 08:41
  • Ensure you pass to function valid image resource, not string. – Enyby Apr 25 '23 at 05:26
2
...

/* rounded corner */
$radius = 20;

// find ghost color
do
{
  $r = rand(0, 255);
  $g = rand(0, 255);
  $b = rand(0, 255);
} while (imagecolorexact($img_resource, $r, $g, $b) < 0);
$ghost_color = imagecolorallocate($img_resource, $r, $g, $b);

imagearc($img_resource, $radius-1, $radius-1, $radius*2, $radius*2, 180, 270, $ghost_color);
imagefilltoborder($img_resource, 0, 0, $ghost_color, $ghost_color);
imagearc($img_resource, $img_width-$radius, $radius-1, $radius*2, $radius*2, 270, 0, $ghost_color);
imagefilltoborder($img_resource, $img_width-1, 0, $ghost_color, $ghost_color);
imagearc($img_resource, $radius-1, $img_height-$radius, $radius*2, $radius*2, 90, 180, $ghost_color);
imagefilltoborder($img_resource, 0, $img_height-1, $ghost_color, $ghost_color);
imagearc($img_resource, $img_width-$radius, $img_height-$radius, $radius*2, $radius*2, 0, 90, $ghost_color);
imagefilltoborder($img_resource, $img_width-1, $img_height-1, $ghost_color, $ghost_color);

imagecolortransparent($img_resource, $ghost_color);

...

try this one ...

jiappo
  • 61
  • 4