Multiply filter with PHP's GD library
Asked Answered
F

7

5

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.

Farcy answered 21/3, 2013 at 17:38 Comment(0)
P
13

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);
}
?>
Peking answered 1/4, 2013 at 17:55 Comment(3)
Excellent, thank you very much! That seems to provide the exact funcionality as Photoshop's multiply filter.Farcy
I used this in another example, but I found this implementation really slow. The faster way would be to use imagefilter with IMG_FILTER_COLORIZE. First, invert the intended color RGB value, then invert the image with imagefilter($image, IMG_FILTER_NEGATE), then apply the inverted color to the inverted image, then invert the image again. See here #26006491Ietta
Works great! I updated it a little to multiply two images, not just an image with a color: codepad.org/BSawY5exMaryammaryann
R
5

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.

Richel answered 29/3, 2014 at 5:12 Comment(0)
M
3

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;    
}
Michikomichon answered 26/8, 2015 at 12:11 Comment(0)
C
0

Have you tried to use php manual?

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.

<?php
function multiplyColor(&$im, $color = array(255, 0, 0)) {
   //get opposite color
   $opposite = array(255 - $color[0], 255 - $color[1], 255 - $color[2]);

   //now we subtract the opposite color from the image
   imagefilter($im, IMG_FILTER_COLORIZE, -$opposite[0], -$opposite[1], -$opposite[2]);
}
?>
Caracul answered 21/3, 2013 at 17:44 Comment(2)
I tried to implement that function, but my testing showed that it didn't produce the same result as Photoshop's multiply filter. It would seem natural to provide a background color to multiply against, and not just substract opposite colors.Farcy
The concept behind was useful in producing exactly what photoshop does. I tested it and the images are identical.Ietta
B
0

If used with png image and alpha must be well and works very well

    $filter_r=215;
    $filter_g=5;
    $filter_b=5;
    $alpha=70;
    $suffixe="_red";
    $path="./img/foto_220_590.png";
    if(is_file($path)){
        $image=imagecreatefrompng($path);
        $new_path=substr($path,0,strlen($path)-4).$suffixe.".png";

         echo $imagex = imagesx($image);
         echo $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);
                // this new alpha
                $newcol = imagecolorallocatealpha($image, $color_r,$color_g,$color_b,$alpha);
                imagesetpixel($image, $x, $y, $newcol);
            }
        }
         imagepng($image,$new_path);
Bermejo answered 6/5, 2016 at 0:32 Comment(0)
M
0

I updated @colivier script to be able to myltiply two images, and not just an image with a color:

/**
 * Multiply $pathToDst and $pathToSrc to $resultPath
 *
 * @param string $pathToDst
 * @param string $pathToSrc
 * @param string $resultPath
 */
function multiply($pathToDst, $pathToSrc, $resultPath) {
    switch (pathinfo($pathToDst, PATHINFO_EXTENSION)) {
        case "gif" :
            $resourceDst = imagecreatefromgif($pathToDst);
            break;
        case "png" :
            $resourceDst = imagecreatefrompng($pathToDst);
            break;
        default :
            $resourceDst = imagecreatefromjpeg($pathToDst);
            break;
    }

    switch (pathinfo($pathToSrc, PATHINFO_EXTENSION)) {
        case "gif" :
            $resourceSrc = imagecreatefromgif($pathToSrc);
            break;
        case "png" :
            $resourceSrc = imagecreatefrompng($pathToSrc);
            break;
        default :
            $resourceSrc = imagecreatefromjpeg($pathToSrc);
            break;
    }

    for ($x = 0; $x < 400; ++$x) {
        for ($y = 0; $y < 400; ++$y) {
            $TabColorsFlag = imagecolorsforindex($resourceDst, imagecolorat($resourceDst, $x, $y));
            $TabColorsPerso = imagecolorsforindex($resourceSrc, imagecolorat($resourceSrc, $x, $y));

            $color_r = floor($TabColorsFlag['red'] * $TabColorsPerso['red'] / 255);
            $color_g = floor($TabColorsFlag['green'] * $TabColorsPerso['green'] / 255);
            $color_b = floor($TabColorsFlag['blue'] * $TabColorsPerso['blue'] / 255);
            imagesetpixel($resourceDst, $x, $y, imagecolorallocate($resourceSrc, $color_r, $color_g, $color_b));
        }
    }

    imagepng($resourceDst, $resultPath, 0);
    imagedestroy($resourceDst);
    imagedestroy($resourceSrc);
}
Maryammaryann answered 12/5, 2016 at 16:6 Comment(0)
B
0

As of PHP 7.2.0 you can use the IMG_EFFECT_MULTIPLY filter with imagelayereffect.

<?php

imagelayereffect($image, IMG_EFFECT_MULTIPLY);
// implement imagecopy()

?>
Bohlen answered 20/8 at 22:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.