PDA

View Full Version : sharpening (GD) (PHP TIMEOUT)


deblur
25th of December 2006 (Mon), 06:39
I don't know if this is really a bug, it could just be a slow server or something, but I'm having problems uploading images with the sharpening turned on in GD.

The sharpening itself is clearly very slow with the preview, taking perhaps a minute or more to display, so it seems currently like it will be to slow to use anyway.

But the uploads themselves seem to give up with several sizes selected - the ftp log during upload seems to show that the first couple of sizes are being uploaded but then after a while it just stops completely and the upload never completes - maybe something is timing out (although there is no message indicating that). With the sharpening turned off it is fine. Also with sharpening on but only one size path selected for upload it seems to work ok (a little bit slower, but it gets there).

deblur
26th of December 2006 (Tue), 14:24
ok, I think I found my own problem, the PHP timeout needed to be increased above 30 sec in .htaccess, as indicated by the Install check.

Mind you, the sharpening still seems to take forever with GD so I've left it switched off for uploads for now.

I'm wondering if the routine could be sped up by an FFT implementation - did I read somewhere it wasn't too efficient currently?

wkitty42
27th of December 2006 (Wed), 21:31
FWIW: the php timeout is the first thing to look into when working on problems like these...

the only graphics manipulation stuff i have on my platform is GD2 and i agree that the internal GD libraries need some work but this is also on my platform and server... with a faster server pair (http and sql) i could end up like many others and not even notice these problems because the servers' speeds would cover them up quite effictively...

Pekka
28th of December 2006 (Thu), 14:42
As PHP is high level (interpreted) language it is much slower than e.g. C which e.g. Imagemagick runs on. FFT with php would be even slower. The difference is more obvious with larger source images.

deblur
28th of December 2006 (Thu), 21:05
yeah I appreciate that, I was just wondering if there is any way to speed it up (I can only use GD on my server)! Imagemagick sharpen code is part of the library, but the GD sharpening is an addon written in PHP, using some offset and composite commands to simulate the sharpening kernel right?

OK I found the code again in SCRIPT_editor_functions.php
I'm presuming the 'imagecopy' and 'imagecopymerged' functions are compiled in the GD library (i.e. not bits of PHP code)? If so then I guess you're right that writing an FFT in php would be slower than all those calls. Actually I'd be interested to see if that's really what's slowing it down... actually the thresholding operation looks pretty inefficient as well having to loop through all the pixels.

Well I figure there's probably an error in those weights used anyway causing the image darkening (the gaussian blur matrix probably isn't summing to 1), fixing that would eliminate the need for the second batch of imagecopymerges.

Also, as the kernel is separable, I think it can be reduced to 3 applications of the shift and add in each direction. i.e. shift up one pixel and down one pixel, weight 50%, then mix this with the unshifted at 50%. Repeat the operation in the horizontal direction with this intermediate image.

Pekka
28th of December 2006 (Thu), 21:50
The code is not by me as function header tells, it is used by permission from the author (VBulletin uses the same code btw). It would be absolutely great if you could make use of your skills to improve it, you sound you know a lot about these algorithms. Lots of Kudos there!! :)

deblur
29th of December 2006 (Fri), 07:23
ok, I'm no expert in PHP, and I ought to check the code more carefully, but I've made this change:

replace
// Move copies of the image around one pixel at the time and merge them with weight
// according to the matrix. The same matrix is simply repeated for higher radii.
for ($i = 0; $i < $radius; $i++) {
imagecopy ($imgBlur, $imgCanvas, 0, 0, 1, 1, $w - 1, $h - 1); // up left
imagecopymerge ($imgBlur, $imgCanvas, 1, 1, 0, 0, $w, $h, 50); // down right
imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 1, 0, $w - 1, $h, 33.33333); // down left
imagecopymerge ($imgBlur, $imgCanvas, 1, 0, 0, 1, $w, $h - 1, 25); // up right
imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 1, 0, $w - 1, $h, 33.33333); // left
imagecopymerge ($imgBlur, $imgCanvas, 1, 0, 0, 0, $w, $h, 25); // right
imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 20 ); // up
imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 16.666667); // down
imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 0, $w, $h, 50); // center
imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h);

// During the loop above the blurred copy darkens, possibly due to a roundoff
// error. Therefore the sharp picture has to go through the same loop to
// produce a similar image for comparison. This is not a good thing, as processing
// time increases heavily.
imagecopy ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 50);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 33.33333);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 25);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 33.33333);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 25);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 20 );
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 16.666667);
imagecopymerge ($imgBlur2, $imgCanvas2, 0, 0, 0, 0, $w, $h, 50);
imagecopy ($imgCanvas2, $imgBlur2, 0, 0, 0, 0, $w, $h);

}

