phppolygoncrop

Howto crop area outside polygon?


I'd like to make transparent anything outside a predefined polygon in PHP. Let's say, you have the shape of your head then anything else of the image should be binned. Thanks a lot in advance! To make myself clear, here's some code I've been working on:

$dat = dirname(__FILE__)."/foto_".time().".png";
$img = imagecreatefrompng("foto.png");
$points = array(93,36,147,1,255,-5,294,37,332,114,327,189,315,249,303,291,290,327,260,360,205,404,165,407,131,376,86,325,62,236,61,155,66,96,77,57,87,45);
$schwarz = ImageColorAllocate ($img, 0, 0, 0);
imagefilledpolygon($img, $points, count($points)/2, $schwarz);
imagepng($img, $dat);

The original image can be found here and the result here. Everything except the black area should be discarded.


Solution

  • I know it's a little late but I looked all over the web for a solution to this problem and couldn't find an all-in-one solution. Many of the posts I did find involved editing the image on a per-pixel basis which I wasn't too fond of doing. After piecing together multiple samples from multiple sites, here's what I came up with:

    resizeCropPolygonImage()

    function resizeCropPolygonImage($source, $dest = null, 
            $newWidth = null, $newHeight = null, $startX = 0, $startY = 0, 
            $points = array(), $numCoords = 2) {
    
        // Added in $numCoords in case we want to do 3D image processing in the future
        // (currently we do not process anything other than 2D coordinates)
        $points = array(100,115, 124,65, 192,65, 216,115, 192,165, 124,165);
        $numPoints = count($points) / $numCoords;
    
        // If there are not enough points to draw a polygon, then we can't perform any actions
        if ($numPoints < 3) {
            return;
        }
    
        // Get the original image's info
        list($width, $height, $file_type) = getimagesize($source);
    
        /******* Here I am using a custom function to resize the image  *********
         ******* keeping the aspect ratio.                              *********
         ******* You'll have to add in your own re-sizing logic         *********
        // Resize the source (using dummy vars because we don't want our
        // start x & y to be overwritten)
        scaleDimensions($width, $height, $newWidth, $newHeight, $dummyX = null, $dummyY = null);
        For simplicity sake, I'll just set the width and height to the new width and height
        *************************************************************************/
        $width = $newWidth;
        $height = $newHeight;
    
        switch ($file_type) {
        case 1:
            $srcImage = imagecreatefromgif($source);
            if (function_exists(ImageGIF)) {
                $imgType = "gif";
            } else {
                $imgType = "jpeg";
            }
            break;
        case 2:
            $srcImage = imagecreatefromjpeg($source);
            $imgType = "jpeg";
            break;
        case 3:
            $srcImage = imagecreatefrompng($source);
            $imgType = "png";
            break;
        default:
            return;
        }
    
        // Setup the merge image from the source image with scaling
        $mergeImage = ImageCreateTrueColor($width, $height);
        imagecopyresampled($mergeImage, $srcImage, 0, 0, 0, 0, $width, $height, imagesx($srcImage), imagesy($srcImage));
    
        /******** This is probably the part that you're most interested in *******/
        // Create the image we will use for the mask of the polygon shape and
        // fill it with an uncommon color
        $maskPolygon = imagecreatetruecolor($width, $height);
        $borderColor = imagecolorallocate($maskPolygon, 1, 254, 255);
        imagefill($maskPolygon, 0, 0, $borderColor);
    
        // Add the transparent polygon mask
        $transparency = imagecolortransparent($maskPolygon, imagecolorallocate($maskPolygon, 255, 1, 254));
        imagesavealpha($maskPolygon, true);
        imagefilledpolygon($maskPolygon, $points, $numPoints, $transparency);
    
        // Apply the mask
        imagesavealpha($mergeImage, true);
        imagecopymerge($mergeImage, $maskPolygon, 0, 0, 0, 0, $width, $height, 100);
    
        /******* Here I am using a custom function to get the outer     *********
         ******* perimeter of the polygon. I'll add this one in below   ********/
        // Crop down to just the polygon area
        $polygonPerimeter = getPolygonCropCorners($points, $numCoords);
        $polygonX = $polygonPerimeter[0]['min'];
        $polygonY = $polygonPerimeter[1]['min'];
        $polygonWidth = $polygonPerimeter[0]['max'] - $polygonPerimeter[0]['min'];
        $polygonHeight = $polygonPerimeter[1]['max'] - $polygonPerimeter[1]['min'];
    
        // Create the final image
        $destImage = ImageCreateTrueColor($polygonWidth, $polygonHeight);
        imagesavealpha($destImage, true);
        imagealphablending($destImage, true);
        imagecopy($destImage, $mergeImage, 
                0, 0, 
                $polygonX, $polygonY,
                $polygonWidth, $polygonHeight);
    
        // Make the the border transparent (we're assuming there's a 2px buffer on all sides)
        $borderRGB = imagecolorsforindex($destImage, $borderColor);
        $borderTransparency = imagecolorallocatealpha($destImage, $borderRGB['red'],
                $borderRGB['green'], $borderRGB['blue'], 127);
        imagesavealpha($destImage, true);
        imagealphablending($destImage, true);
        imagefill($destImage, 0, 0, $borderTransparency);
    
        if (!$dest) {
            // If no dest was given, then return to browser
            header('Content-Type: image/png');
            imagepng($destImage);
        } else {
            // Output image will always be png
            $dest .= '.png';
    
            // Save to destination
            imagepng($destImage, $dest);
        }
    
        // Destroy remaining images
        imagedestroy($maskPolygon);
        imagedestroy($srcImage);
        imagedestroy($destImage);
    
        // Only return a value if we were given a destination file
        if ($dest) {
            return $dest;
        }
    }
    

    getPolygonCropCorners()

    function getPolygonCropCorners($points, $numCoords) {
        $perimeter = array();
    
        for ( $i = 0; $i < count($points); $i++ ) {
            $axisIndex = $i % $numCoords;
    
            if (count($perimeter) < $axisIndex) {
                $perimeter[] = array();
            }
    
            $min = isset($perimeter[$axisIndex]['min']) ? $perimeter[$axisIndex]['min'] : $points[$i];
            $max = isset($perimeter[$axisIndex]['max']) ? $perimeter[$axisIndex]['max'] : $points[$i];
    
            // Adding an extra pixel of buffer
            $perimeter[$axisIndex]['min'] = min($min, $points[$i] - 2);
            $perimeter[$axisIndex]['max'] = max($max, $points[$i] + 2);
        }
    
        return $perimeter;
    }
    

    Using this function, I can turn this image

    Large giraffes

    into this one

    Small giraffes

    It may not be entirely complete but I think it's definitely a good starting off point.


    EDIT