Php - replace base color of transparent png image
Asked Answered
B

6

12

I have searched a lot and I found only few solutions (on google and stackoverflow so please don't mark this one as a duplicate unless there's really duplicate question), but problems are hard edges. Is there any proper way of changing base color of, let's say black shape png image with transparent background but to preserve soft edges?

This is an example image:

enter image description here

I want it to look like this:

enter image description here

but the solutions I found give me this one:

enter image description here

Since I will be using this on my localhost, only for personal use, any php library that could help achieve this is appreciated.

UPDATE:

This is the function that gives me 3rd image:

function LoadPNG($imgname)
{
    $im = imagecreatefrompng ($imgname);
    imagetruecolortopalette($im,false, 255);
    $index = imagecolorclosest ( $im,  0,0,0 ); // GET BLACK COLOR
    imagecolorset($im,$index,0,150,255); // SET COLOR TO BLUE
    $name = basename($imgname);
    imagepng($im, getcwd()."/tmp/$name" ); // save image as png
    imagedestroy($im);
}
$dir = getcwd()."/img/";
$images = glob($dir."/*.png",GLOB_BRACE);
foreach($images as $image) {
    LoadPNG($image);
}

Originally, this function was a solution for GIF images (palette of 255 colors) so I guess that's why there are hard edges. I am looking for a solution (improvement to this script) to preserve transparency and soft edges of PNG image.

EDIT 2:

I have found an interesting approach using html5 canvas and javascript here: http://users7.jabry.com/overlord/mug.html

Maybe someone could have an idea how to translate this into PHP if even possible.

NEW SOLUTION

In answers

Blanks answered 18/7, 2013 at 21:1 Comment(4)
can you post some code for the solution that resulted in 3rd image ?Unscathed
@Unscathed yes, please give me a moment, I have to find it because in the meantime I have tried many others but unsuccessfully.Blanks
I think because of very small size of the image, there might be some overlap in the pixels and so the 3rd image is the closest that you would get. I can be wrong here though and I too would like to look at a better solution for this.Unscathed
very relevant and well phrased question, saved me tons of time. excellent answer by @SteApBingaman
N
16

This code doesn't exemplify the problem, but transforms colors like this:

enter image description here

Uses the ALPHA channel of an image to determines coloring. For other results, just play around with imagecolorallocatealpha():

function colorizeBasedOnAplhaChannnel( $file, $targetR, $targetG, $targetB, $targetName ) {

    $im_src = imagecreatefrompng( $file );

    $width = imagesx($im_src);
    $height = imagesy($im_src);

    $im_dst = imagecreatefrompng( $file );

    // Note this:
    // Let's reduce the number of colors in the image to ONE
    imagefilledrectangle( $im_dst, 0, 0, $width, $height, 0xFFFFFF );

    for( $x=0; $x<$width; $x++ ) {
        for( $y=0; $y<$height; $y++ ) {

            $alpha = ( imagecolorat( $im_src, $x, $y ) >> 24 & 0xFF );

            $col = imagecolorallocatealpha( $im_dst,
                $targetR - (int) ( 1.0 / 255.0  * $alpha * (double) $targetR ),
                $targetG - (int) ( 1.0 / 255.0  * $alpha * (double) $targetG ),
                $targetB - (int) ( 1.0 / 255.0  * $alpha * (double) $targetB ),
                $alpha
                );

            if ( false === $col ) {
                die( 'sorry, out of colors...' );
            }

            imagesetpixel( $im_dst, $x, $y, $col );

        }

    }

    imagepng( $im_dst, $targetName);
    imagedestroy($im_dst);

}

unlink( dirname ( __FILE__ ) . '/newleaf.png' );
unlink( dirname ( __FILE__ ) . '/newleaf1.png' );
unlink( dirname ( __FILE__ ) . '/newleaf2.png' );

$img = dirname ( __FILE__ ) . '/leaf.png';
colorizeBasedOnAplhaChannnel( $img, 0, 0, 0xFF, 'newleaf1.png' );
colorizeBasedOnAplhaChannnel( $img, 0xFF, 0, 0xFF, 'newleaf2.png' );
?>