in SCRIPT_editor_functions.php with:
// Move copies of the image around one pixel at the time and merge them with weight
// according to the matrix. The same matrix is simply repeated for higher radii.
for ($i = 0; $i < $radius; $i++) {
imagecopy ($imgBlur, $imgCanvas, 0, 0, 1, 0, $w - 1, $h); // left
imagecopymerge ($imgBlur, $imgCanvas, 1, 0, 0, 0, $w, $h, 50); // right
imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 0, $w, $h, 50); // center
imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h);

imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 33.33333 ); // up
imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 25); // down
imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h);

}

the result appears to be the same and the test image is generated about 3 times faster (takes about 30s instead of 100s for a 1024pixel image on my server). Don't know if there's a better way of benchmarking it than using the Resize methods preview?

There may be other ways to make it run faster, I'll have to take a think when I get some more free time!

deblur
29th of December 2006 (Fri), 20:43
ok, with that change I've found out that the thing that is taking most of the time is the thresholding operation. I was trying to make a simplification if the threshold was set to zero to see if that would speed it up, but I've gone and messed something up and now I can't seem to get any of the admin pages to load - they just appear blank :cry:

sorry I'm not too familiar with debugging php! Do I need to reset my session or something, or delete a cookie? The gallery itself is still working fine, but nothing in the admin section loads (not even the login page).

Pekka
29th of December 2006 (Fri), 22:55
If you get blank pages it most likely means there is an error in some included function (SCRIPT_editor_functions.php?). EE 2 has all php errors suppressed by default. You can set line 1

error_reporting(0);

to

error_reporting(255);

to see the error in SCRIPT_editor_functions.php when you load that file to browser.

deblur
1st of January 2007 (Mon), 19:51
I tried changing the error reporting to 255 in both SCRIPT_editor_functions.php and in index.php that I was trying to load, but it still didn't show anything! Maybe there were other files that needed it too? Well I fixed the mistake anyway (it was just a missing semicolon), and it is all working again now.

So, here's my revised version of the code. Actually there is an updated version at http://vikjavev.no/computing/ump.php, which I have based the changes on, although the only change is using a builtin function for the blurred image which is only available in PHP>5.1 (no good to me!). I should probably send the revised version to the author but maybe someone can confirm the speed improvement first?

I've sped up the blurring part for older PHP versions considerably here by removing many of the redundant imagecopymerge and imagecopy's. As far as I can tell it is now the final stage which is taking most of the time to process - unfortunately it's not possible to get rid of the loop through the pixels as I'd hoped, because there is no other way to find the difference of two images that I can see using GD. However I have made things a little faster by optimizing it slightly when the threshold is set to 0 (for downsized images you can probably get away with this).

In my tests setting the threshold to 0 makes it about 20 or 30% faster, but I could really do with someone else testing it (on a dedicated server?), the runtimes can vary wildly for me (presumably the server is busy), now typically between 10 and 30 seconds to sharpen the test image with radius 0.5 and threshold 0. I don't know if adding some stopwatch code to the resize methods preview would help here?

Sadly, I think for uploading batches of photos it is still to slow for me to use on my server except for one-offs. Which means I'm probably back to trying to batch process the files in photoshop and upload them manually... but I'm having big problems with this too :confused: - I'll start another thread on these matters soon!

/*

WARNING! Due to a known bug in PHP 4.3.2 this script is not working well in this version. The sharpened images get too dark. The bug is fixed in version 4.3.3.

From version 2 (July 17 2006) the script uses the imageconvolution function in PHP version >= 5.1, which improves the performance considerably.

Unsharp masking is a traditional darkroom technique that has proven very suitable for
digital imaging. The principle of unsharp masking is to create a blurred copy of the image
and compare it to the underlying original. The difference in colour values
between the two images is greatest for the pixels near sharp edges. When this
difference is subtracted from the original image, the edges will be
accentuated.

The Amount parameter simply says how much of the effect you want. 100 is 'normal'.
Radius is the radius of the blurring circle of the mask. 'Threshold' is the least
difference in colour values that is allowed between the original and the mask. In practice
this means that low-contrast areas of the picture are left unrendered whereas edges
are treated normally. This is good for pictures of e.g. skin or blue skies.

Any suggenstions for improvement of the algorithm, expecially regarding the speed
and the roundoff errors in the Gaussian blur process, are welcome.

*/

