将GD图像大小调整器转换为具有双三次锐化功能的等效ImageMagick


Convert GD image resizer to the equivalent ImageMagick with bicubic sharper

我有下面的PHP脚本,它采用现有的jpeg图像,并将其调整为较小的缩略图,同时添加png水印。我遇到的问题是,缩小是用GD库完成的,这使得缩小后的输出拇指不那么锋利(当不需要缩小大小时,即如果原始大小和输出大小相同,则会更锋利)。有人告诉我gd中的调整大小算法不是很好,建议我使用ImageMagick,并带有自适应调整大小选项。我基本上想将脚本转换为使用ImageMagick(带有双三次锐化),而不是GD库:

<?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);
                                                   }
                     }
  }
?>

我自己用ImageMagick修改了它。比GD更锋利、更好的输出。以下是修改后的版本。希望这能帮助其他人。

<?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);
 }
}
?>

编辑

似乎ImageMagick在我的服务器上使用了过多的资源,因为它正在进行大容量调整。我最终决定改用GraphicsMagick,它输出与我需要的图像质量相同的图像,文件大小几乎相同,同时工作速度更快,在服务器上使用的资源更少。

为了做到这一点,我刚刚安装了GraphicsMagick,并将exec行从:更改为

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}"); 

收件人:

  //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}");

编辑2

对于任何想要或需要继续使用GD的人来说,另一种方法是使用以下出色的反锐化掩码功能(取自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;  
} 
?>

PHP 5.5及以上版本有一个未记录的函数imagesetinterpolation(),它允许您更改插值方法。我只在源代码中看到了它。我自己从来没有试过。您的里程数可能会有所不同。

该函数采用两个参数:图像资源和以下常量之一。

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是默认值。