I run a website that needs to routinely loop through a bunch of PNGs with transparency's and merge them together. Sometimes this can be a LONG process so I was wondering what is the most efficient way to do this. I'm using GD as I hear ImageMagick isn't really any FASTER..
$firstTime = true; // need to know if it's the first time through the loop
$img = null; // placeholder for each iterative image
$base = null; // will become the final merged image
$width = 0;
$height = 0;
while( $src = getNextImageName() ){
$imageHandle = imagecreatefrompng($src);
imageAlphaBlending($imageHandle, true);
imageSaveAlpha($imageHandle, true);
if( $firstTime ){
$w = imagesx( $img ); // first time in we need to
$h = imagesy( $img ); // save the width & height off
$firstTime = false;
$base = $img; // copy the first image to be the 'base'
} else {
// if it's not the first time, copy the current image on top of base
// and then delete the current image from memory
imagecopy($base, $img, 0, 0, 0, 0, $w, $h);
imagedestroy($img);
}
}
// final cleanup
imagepng($base);
imagedestroy($base);
You should definitely give ImageMagick a try. It's easy to implement, just use exec('composite 1.png 2.png');. It's well documented, not bound to PHPs memory limits and the performance is ok.
In addition, ImageMagick works great as a stand-alone for bash scripting or another terminal functions which means what you learn is useful outside of PHP.
According to a benchmark, ImageMagick is faster than GD. This would be a start at least.
I don't know whether you could also increase the priority of PHP to Above Normal/High?
Related
why is scaling image in php so complicated ? A lot of confusin about
imagecreatetruecolor
imagecreatefromjpeg
imagecopyresampled
imagejpeg - and so on
I simply want to scale down an image - if it's width is over 960px
something like this:
$path = "sbar/01.jpg";
$w = getimagesize($path)[0];
if($w > 960){scale($path, 960, auto);}
what is the simplest way to do this ? Here is my try:
$max = 960;
$h = getimagesize($path)[1];
$ratio = $w/$h;
$new_height = $w/$ratio;
$img = imagecreatetruecolor($max, $new_height);
$image = imagecreatefromjpeg($path);
And what now? There is still no desired image (960 x auto) in $path
There is no "best" way to do it. There are just different ways to do it. If you have an image that is already stored and you want to scale it and then save the scaled version you can simply do this:
$image = imagecreatefromjpeg('test.jpg');
$newImage = imagescale($image, 960, -1);
imagejpeg($newImage, 'scaled_image.jpg');
You first load the image from a given path. Then you scale it (while the image stays in memory). At the end, you save it under the provided path. Done.
The -1 (or if you omit the value altogether) means to only scale the other dimension and keep the aspect ratio.
In a recent competition I was given the task to extract binary data (another PNG) from a PNG image file's alpha channel. The data was encoded in such a way that if I read the values in the alpha channel for each pixel from the top left (e.g. 80,78,71,13,10,26,10) up to a specific point then the resulting data would form another image.
Initially I tried to complete this task using PHP, but I hit a roadblock that I could not overcome. Consider the code below:
function pad($hex){
return strlen($hex) < 2 ? "0$hex" : $hex;
}
$channel = '';
$image = 'image.png';
$ir = imagecreatefrompng($image);
imagesavealpha($ir, true);
list($width, $height) = getimagesize($image);
for ($y = 0; $y < $height; $y++){
for ($x = 0; $x < $width; $x++){
$pixel = imagecolorat($ir, $x, $y);
$colors = imagecolorsforindex($ir, $pixel);
$a = pad(dechex($colors['alpha']));
$channel.= $a;
}
}
After running this, I noticed that the output did not contain the PNG magic number, and I just didn't know what went wrong. After a bit of digging I found out that $colors['alpha'] only contained values less than or equal to 127. Due to the data having been encoded with a 0-255 range in mind, I could not find a way to extract it, and ended up (successfully) solving the problem with node.js instead.
So, my question is: Is there any way I could have read the PNG file's alpha channel that would have returned the values in a 0 to 255 range as opposed to 0-127, or is this a hard-coded limitation of PHP and/or GD?
For the record, I tried to use ($colors['alpha']/127)*255 in order to try and forge the value in the incorrect range to the correct one, but to no avail.
It is a limitation of GD. According to https://bitbucket.org/libgd/gd-libgd/issues/132/history-behind-7-bit-alpha-component, in GD source it says:
gd has only 7 bits of alpha channel resolution, and 127 is
transparent, 0 opaque. A moment of convenience, a lifetime of
compatibility.
If you install ImageMagick PHP extension, you can get the alpha value between 0-255 for a pixel (let's say with x=300 and y=490) like this:
$image = new Imagick(__DIR__ . DIRECTORY_SEPARATOR . 'image.png');
$x = 300;
$y = 490;
$pixel = $image->getImagePixelColor($x, $y);
$colors = $pixel->getColor(true);
echo 'Alpha value: ' . round($colors['a'] * 255, 0);
ImageMagick: https://www.imagemagick.org
ImageMagick for PHP (called Imagick): http://php.net/manual/en/book.imagick.php
In your code:
list($width, $height) = getimagesize($image);
references a variable '$image' that was not defined in the code.
using getimagesize means $image is a filename.
so this line get's width and height from a filename.
This line broke the code when I tested it.
You already have your answer, but this is for posterity.
Considering the code, I feel:
$width=imagesx ($ir);
$height=imagesy ($ir);
Would be more logical (and actually works)
I'm using imagecopyresampled to resize (shrink) an image, which happens to be a gif. The image contains text which, when resized, is quite blurry. I wouldn't necessarily mind that, but when displaying the original image on a web page, at the reduced size, my browser scales it down with much nicer results. Any idea what I can do to improve what PHP produces?
UPDATE: Here's an example of the code I'm running:
$x1 = 0;
$y1 = 0;
$w1 = 196;
$h1 = 260;
$x2 = 0;
$y2 = 0;
$w2 = 140;
$h2 = 186;
$r1 = imagecreatefromgif($source);
$r2 = imagecreatetruecolor($w2, $h2);
imagealphablending($r2, false);
imagesavealpha($r2, true);
$res = imagecopyresampled($r2, $r1, $x2, $y2, $x1, $y1, $w2, $h2, $w1, $h1);
imagegif($r2, $dest);
Here's an example of the image scaled by the browser:
Here's an example of the image scaled with the above code:
Try to use imagepng instead of imagegif. imagepng third parameter is quality. Check specs: http://php.net/manual/en/function.imagepng.php
The PHP libraries aren't going to produce the best quality resize operation however you may find you have better results if you stick to a ratio based on the original file such that resizing a 200x200 to 100x100 or 50x50 would be find as its easy maths for the process to handle (i.e. merge 2/4 pixels into one). Your current operation is producing blurry results as it'll be a random value such as scaling to 0.63% of the original size.
I'm using FPDF with an extension I found online called TransFPDF that allows for transparent PNG's to be put into the PDF that I am dynamically generating with PHP. The problem I am having however is that the PDF takes a long time to generate (the script takes about 30 seconds to run when I am embedding about 6 characters, where each characters is a transparent PNG. This time also includes text and the background but I checked times and those only take about a second or two and are not slowing down the code).
I have found that the main slow point is the following function:
// needs GD 2.x extension
// pixel-wise operation, not very fast
function ImagePngWithAlpha($file,$x,$y,$w=0,$h=0,$link='')
{
$tmp_alpha = tempnam('.', 'mska');
$this->tmpFiles[] = $tmp_alpha;
$tmp_plain = tempnam('.', 'mskp');
$this->tmpFiles[] = $tmp_plain;
list($wpx, $hpx) = getimagesize($file);
$img = imagecreatefrompng($file);
$alpha_img = imagecreate( $wpx, $hpx );
// generate gray scale pallete
for($c=0;$c<256;$c++) ImageColorAllocate($alpha_img, $c, $c, $c);
// extract alpha channel
$xpx=0;
while ($xpx<$wpx){
$ypx = 0;
while ($ypx<$hpx){
$color_index = imagecolorat($img, $xpx, $ypx);
$alpha = 255-($color_index>>24)*255/127; // GD alpha component: 7 bit only, 0..127!
imagesetpixel($alpha_img, $xpx, $ypx, $alpha);
++$ypx;
}
++$xpx;
}
imagepng($alpha_img, $tmp_alpha);
imagedestroy($alpha_img);
// extract image without alpha channel
$plain_img = imagecreatetruecolor ( $wpx, $hpx );
imagecopy ($plain_img, $img, 0, 0, 0, 0, $wpx, $hpx );
imagepng($plain_img, $tmp_plain);
imagedestroy($plain_img);
//first embed mask image (w, h, x, will be ignored)
$maskImg = $this->Image($tmp_alpha, 0,0,0,0, 'PNG', '', true);
//embed image, masked with previously embedded mask
$this->Image($tmp_plain,$x,$y,$w,$h,'PNG',$link, false, $maskImg);
}
Does anyone have any ideas of how I could speed this up? I can't seem to get it to go faster than about 4 seconds per character which really adds up fast (a character probably is about 1000x2000 pixels, and yes I know this is a lot, but yes it is neccessary for a printable PDF).
Thank you.
How would I be able to detect that an image is blank (only of a single, arbitrary color or, with a gif, frames of random arbitrary colors) using PHP and/or imagemagick?
I think this is what I'm going to try:
http://www.php.net/manual/en/function.imagecolorat.php#97957
You can check the image inside of PHP using imagecolorat (this may be slow, but it works):
function isPngValidButBlank($filename) {
$img = imagecreatefrompng($filename);
if(!$img)
return false;
$width = imagesx($img);
$height = imagesy($img);
if(!$width || !$height)
return false;
$firstcolor = imagecolorat($img, 0, 0);
for($i = 0; $i < $width; $i++) {
for($j = 0; $j < $height; $j++) {
$color = imagecolorat($img, $i, $j);
if($color != $firstcolor)
return false;
}
}
return true;
}
http://www.php.net/manual/en/function.imagecolorstotal.php gives you the amount of colors in an image. Hmm, in my demo it doesn't seem to work, sorry :( an image i created (fully red, 20x20 pixels) gives 0 colors for PNG and 3 colors for GIF.
Ok: http://www.dynamicdrive.com/forums/showpost.php?p=161187&postcount=2 look at the 2nd piece of code. Tested here: http://www.pendemo.nl/totalcolors.php
Kevin's solution can be sped up using random sampling. If you have some idea of the percentage of pixels that should be different from the background (assuming you aren't dealing with lots of images with only 1 different pixel), you can use the Poisson distribution:
probability of finding a nonblank pixel = 1 - e^(-n*p)
where n is the number of samples to try, and p is the percentage of pixels expected to be nonblank. Solve for n to get the appropriate number of samples to try:
n = -log(1 - x) / p
where x is the desired probability and log is natural log. For example, if you are reasonably sure that 0.1% of the image should be nonblank, and you want to have a 99.99% chance of finding at least one nonblank pixel,
n = -log(1-.9999)/.001 = 9210 samples needed.
Much faster than checking every pixel. To be 100% sure, you can always go back and check all of them if the sampling doesn't find any.
For anyone using Imagick to try and achieve this, the getImageColors() method did the trick.
https://www.php.net/manual/en/imagick.getimagecolors.php
$img = new Imagick();
$img->readImage($image);
$colors = $img->getImageColors();
if(!$colors > 1) {
return false;
}
Get the standard-deviation from the verbose statistics for each tile. If the standard deviation is 0, then the image is one color.
Supposedly, 'number of colors' will also do this; would be 1.
Use the -format option: http://www.imagemagick.org/script/escape.php