function UnsharpMask($img, $amount, $radius, $threshold) {

////////////////////////////////////////////////////////////////////////////////////////////////
////
//// Unsharp Mask for PHP - version 2.0
////
//// Unsharp mask algorithm by Torstein Hønsi 2003-06.
//// thoensi_at_netcom_dot_no.
//// Please leave this notice.
////
///////////////////////////////////////////////////////////////////////////////////////////////


/*
USED WITH AUTHORS PERMISSION IN EXHIBIT ENGINE
*/

// $img is an image that is already created within php using
// imgcreatetruecolor. No url! $img must be a truecolor image.

// Attempt to calibrate the parameters to Photoshop:
if ($amount > 500) $amount = 500;
$amount = $amount * 0.016;
if ($radius > 50) $radius = 50;
$radius = $radius * 2;
if ($threshold > 255) $threshold = 255;

$radius = abs(round($radius)); // Only integers make sense.
if ($radius == 0) {
return $img; imagedestroy($img); break; }
$w = imagesx($img); $h = imagesy($img);
$imgCanvas = imagecreatetruecolor($w, $h);
$imgBlur = imagecreatetruecolor($w, $h);


// Gaussian blur matrix:
//
// 1 2 1
// 2 4 2
// 1 2 1
//
//////////////////////////////////////////////////


if (function_exists('imageconvolution')) { // PHP >= 5.1
$matrix = array(
array( 1, 2, 1 ),
array( 2, 4, 2 ),
array( 1, 2, 1 )
);
imagecopy ($imgBlur, $img, 0, 0, 0, 0, $w, $h);
imageconvolution($imgBlur, $matrix, 16, 0);
}
else {

// Move copies of the image around one pixel at the time and merge them with weight
// according to the matrix. The same matrix is simply repeated for higher radii.
for ($i = 0; $i < $radius; $i++) {
imagecopy ($imgBlur, $img, 0, 0, 1, 0, $w - 1, $h); // left
imagecopymerge ($imgBlur, $img, 1, 0, 0, 0, $w, $h, 50); // right
imagecopymerge ($imgBlur, $img, 0, 0, 0, 0, $w, $h, 50); // center
imagecopy ($imgCanvas, $imgBlur, 0, 0, 0, 0, $w, $h);

imagecopymerge ($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 33.33333 ); // up
imagecopymerge ($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 25); // down
}
}

if($threshold>0){
// Calculate the difference between the blurred pixels and the original
// and set the pixels
for ($x = 0; $x < $w; $x++) { // each row
for ($y = 0; $y < $h; $y++) { // each pixel

$rgbOrig = ImageColorAt($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);

$rgbBlur = ImageColorAt($imgBlur, $x, $y);

$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
$bBlur = ($rgbBlur & 0xFF);

// When the masked pixels differ less from the original
// than the threshold specifies, they are set to their original value.
$rNew = (abs($rOrig - $rBlur) >= $threshold)
? max(0, min(255, ($amount * ($rOrig - $rBlur)) + $rOrig))
: $rOrig;
$gNew = (abs($gOrig - $gBlur) >= $threshold)
? max(0, min(255, ($amount * ($gOrig - $gBlur)) + $gOrig))
: $gOrig;
$bNew = (abs($bOrig - $bBlur) >= $threshold)
? max(0, min(255, ($amount * ($bOrig - $bBlur)) + $bOrig))
: $bOrig;



if (($rOrig != $rNew) || ($gOrig != $gNew) || ($bOrig != $bNew)) {
$pixCol = ImageColorAllocate($img, $rNew, $gNew, $bNew);
ImageSetPixel($img, $x, $y, $pixCol);
}
}
}
}
else{
for ($x = 0; $x < $w; $x++) { // each row
for ($y = 0; $y < $h; $y++) { // each pixel
$rgbOrig = ImageColorAt($img, $x, $y);
$rOrig = (($rgbOrig >> 16) & 0xFF);
$gOrig = (($rgbOrig >> 8) & 0xFF);
$bOrig = ($rgbOrig & 0xFF);

$rgbBlur = ImageColorAt($imgBlur, $x, $y);

$rBlur = (($rgbBlur >> 16) & 0xFF);
$gBlur = (($rgbBlur >> 8) & 0xFF);
$bBlur = ($rgbBlur & 0xFF);

$rNew = ($amount * ($rOrig - $rBlur)) + $rOrig;
if($rNew>255){$rNew=255;}
elseif($rNew<0){$rNew=0;}
$gNew = ($amount * ($gOrig - $gBlur)) + $gOrig;
if($gNew>255){$gNew=255;}
elseif($gNew<0){$gNew=0;}
$bNew = ($amount * ($bOrig - $bBlur)) + $bOrig;
if($bNew>255){$bNew=255;}
elseif($bNew<0){$bNew=0;}
$rgbNew = ($rNew << 16) + ($gNew <<8) + $bNew;
ImageSetPixel($img, $x, $y, $rgbNew);
}
}
}
imagedestroy($imgCanvas);
imagedestroy($imgBlur);

return $img;

}