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:
I want it to look like this:
but the solutions I found give me this one:
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
This code doesn't exemplify the problem, but transforms colors like this:
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">
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);
}
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 />
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)
For illustrative purposes, in this case, the following image was used (because leaf.png
is monochromatic, with transparency):
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With