Frage

I have the following PHP script that takes an existing jpeg image and resizes it to a smaller thumbnail, while adding a png watermark to it. The problem I have is that the reduction is done with GD library which makes the output thumb not so sharp after reducing it (it's much sharper when no size reduction is required, ie. if the original and output size are the same). I was told that resizing algorithm in gd isn't very good and was advised to use ImageMagick instead, with the adaptive-resize option. I basically want to convert the script to use ImageMagick (with bicubic sharper) instead of the GD library:

<?php

if (isset($_GET['image']) && isset($_GET['width'])) {
$image = $_GET['image'];
$max_width = $_GET['width'];
$max_height = 800;
$wmark='watermark.png';
$wmarks='watermark_s.png';
$wmarkm='watermark_m.png';
$wmarkno='nowatermark.png';
$noimg='noimg.png'; 
if (file_exists($image)) {

  $cached='cache/'.preg_replace('/(\.\w+$)/',".{$max_width}\\1",$image);

  if (file_exists($cached)) {
    $cst=stat($cached);
    $fst=stat($image);
    if ($fst[9]<=$cst[9] && $fst[10]<=$cst[10]) {
      if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$cst[9]) {
        header("HTTP/1.0 304 Not Modified");
  } else {
header('Content-type: image/jpeg');
header('Last-Modified: '.gmdate('D, d M Y H:i:s',$cst[9]).' GMT');
header('Cache-Control: private');
print file_get_contents($cached);
      }
      exit;
    }
  }

$size = GetImageSize($image);
$watermark_img = imagecreatefrompng($wmark);
$watermarks_img = imagecreatefrompng($wmarks);
$watermarkm_img = imagecreatefrompng($wmarkm);
$watermarkno_img = imagecreatefrompng($wmarkno);

$wmrk_size = getimagesize($wmark);
$wmrks_size = getimagesize($wmarks);
$wmrkm_size = getimagesize($wmarkm);
$wmrkno_size = getimagesize($wmarkno);


$width = $size[0];
$height = $size[1];

@$x_ratio = $max_width / $width;
@$y_ratio = $max_height / $height;

if (($width <= $max_width) && ($height <= $max_height)) 
{
    $tn_height = $height;
    $tn_width = $width;
}
else if (($x_ratio * $height) < $max_height)
{
    $tn_height = ceil($x_ratio * $height);
    $tn_width = $max_width;
}
else
{
    $tn_height = $max_height;
    $tn_width = ceil($y_ratio * $width);
}

 if ((($tn_width) <>0) && (($tn_height)<>0)) {



$src = ImageCreateFromJPEG($image);
$dst = ImageCreateTrueColor($tn_width, $tn_height);
ImageCopyResampled($dst, $src, 0, 0, 0, 0, $tn_width, $tn_height, $width, $height);
//$dst = imagecreatefromjpeg($dst);
if (ImageSX($dst) > 300) {
    $posx = (ImageSX($dst) - ImageSX($watermark_img))/2;
    $posy = (ImageSY($dst) - ImageSY($watermark_img))/2;
imagecopy($dst, $watermark_img, $posx, $posy, 0, 0, $wmrk_size[0], $wmrk_size[1]);
} else {
$posxs = (ImageSX($dst) - ImageSX($watermarkno_img))/2;
$posys = (ImageSY($dst) - ImageSY($watermarkno_img))/2;
imagecopy($dst, $watermarkno_img, $posxs, $posys, 0, 0, $wmrkno_size[0], $wmrkno_size[1]);
}
header('Content-type: image/jpeg');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Cache-Control: private');
ImageJPEG($dst, null, 90);
ImageJPEG($dst, $cached, 90);
ImageDestroy($src);
ImageDestroy($dst);
                                                   }

                     }
  }
?>
War es hilfreich?

Lösung

I have modified it myself using ImageMagick. Much sharper and nicer output than GD. Below is the modified version. Hope this may help others.

