2

I need to know how it is possible to mask any square image using Imagick. Here is the code I have so far, but the image just doesn't get masked properly:

Get the image

$srcFile = 'filename.png';
$image = new Imagick($srcFile);

Crop the image to square

$d = $image->getImageGeometry();
$src_width = $d['width']; 
$src_height = $d['height'];
$thumbSize = min(max($src_width, $src_height), abs($thumbSize));        
if ($src_width < $src_height) {
    $image->cropImage($src_width, $src_width, 0, (($src_height - $src_width)/2));
} else {
    $image->cropImage($src_height, $src_height, (($src_width - $src_height)/2), 0);
}

Resize the image

$image->thumbnailImage($thumbSize, $thumbSize, 1);

Crop / Mask the image with the bezier shape

$image->compositeImage(bezier($thumbSize, $thumbSize), Imagick::COMPOSITE_COPYOPACITY, 0, 0);

The bezier function creates a shape looking like this:

bezier mask shape

function bezier($width, $height) {
    $fillColor = "#000";
    $draw = new ImagickDraw();

Fill the unmasked part with black color

    $fillColor = new ImagickPixel($fillColor);
    $draw->setFillColor($fillColor);
    $smoothPointsSet = [
        [
            ['x' => 0.0 * $width, 'y' => 0.5 * $width],
            ['x' => 0.0 * $width, 'y' => 0.905 * $width],
            ['x' => 0.095 * $width, 'y' => 1.0 * $width],
            ['x' => 0.5 * $width, 'y' => 1.0 * $width]
        ], [
            ['x' => 0.5 * $width, 'y' => 1.0 * $width],
            ['x' => 0.905 * $width, 'y' => 1.0 * $width],
            ['x' => 1.0 * $width, 'y' => 0.905 * $width],
            ['x' => 1.0 * $width, 'y' => 0.5 * $width]
        ], [
            ['x' => 1.0 * $width, 'y' => 0.5 * $width],
            ['x' => 1.0 * $width, 'y' => 0.095 * $width],
            ['x' => 0.905 * $width, 'y' => 0.0 * $width],
            ['x' => 0.5 * $width, 'y' => 0.0 * $width]
        ], [
            ['x' => 0.5 * $width, 'y' => 0.0 * $width],
            ['x' => 0.095 * $width, 'y' => 0.0 * $width],
            ['x' => 0.0 * $width, 'y' => 0.095 * $width],
            ['x' => 0.0 * $width, 'y' => 0.5 * $width]
        ]
    ];

    foreach ($smoothPointsSet as $points) {
        $draw->bezier($points);
    }

The bezier points don't fill a square in the middle so fill it manually

    $points = [
        ['x' => $width * 0.5, 'y' => 0.0],
        ['x' => 0.0, 'y' => $height * 0.5],
        ['x' => $width * 0.5, 'y' => $height],
        ['x' => $width, 'y' => $height * 0.5]
    ];
    $draw->polygon($points);

Copy the drawer image to a new transparent Imagick image

    $imagick = new Imagick();
    $imagick->newImage($width, $width, "none");

From here on I have experimented with various properties. I didn't get any satisfying result - the image almost always doesn't get masked.

    #$imagick->setImageAlphaChannel(Imagick::ALPHACHANNEL_SHAPE);
    #$imagick->setImageFormat("png");

    $imagick->drawImage($draw);

    #$imagick->setImageMatte(false);

    return $imagick;
}

I would be very glad if I could know where the problem lies and how to fix it. I found various answers on SO that didn't work for me:

Use $dude->setImageMatte(1); Using a transparent PNG as a clip mask

Use $base->compositeImage($mask, Imagick::COMPOSITE_DSTIN, 0, 0, Imagick::CHANNEL_ALPHA); How to use Imagick to merge and mask images?

Unfortunately I couldn't resolve the problem.

Community
  • 1
  • 1

1 Answers1

1

The problem was that images were stored as JPEG so the transparent part became black. This is the code I used:

$mask = bezier($thumbSize, $thumbSize);

// Copy opacity mask
if ($image->getImageMatte()) {
    $image->compositeImage($mask, Imagick::COMPOSITE_DSTIN, 0, 0, Imagick::CHANNEL_ALPHA);
} else {
    $image->compositeImage($mask, Imagick::COMPOSITE_COPYOPACITY, 0, 0);
}

$image->setImageBackgroundColor(new ImagickPixel('white'));
#$image->setImageAlphaChannel(Imagick::ALPHACHANNEL_DEACTIVATE);
$image = $image->flattenImages();

In function bezier:

$imagick = new Imagick();
$imagick->newImage($width, $width, "none");
$imagick->setImageBackgroundColor(new ImagickPixel('transparent'));
#$imagick->setImageAlphaChannel(Imagick::ALPHACHANNEL_SHAPE);
$imagick->setImageFormat("png");

//Render the draw commands in the ImagickDraw object 
//into the image.
$imagick->drawImage($draw);
$imagick->negateImage(FALSE);


return $imagick;
  • It's a worthwhile idea to post to the imagemagick mailing list, or hit up their IRC channel, or whatever their method of direct contact is, to let them know that image magick apparently does not warn users when they save images in a format that does not support the features used. Saving an image with an alpha channel in the bitmap as JPEG should have at the least given you a console warning, which would have allowed you to immediately spot and fix the problem. – Mike 'Pomax' Kamermans Dec 27 '16 at 18:39
  • "to let them know that image magick apparently does not warn users when they save images in a format" That is probably by design. The philosopy behind the C api that ImageMagick exposes is to do what the user tells it to do, on the assumption that they know what they're doing. Saving an image with transparency as Jpeg is a normal thing to do (some of the time), so it just does it, rather than doubting what the user intended. – Danack Dec 27 '16 at 23:07