Development

#860 (unsharp mask for sfThumbnail)

You must first sign up to be able to contribute.

Ticket #860 (new enhancement)

Opened 2 years ago

Last modified 1 year ago

unsharp mask for sfThumbnail

Reported by: davidfurfero Assigned to: fabien
Priority: major Milestone: plugins
Component: sfThumbnailPlugin Version: 0.7.X
Keywords: thumbnail unsharp mask sharpen Cc:
Qualification: Unreviewed

Description

I modified some code I found to add sharpening to sfThumbnail. There may be better code out there and this probably lacks the necessary parameter validation and error checking, but it works well for me!

Originally found at: http://vikjavev.no/computing/ump.php?id=35

  /**
   * Unsharp Mask for PHP
   * Unsharp mask algorithm by Torstein Hønsi
   * Please leave this notice.
   * @author     Torstein Hønsi <thoensi@netcom.no>
   * @author     Modified for use with Symfony by Dave Furfero <furf@furf.com>
   * @version    2.0, 2003-06
   *
   * 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. 
   *
   * Amount 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. 
   * 
   * @param int amount (0-500)
   * @param float radius (0-50)
   * @param int threshold (0-255)
   * @return void
   * @access public
   */
  public function unsharpMask ($amount, $radius, $threshold)
  {
    // Attempt to calibrate the parameters to Photoshop:
    $amount    = min($amount, 500) * 0.016;
    $radius    = min($radius, 50) * 2;
    $radius    = abs(round($radius)); // Only integers make sense
    $threshold = min($threshold, 255);

    if ($amount == 0 || $radius == 0) {
      return;
    }

    $w = $this->getThumbWidth();
    $h = $this->getThumbHeight();

    $imgCanvas  = imagecreatetruecolor($w, $h);
    $imgCanvas2 = imagecreatetruecolor($w, $h);
    $imgBlur    = imagecreatetruecolor($w, $h);
    $imgBlur2   = imagecreatetruecolor($w, $h);

    imagecopy($imgCanvas,  $this->thumb, 0, 0, 0, 0, $w, $h);
    imagecopy($imgCanvas2, $this->thumb, 0, 0, 0, 0, $w, $h);

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

    imagecopy($imgBlur, $imgCanvas, 0, 0, 0, 0, $w, $h); // background

    for ($i = 0; $i < $radius; $i++)  {

      if (function_exists('imageconvolution')) { // PHP >= 5.1
        $matrix = array(
          array(1, 2, 1),
          array(2, 4, 2),
          array(1, 2, 1)
        );
        imageconvolution($imgCanvas, $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.      
        imagecopy($imgBlur, $imgCanvas, 0, 0, 1, 1, $w - 1, $h - 1);
        imagecopymerge($imgBlur, $imgCanvas, 1, 1, 0, 0, $w, $h, 50);
        imagecopymerge($imgBlur, $imgCanvas, 0, 1, 1, 0, $w - 1, $h, 33.33333); 
        imagecopymerge($imgBlur, $imgCanvas, 1, 0, 0, 1, $w, $h - 1, 25);
        imagecopymerge($imgBlur, $imgCanvas, 0, 0, 1, 0, $w - 1, $h, 33.33333); 
        imagecopymerge($imgBlur, $imgCanvas, 1, 0, 0, 0, $w, $h, 25);
        imagecopymerge($imgBlur, $imgCanvas, 0, 0, 0, 1, $w, $h - 1, 20 );
        imagecopymerge($imgBlur, $imgCanvas, 0, 1, 0, 0, $w, $h, 16.666667);
        imagecopymerge($imgBlur, $imgCanvas, 0, 0, 0, 0, $w, $h, 50);
        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);
      }
    }

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

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

        $rgbBlur = imageColorAt($imgCanvas, $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($this->thumb, $rNew, $gNew, $bNew);
          imageSetPixel($this->thumb, $x, $y, $pixCol);
        }
      }
    }
  }

Change History

08/31/06 00:12:44 changed by davidfurfero

I suppose I should mention that to use it, you simply add it to the sfThumbnail class.

11/16/06 14:24:29 changed by fabien

  • milestone changed from post-1.0 to plugins.

02/21/07 14:57:35 changed by francois

  • owner set to fabien.
  • component set to sfThumbnailPlugin.