Original
<img src="leaf.png">
<br />
<img src="newleaf1.png">
<br />
<img src="newleaf2.png">
Novocaine answered 19/7, 2013 at 20:22 Comment(1)
There are darker edges, if I am correct (small image so I cannot see perfectly) but this is by far the best php based solution. Thank you for effort, I will accept this answer.Blanks
K
2

Extending on the answer from SteAp, I had the need to also be able to adjust the transparency of each pixel based on an RGBA target color.

I also fixed the issue of having dark edges - which was a result of each pixel's color being adjusted by the original alpha level, rather than just adjusting the alpha on it's own.

// R,G,B = 0-255 range
// A = 0.0 to 1.0 range
function colorizeBasedOnAplhaChannnel($file, $targetR, $targetG, $targetB, $targetA, $targetName ) {
    $im_src = imagecreatefrompng($file);

    $width = imagesx($im_src);
    $height = imagesy($im_src);

    $im_dst = imagecreatefrompng($file);

    // Turn off alpha blending and set alpha flag
    imagealphablending($im_dst, false);
    imagesavealpha($im_dst, true);

    // Fill transparent first (otherwise would result in black background)
    imagefill($im_dst, 0, 0, imagecolorallocatealpha($im_dst, 0, 0, 0, 127));

    for ($x=0; $x<$width; $x++) {
        for ($y=0; $y<$height; $y++) {
            $alpha = (imagecolorat( $im_src, $x, $y ) >> 24 & 0xFF);

            $col = imagecolorallocatealpha( $im_dst,
                $targetR - (int) ( 1.0 / 255.0 * (double) $targetR ),
                $targetG - (int) ( 1.0 / 255.0 * (double) $targetG ),
                $targetB - (int) ( 1.0 / 255.0 * (double) $targetB ),
                (($alpha - 127) * $targetA) + 127
            );

            if (false === $col) {
                die( 'sorry, out of colors...' );
            }

            imagesetpixel( $im_dst, $x, $y, $col );
        }
    }

    imagepng( $im_dst, $targetName);
    imagedestroy($im_dst);
}
Keesee answered 7/11, 2014 at 1:47 Comment(0)
B
2

Using SteAp's accepted code as a starting point (since with it i didnt manage to achieve transparency, just a white background), i adapted said code and the result is this:

<?php 

function colorizeKeepAplhaChannnel( $inputFilePathIn, $targetRedIn, $targetGreenIn, $targetBlueIn, $outputFilePathIn ) {
    $im_src = imagecreatefrompng( $inputFilePathIn );
    $im_dst = imagecreatefrompng( $inputFilePathIn );
    $width = imagesx($im_src);
    $height = imagesy($im_src);

    // Note this: FILL IMAGE WITH TRANSPARENT BG
    imagefill($im_dst, 0, 0, IMG_COLOR_TRANSPARENT);
    imagesavealpha($im_dst,true);
    imagealphablending($im_dst, true);

    $flagOK = 1;
    for( $x=0; $x<$width; $x++ ) {
        for( $y=0; $y<$height; $y++ ) {
            $rgb = imagecolorat( $im_src, $x, $y );
            $colorOldRGB = imagecolorsforindex($im_src, $rgb);
            $alpha = $colorOldRGB["alpha"];
            $colorNew = imagecolorallocatealpha($im_src, $targetRedIn, $targetGreenIn, $targetBlueIn, $alpha);

            $flagFoundColor = true;
            // uncomment next 3 lines to substitute only 1 color (in this case, BLACK/greys)
/*
            $colorOld = imagecolorallocatealpha($im_src, $colorOldRGB["red"], $colorOldRGB["green"], $colorOldRGB["blue"], 0); // original color WITHOUT alpha channel
            $color2Change = imagecolorallocatealpha($im_src, 0, 0, 0, 0); // opaque BLACK - change to desired color
            $flagFoundColor = ($color2Change == $colorOld);
*/

            if ( false === $colorNew ) {
                //echo( "FALSE COLOR:$colorNew alpha:$alpha<br/>" );
                $flagOK = 0; 
            } else if ($flagFoundColor) {
                imagesetpixel( $im_dst, $x, $y, $colorNew );
                //echo "x:$x y:$y col=$colorNew alpha:$alpha<br/>";
            } 
        }
    }
    $flagOK2 = imagepng($im_dst, $outputFilePathIn);

    if ($flagOK && $flagOK2) {
        echo ("<strong>Congratulations, your conversion was successful </strong><br/>new file $outputFilePathIn<br/>");
    } else if ($flagOK2 && !$flagOK) {
        echo ("<strong>ERROR, your conversion was UNsuccessful</strong><br/>Please verify if your PNG is truecolor<br/>input file $inputFilePathIn<br/>");
    } else if (!$flagOK2 && $flagOK) {
        $dirNameOutput = dirname($outputFilePathIn)."/";
        echo ("<strong>ERROR, your conversion was successful, but could not save file</strong><br/>Please verify that you have PERMISSION to save to directory $dirName <br/>input file $inputFilePathIn<br/>");
    } else {
        $dirNameOutput = dirname($outputFilePathIn)."/";
        echo ("<strong>ERROR, your conversion was UNsuccessful AND could not save file</strong><br/>Please verify if your PNG is truecolor<br/>Please verify that you have PERMISSION to save to directory $dirName <br/>input file $inputFilePathIn<br/>");
    }

    echo ("TargetName:$outputFilePathIn wid:$width height:$height CONVERTED:|$flagOK| SAVED:|$flagOK2|<br/>");
    imagedestroy($im_dst);
    imagedestroy($im_src);
}




