Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiply filter with PHP's GD library

I've tried experimenting with the GD library to simulate Photoshop's muliply effect, but I haven't found a working solution yet.

According to Wikipedia, the multiply blend mode:

[...] multiplies the numbers for each pixel of the top layer with the corresponding pixel for the bottom layer. The result is a darker picture.

Does anyone know of a way to achieve this using PHP? Any help would be much appreciated.

like image 773
heintore Avatar asked Mar 21 '13 17:03

heintore


People also ask

What is the use of GD library in PHP?

Graphics are used to make a website more beautiful and you can use many tools like Adobe Photoshop for that but in a lot of places, we need dynamic image manipulation. For this purpose, we can use GD library in PHP.

How to add a multiply effect to an image?

For people looking to apply a 'multiply' effect on images like the one in Photoshop (generally b&w ones), you can achieve it with the IMG_FILTER_COLORIZE filter. filtertype is an integer. So if you want to use it as a variable and need to use, e.g. preg_match function you can do it in this way:

How to use the ImageFilter () function?

The imagefilter () function can be used to apply various fun and/or useful effects to an existing image or photograph. The selection of effects is: Use arg1, arg2 & arg3 in the form of red, blue, green and arg4 for the alpha channel.

How does IMG_filter_grayscale work?

IMG_FILTER_GRAYSCALE: Converts the image into grayscale by changing the red, green and blue components to their weighted sum using the same coefficients as the REC.601 luma (Y') calculation. The alpha components are retained. For palette images the result may differ due to palette limitations.


3 Answers

You need to take every pixel of your image, then multiply each RGB value with your background color / 255 (it's the Photoshop formula). For example, a JPG file with a red background color multiply filter, saved as a PNG file for better results:

<?php 
$filter_r=216;
$filter_g=0;
$filter_b=26;
$suffixe="_red";
$path=YOURPATHFILE;

if(is_file($path)){
    $image=@imagecreatefromjpeg($path);
    $new_path=substr($path,0,strlen($path)-4).$suffixe.".png";

    $imagex = imagesx($image);
    $imagey = imagesy($image);
    for ($x = 0; $x <$imagex; ++$x) {
        for ($y = 0; $y <$imagey; ++$y) {
            $rgb = imagecolorat($image, $x, $y);
            $TabColors=imagecolorsforindex ( $image , $rgb );
            $color_r=floor($TabColors['red']*$filter_r/255);
            $color_g=floor($TabColors['green']*$filter_g/255);
            $color_b=floor($TabColors['blue']*$filter_b/255);
            $newcol = imagecolorallocate($image, $color_r,$color_g,$color_b);
            imagesetpixel($image, $x, $y, $newcol);
        }
    }

    imagepng($image,$new_path);
}
?>
like image 128
colivier Avatar answered Oct 18 '22 06:10

colivier


I've been looking for Multiply blend between two images as well and couldn't find any native-php solution for it. It appears that only way (for now) is to "manually" set pixels, pixel-by-pixel. Here's my code that does Multiply blend between two images, assuming that images are of the same size. You can adjust it to handle different sizes if you like.

function multiplyImage($dst,$src)
{
    $ow = imagesx($dst);
    $oh = imagesy($dst);

    $inv255 = 1.0/255.0;

    $c = imagecreatetruecolor($ow,$oh);
    for ($x = 0; $x <$ow; ++$x) 
    {
        for ($y = 0; $y <$oh; ++$y) 
        {
            $rgb_src = imagecolorsforindex($src,imagecolorat($src, $x, $y));
            $rgb_dst = imagecolorsforindex($dst,imagecolorat($dst, $x, $y));
            $r = $rgb_src['red'] * $rgb_dst['red']*$inv255;
            $g = $rgb_src['green'] * $rgb_dst['green']*$inv255;
            $b = $rgb_src['blue'] * $rgb_dst['blue']*$inv255;
            $rgb = imagecolorallocate($c,$r,$g,$b);
            imagesetpixel($c, $x, $y, $rgb);
        }
    }
    return $c;
}

Function returns image object so you should ensure to do imagedestroy after you're done using it.

There should be a workaround using overlay native-php blend, which suggests that 50% gray pixels of destination image will be affected by source pixels. In theory, if you do need to blend two black-and-white images (no gray tones), if you set contrast of destination image so white will become 50%-gray, and then overlay-blend source image over it, should give you something similar to multiply. But for color images, or grayscale images, this wouldn't work - above method appears to be the only option.

like image 24
Siniša Avatar answered Oct 18 '22 06:10

Siniša


I was led into this thread when I needed to blend two images in GD. It seems there is no code specifically for that so I will just leave this here for future visitors to this page.

This is a fork from the answer of colivier that supports multiply-blending of two images.

The two images need not be of the same size BUT the overlaying image will be resized and cropped to the size of the bottom layer. I made a fit helper function to do just that but don't bother with that.

imagecolorat returns the base color, even with PNGs with transparency. That is, a 50% black (visible as (128, 128, 128)) will be returned as (0, 0, 0, 64) 64 being the alpha value. This code takes into consideration translucency and converts the translucent colors to the visible color values.

// bottom layer
$img1 = imagecreatefromjpeg(realpath(__DIR__.'/profilePic.jpg'));

// top layer
$img2 = imagecreatefrompng(realpath(__DIR__.'/border2.png'));
imagealphablending($img2, false);
imagesavealpha($img2, true);

$imagex = imagesx($img1);
$imagey = imagesy($img1);

$imagex2 = imagesx($img2);
$imagey2 = imagesy($img2);

// Prereq: Resize img2 to match img1, cropping beyond the aspect ratio
$w1 = max(min($imagex2, $imagex), $imagex);
$h1 = max(min($imagey2, $imagey), $imagey);

$w_using_h1 = round($h1 * $imagex2 / $imagey2);
$h_using_w1 = round($w1 * $imagey2 / $imagex2);

if ($w_using_h1 > $imagex) {
    fit($img2, $imagex, $imagey, 'HEIGHT', true);
}
fit($img2, $imagex, $imagey, 'WIDTH', true);

// Actual multiply filter
for ($x = 0; $x < $imagex; ++$x) {
    for ($y = 0; $y < $imagey; ++$y) {
        $rgb1 = imagecolorat($img1, $x, $y);
        $rgb2 = imagecolorat($img2, $x, $y);
        $idx1 = imagecolorsforindex($img1, $rgb1);
        $idx2 = imagecolorsforindex($img2, $rgb2);

        // Shift left 8, then shift right 7
        // same as multiply by 256 then divide by 128
        // approximate multiply by 255 then divide by 127
        // This is basically multiply by 2 but, expanded to show that
        // we are adding a fraction of white to the translucent image
        // $adder = ($idx2['alpha'] << 8 >> 7);
        $adder = ($idx2['alpha'] << 1);
        $rmul = min(255, $idx2['red']   + $adder);
        $gmul = min(255, $idx2['green'] + $adder);
        $bmul = min(255, $idx2['blue']  + $adder);

        $color_r = floor($idx1['red'] * $rmul / 255);
        $color_g = floor($idx1['green'] * $gmul / 255);
        $color_b = floor($idx1['blue'] * $bmul / 255);

        $newcol = imagecolorallocatealpha($img1, $color_r, $color_g, $color_b, 0);
        imagesetpixel($img1, $x, $y, $newcol);
    }
}
imagejpeg($img1, __DIR__.'/out.jpg');



/**
 * Fits an image to a $w x $h canvas
 * 
 * @param type $w Target width
 * @param type $h Target height
 * @param int $fit_which Which dimension to fit
 * @param bool $upscale If set to true, will scale a smaller image to fit the given dimensions
 * @param bool $padded If set to true, will add padding to achieve given dimensions
 * 
 * @return Image object
 */
function fit(&$img, $w, $h, $fit_which = 'BOTH', $upscale = false, $padded = true) {

    if (!in_array($fit_which, array('WIDTH', 'HEIGHT', 'BOTH'))) {
        $fit_which = 'BOTH';
    }
    $w0 = imagesx($img);
    $h0 = imagesy($img);

    if (!$upscale && $w0 <= $w && $h0 <= $h)
        return $this;

    if ($padded) {
        $w1 = max(min($w0, $w), $w);
        $h1 = max(min($h0, $h), $h);
    }
    else {
        $w1 = min($w0, $w);
        $h1 = min($h0, $h);
    }
    $w_using_h1 = round($h1 * $w0 / $h0);
    $h_using_w1 = round($w1 * $h0 / $w0);

    // Assume width, crop height
    if ($fit_which == 'WIDTH') {
        $w2 = $w1;
        $h2 = $h_using_w1;
    }
    // Assume height, crop width
    elseif ($fit_which == 'HEIGHT') {
        $w2 = $w_using_h1;
        $h2 = $h1;
    }
    elseif ($fit_which == 'BOTH') {
        if (!$padded) {
            $w2 = $w = min($w, $w_using_h1);
            $h2 = $h = min($h, $h_using_w1);
        }
        else {
            // Extend vertically
            if ($h_using_w1 <= $h) {
                $w2 = $w1;
                $h2 = $h_using_w1;
            }
            // Extend horizontally
            else {
                $w2 = $w_using_h1;
                $h2 = $h1;
            }
        }
    }

    $im2 = imagecreatetruecolor($w, $h);
    imagealphablending($im2, true);
    imagesavealpha($im2, true);

    $transparent = imagecolorallocatealpha($im2, 255, 255, 255, 127);
    imagefill($im2, 0, 0, $transparent);

    imagealphablending($img, true);
    imagesavealpha($img, true);
    // imagefill($im, 0, 0, $transparent);

    imagecopyresampled($im2, $img, ($w - $w2) / 2, ($h - $h2) / 2, 0, 0, $w2, $h2, $w0, $h0);

    $img = $im2;    
}
like image 35
Kyle Domingo Avatar answered Oct 18 '22 06:10

Kyle Domingo