phpgdphp-gd

Draw polygon extending beyond image-border


Note:


I want to create custom graphic by putting together a background image with a polygon and some texts.

Input background images are of varied dimensions and aspect-ratios, but the final graphic has to be of a fixed (2:1) aspect ratio (it also has to be of pre-defined dimensions, but resizing of image is trivial, so correct aspect ratio is my only target).


Presently I'm cropping-to-fit my input image to target aspect ratio (2:1) by performing max-center area cropping using imagecrop function. Thereafter I draw red polygon on it as shown below (ignore the texts drawn on red band) using imagefilledpolygon method [cropping screenshot below is for demonstration purpose only, it is actually being done programmatically via imagecrop function]

present cropping + overlaying mechanism

Here's my function that draws the overlay (this function is called after cropping of image to 2:1 aspect ratio is done)

/**
 * adds overlay (colored band) on the image
 * for better output, overlay must be added before resizing
 */
public function withOverlay(): NotifAdsCreativeGenerator {
    // Prepare custom red-color Hex to RGB https://stackoverflow.com/a/15202130/3679900
    list($r, $g, $b) = sscanf(self::OVERLAY_COLOR, "#%02x%02x%02x");
    $custom_red_color = imagecolorallocate($this->getCrrImage(), $r, $g, $b);
    // prepare coordinates for polygon
    $coords = [
        [0, 0],
        [(int) ($this->getCrrWidth() * self::OVERLAY_BEGIN_X_RATIO), 0],
        [(int) ($this->getCrrWidth() * self::OVERLAY_END_X_RATIO), $this->getCrrHeight()],
        [0, $this->getCrrHeight()]
    ];
    $flattened_coords = array_merge(...$coords);
    // draw polygon on image
    imagefilledpolygon($this->getCrrImage(), $flattened_coords, count($flattened_coords) / 2, $custom_red_color);
    return $this;
}

But what I want is to crop the image to ~ 1.28:1 aspect ratio (the approx ratio of right part of graphic without the red band) and then draw the polygon (extending) outside the image so as to obtain the final graphic in the same same 2:1 aspect ratio as shown below

desired cropping + overlay mechanism


I'm able to crop image to my desired aspect ratio (1.28:1) but I can't figure out a way to draw the polygon outside the image bounds (effectively expanding the image in the process). Is there a way to do this using PHP-GD library?


Solution

  • It was just a lack of understanding (about working of PHP-GD, available methods) on my part, but the solution is pretty simple


    /**
     * adds overlay (colored band) on the image
     * for better output, overlay must be added before resizing
     *
     * This method tries to preserve maximum center region of input image by performing minCenterCrop(.) on it
     * before drawing an overlay that extends beyond left border of the cropped image
     *
     * (since this method incorporates call to 'withMinCenterCrop', calling that method before this is  not required
     * (and is redundant). For benefits of this method over 'withOverlay', read docstring comment of
     * 'withMinCenterCrop' method
     * @return NotifAdsCreativeGenerator
     */
    public function withExtendedOverlay(): NotifAdsCreativeGenerator {
        // perform min center crop to the 1.28:1 aspect ratio (preserve max central portion of image)
        $this->withMinCenterCrop();
    
        // this $required_canvas_aspect_ratio calculates to 2.0 (2:1 aspect ratio)
        // calculate aspect ratio & dimensions of empty 'canvas' image
        // since canvas is wider than min center-cropped image (as space on the left will be occupied by red overlay)
        // therefore it's height is matched with cropped image and width is calculated
        $required_canvas_aspect_ratio = self::IMAGE_WIDTH / self::IMAGE_HEIGHT;
        // height of cropped image
        $canvas_height = $this->getCrrHeight();
        $canvas_width = $required_canvas_aspect_ratio * $canvas_height;
        // create a new 'canvas' (empty image) on which we will
        // 1. draw the existing input 'min-cropped' image on the right
        // 2. draw the red overlay on the left
        $canvas_image = imagecreatetruecolor($canvas_width, $canvas_height);
    
        // copy contents of image on right side of canvas
        imagecopy(
            $canvas_image,
            // cropped image
            $this->getCrrImage(),
            self::OVERLAY_BEGIN_X_RATIO * $canvas_width,
            0,
            0,
            0,
            // dimensions of cropped image
            $this->getCrrWidth(),
            $this->getCrrHeight()
        );
    
        // draw red band overlay on left side of canvas
        $this->crr_image = $canvas_image;
        return $this->withOverlay();
    }