<?php 
if (isset($_GET['image']) && isset($_GET['width']) && is_numeric($_GET['width']) ) {
// Get image name 
  $original_image = $_GET['image'];
  // Watermarks
  $wmark='watermark.png'; //largest watermark
  $wmarkm='watermark_m.png'; //medium watermark
  $wmarks='watermark_s.png'; //smallest watermark
  $wmarkno='nowatermark.png'; //No watermark

// Maximum image width 
  $max_width = (int)$_GET['width'];
// Maximum image height 
  $max_height = "800"; 

  if (file_exists($original_image)) {
  $cached='cache/'.preg_replace('/(\.\w+$)/',".{$max_width}\\1",$original_image);

  if (file_exists($cached)) {
    $cst=stat($cached);
    $fst=stat($original_image);
    if ($fst[9]<=$cst[9] && $fst[10]<=$cst[10]) {
      if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])>=$cst[9]) {
        header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($cached)).' GMT', true, 304);
      } else {
    header('Content-type: image/jpeg');
    header('Last-Modified: '.gmdate('D, d M Y H:i:s',$cst[9]).' GMT');
    header('Cache-Control: private');
    readfile($cached);
      }
      exit;
    }
  }

  if ($max_width > 300) {
    $watermark=$wmark;
    } elseif ($max_width > 152 && $max_width < 300) {
      $watermark=$wmarkm;
    }elseif ($max_width > 50 &&  $max_width < 151){
        $watermark=$wmarks;
    } else {
        $watermark=$wmarkno;
    }

// Resize the image, save and output to browser with headers
  exec("convert -filter Lanczos $original_image -thumbnail {$max_width}x{$max_height} -quality 90 {$watermark} -gravity center -unsharp 2x0.5+0.2+0 -composite {$cached}"); 
  header('Content-type: image/jpeg');
  header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  header('Cache-Control: private');
  readfile($cached);
 }
}
?>

EDIT

Seems that ImageMagick was using excessive resources on my server, as it was doing bulk resizing. I eventually decided to switch to GraphicsMagick instead, which outputs the same image quality I need, with almost the same file size, while working much faster and using far less resources on my server.

To do that, I just installed GraphicsMagick and changed the exec line from:

exec("convert -filter Lanczos $original_image -thumbnail {$max_width}x{$max_height} -quality 90 {$watermark} -gravity center -unsharp 2x0.5+0.2+0 -composite {$cached}"); 

To:

  //create the resized image
  exec("gm convert -filter Lanczos {$original_image} -thumbnail {$max_width}x{$max_height} -quality 90 -unsharp 2x0.5+0.2+0 {$cached}");
  //apply the watermark and recreate the watermarked image, overwriting the previously resized image
  exec("gm composite -quality 90 -dissolve 100 -gravity center {$watermark} {$cached} {$cached}");

EDIT 2

Another way, for anyone who wants or needs to keep working with GD, is to use the following excellent unsharp mask function (taken from http://vikjavev.no/computing/ump.php):

<?php 

/* 

New:  
- In version 2.1 (February 26 2007) Tom Bishop has done some important speed enhancements. 
- 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.1.1   
////   
////    Unsharp mask algorithm by Torstein H?nsi 2003-07.   
////             thoensi_at_netcom_dot_no.   
////               Please leave this notice.   
////   
///////////////////////////////////////////////////////////////////////////////////////////////   



    // $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-1; $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;  

} 
?>

Andere Tipps

PHP 5.5 and above has an undocumented function imagesetinterpolation() that let you change the interpolation method. I only saw it in the source code. Never tried it myself. Your mileage might vary.

The function takes two parameters: the image resource and one of the following constants.

IMG_BELL
IMG_BESSEL
IMG_BILINEAR_FIXED
IMG_BICUBIC
IMG_BICUBIC_FIXED
IMG_BLACKMAN
IMG_BOX
IMG_BSPLINE
IMG_CATMULLROM
IMG_GAUSSIAN
IMG_GENERALIZED_CUBIC
IMG_HERMITE
IMG_HAMMING
IMG_HANNING
IMG_MITCHELL
IMG_POWER
IMG_QUADRATIC
IMG_SINC
IMG_NEAREST_NEIGHBOUR
IMG_WEIGHTED4
IMG_TRIANGLE

IMG_BILINEAR_FIXED is the default.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top