$targetRed = 0;
$targetGreen = 180;
$targetBlue = 0;

//$inputFileName = 'frameSquareBlack_88x110.png';
$inputFileName = 'testMe.png';
$dirName = "../img/profilePics/";
$nameTemp = basename($inputFileName, ".png");
$outputFileName = $nameTemp."_$targetRed"."_$targetGreen"."_$targetBlue.png";
$inputFilePath = $dirName.$inputFileName;
$outputFilePath = $dirName.$outputFileName;

//echo "inputFileName:$inputFilePath<br>outputName:$outputFilePath<br>";
colorizeKeepAplhaChannnel( $inputFilePath, $targetRed, $targetGreen, $targetBlue, $outputFilePath);
?>
<br/><br/>
Original <br/>
<img src="<?php echo $inputFilePath; ?>">
<br /><br />Colorized<br/>
<img src="<?php echo $outputFilePath; ?>">
<br />

enter image description here

this variation changes ALL colors to chosen color (not just black, a simple IF can solve the problem - uncomment 3 indicated lines in function and you will achieve this)

enter image description here

For illustrative purposes, in this case, the following image was used (because leaf.png is monochromatic, with transparency): enter image description here

Bingaman answered 15/12, 2014 at 13:43 Comment(0)
B
1

As I already told, I spent a lot of time searching and what I found so far is using html5 canvas, javascript and ajax.

Only library I used is javascript library jQuery but it is optional. Code can be easily rewritten to use plain javascript.

How it works:

1) js pulls data from ajax.php which returns an array of all the files

2) js then loops thru file list and performs change(src,color) for each item

3) js function change(src,color) loads image from source, replaces it's color and adds an img element to #Cell and displays it (for debug).

4) change() also calls save(src,filename,cname) function 5) js function save(src,filename,cname) sends an ajax request with image data and ajax.php saves image to server.

So here's the code:

ajax.php

<?php
$r = $_REQUEST;
$act = $r['action'];
if($act == "get_all") {
    $js = "";
    $dir = getcwd()."/img/";
    $images = glob($dir."/*.png",GLOB_BRACE);
    foreach($images as $image) {
        $name = basename($image);
        $js[] = $name;
    }
    echo json_encode($js);
    die();
}
elseif($act == "save") {
    $img = $r['file'];
    $name = $r['name'];
    $color = $r['color'];
    $dir = "results/$color";
    if(!file_exists($dir) || !is_dir($dir)) mkdir($dir,777,true);
    $file = $dir."/$name";
    file_put_contents($file,file_get_contents("data://".$img));
    if(file_exists($file)) echo "Success";
    else echo $file;
    die();
}

index.php (html only)

<!doctype html>
        <html>
<head>
    <script src="jquery.js" type="text/javascript"></script>
    <script src="demo.js" type="text/javascript"></script>
</head>
<body>
<div id="ctrl">
    <input type="text" id="color" value="#666666" placeholder="Color in HEX format (ex. #ff0000)" />
    <input type="text" id="cname" value="grey" placeholder="Color name (destionation dir name)" />
    <button type="button" id="doit">Change</button>
</div>
<div id="Cell">

</div>
</body>

</html>

demo.js

$(document).ready(function() {
    $(document).on("click","#doit",function() {
        var c = $("#color");
        if(c.val() != "") {
            $("#Cell").html("");
            $.post("ajax.php",{ action: "get_all" },function(s) {
                var images = $.parseJSON(s);
                $.each(images, function(index, element) {
                    change(images[index], c.val());
                });
            });
        }
    });
});
function change(src,color) {
    var myImg = new Image();
    myImg.src = "img/"+src;
    myImg.onload = function() {
        var canvas = document.createElement("canvas");
        var ctx = canvas.getContext("2d");
        ctx.drawImage(myImg,0,0);
        var imgd = ctx.getImageData(0, 0, myImg.width, myImg.height);
        canvas.height = myImg.height;
        canvas.width = myImg.width;
        var new_color = HexToRGB(color);
        // console.log(imgd)
        for (i = 0; i <imgd.data.length; i += 4) {
            imgd.data[i]   = new_color.R;
            imgd.data[i+1] = new_color.G;
            imgd.data[i+2] = new_color.B;
        }
        ctx.putImageData(imgd, 0, 0);
        var newImage=new Image()
        newImage.src=canvas.toDataURL("image/png");
        $(newImage).css("margin","5px");
        $(newImage).attr('data-title',src);
        $("#Cell").append(newImage);
        var c = $("#cname");
        if(c.val() == "") c.val("temp");
        save(newImage.src,src, c.val());
    };
}
function save(src,filename,cname) {
    $.post("ajax.php", { action: "save", file: src, name: filename, color: cname },function(s) {
        console.log(s);
    })
}
function HexToRGB(Hex)
{
    var Long = parseInt(Hex.replace(/^#/, ""), 16);
    return {
        R: (Long >>> 16) & 0xff,
        G: (Long >>> 8) & 0xff,
        B: Long & 0xff
    };
}

I have tested it, for re-coloring and saving 420 24x24 images, it took less than 10 seconds (on localhost) (420 async ajax calls). Once original images are cached, it finishes much faster. Image quality stays the same as original.

Again, this solution is for my personal use so code is pretty unmanaged and I am sure it can be improved but here you go - as is, it works.

Blanks answered 19/7, 2013 at 7:58 Comment(0)
N
0

The third image doesn't look fine, because imagetruecolortopalette($im,true, 255); renders an ugly image:

enter image description here

Since the second image doesn't look fine, the third can't look beautiful too.

Code:

<?php
unlink( dirname ( __FILE__ ) . '/newleaf.png' );
unlink( dirname ( __FILE__ ) . '/newleaf1.png' );

function LoadPNG( $imgname )
{
    $im = imagecreatefrompng ($imgname);
    imagetruecolortopalette($im,true, 255);

    imagepng($im, 'newleaf1.png' ); // save image as png

    $index = imagecolorclosest ( $im,  0,0,0 ); // GET BLACK COLOR
    imagecolorset($im,$index,0,150,255); // SET COLOR TO BLUE
    $name = basename($imgname);
    imagepng($im, 'newleaf.png' ); // save image as png
    imagedestroy($im);
}

$img = dirname ( __FILE__ ) . '/leaf.png';
LoadPNG( $img );

?>

Original
<img src="leaf.png">
<br />After make truecolortopalette($im,true, 255);
<img src="newleaf1.png">
<br />Thus..
<img src="newleaf.png">
Novocaine answered 18/7, 2013 at 22:7 Comment(3)
As I though. Do you have a solution for better rendering?Blanks
@NikolaR. Unfortunately, I can't provide a solution right now. Too late... Probably tomorrow. Interesting case...Novocaine
@NikolaR. Please find attached a working solution in my additional answer.Novocaine
P
0

I have tried example from SteAp, but it's not working on some files. I use imagemagick instead:

convert liquid.png -fuzz 100% -fill 'green' +opaque transparent -colorize 100 liquid_im.png
Proceed answered 19/6, 2020 at 7:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.