First stab at a Center of Edginess smart crop.
authorMichael J. Rubinsky <mrubinsk@horde.org>
Mon, 27 Sep 2010 16:01:10 +0000 (12:01 -0400)
committerMichael J. Rubinsky <mrubinsk@horde.org>
Mon, 27 Sep 2010 16:02:40 +0000 (12:02 -0400)
Currently not working, but the basic algorithm is there, and still needs
further porting to the Im driver.

framework/Image/lib/Horde/Image/Effect/Imagick/SmartCrop.php [new file with mode: 0644]
framework/Image/package.xml

diff --git a/framework/Image/lib/Horde/Image/Effect/Imagick/SmartCrop.php b/framework/Image/lib/Horde/Image/Effect/Imagick/SmartCrop.php
new file mode 100644 (file)
index 0000000..5eea966
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Image effect for determining the best crop based on the center of edginess.
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * Based on ideas and code by Jue Wang <jue@jueseph.com>
+ * http://jueseph.com/2010/06/opticrop-usage-and-implementation/
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael J. Rubinsky <mrubinsk@horde.org>
+ * @package Horde_Image
+ */
+class Horde_Image_Effect_Imagick_SmartCrop extends Horde_Image_Effect
+{
+    /**
+     * Valid parameters:
+     *  <pre>
+     *    width    - Target width
+     *    height   - Target height
+     * </pre>
+     *
+     * @var array
+     */
+    protected $_params = array();
+
+    public function apply()
+    {
+        $this->_params = new Horde_Support_Array($this->_params);
+       
+        // Existing geometry
+        $geometry = $this->_image->getDimensions();
+        $w0 = $geometry['width'];
+        $h0 = $geometry['height'];
+
+        // @TODO: Parameterize these
+        $r = 1;         // radius of edge filter
+        $nk = 9;        // scale count: number of crop sizes to try
+        $gamma = 0.2;   // edge normalization parameter -- see documentation
+        
+        // Target AR
+        $ar = $this->_params->width / $this->_params->height;
+
+        // Existing AR
+        $ar0 = $w0/$h0;
+
+        // Compute COE
+        $img = $this->_image->imagick->clone();
+        $img->edgeImage($r);
+        $img->modulateImage(100,0,100);
+        $img->blackThresholdImage("#0f0f0f");
+
+        // Get a 1x1 iterator (only way to get a single pixel's info without
+        // iterating the entire row.
+        $xcenter = $ycenter = $sum = 0;
+        $n = 100000;
+        for ($k = 0; $k < $n; $k++) {
+            $i = mt_rand(0, $w0 - 1);
+            $j = mt_rand(0, $h0 - 1);
+            // A single pixel iterator!
+            $itr = $img->getPixelRegionIterator($i, $j, 1, 1);
+            foreach ($itr as $row => $pixels) {
+                foreach ($pixels as $col => $pixel) {
+                    $val = $pixel->getColor();
+                }
+            }
+
+            $sum += $val;
+            $xcenter += ($i + 1) * $val;
+            $ycenter += ($j + 1) * $val;
+        }
+
+        $xcenter /= $sum;
+        $ycenter /= $sum;
+
+        // crop source img to target AR
+        if ($w0/$h0 > $ar) {
+            // source AR wider than target
+            // crop width to target AR
+            $wcrop0 = round($ar * $h0);
+            $hcrop0 = $h0;
+        } else {
+            // crop height to target AR
+            $wcrop0 = $w0;
+            $hcrop0 = round($w0 / $ar);
+        }
+
+        // crop parameters for all scales and translations
+        $params = array();
+
+        // crop at different scales
+        $hgap = $hcrop0 - $h;
+        $hinc = ($nk == 1) ? 0 : $hgap / ($nk - 1);
+        $wgap = $wcrop0 - $w;
+        $winc = ($nk == 1) ? 0 : $wgap / ($nk - 1);
+
+        // find window with highest normalized edginess
+        $n = 10000;
+        $maxbetanorm = 0;
+        $maxfile = '';
+        $maxparam = array('w' => 0,
+                          'h' => 0,
+                          'x' => 0,
+                          'y' => 0);
+
+        for ($k = 0; $k < $nk; $k++) {
+            $hcrop = round($hcrop0 - $k * $hinc);
+            $wcrop = round($wcrop0 - $k * $winc);
+            $xcrop = $xcenter - $wcrop / 2;
+            $ycrop = $ycenter - $hcrop / 2;
+            if ($xcrop < 0) {
+                $xcrop = 0;
+            }
+            if ($xcrop + $wcrop > $w0) {
+                $xcrop = $w0 - $wcrop;
+            }
+            if ($ycrop < 0) {
+                $ycrop = 0;
+            }
+            if ($ycrop+$hcrop > $h0) {
+                $ycrop = $h0 - $hcrop;
+            }
+
+            $beta = 0;
+            for ($c = 0; $c < $n; $c++) {
+                $i = mt_rand(0, $wcrop - 1);
+                $j = mt_rand(0, $hcrop - 1);
+                $itr = $img->getPixelRegionIterator($xcrop + $i, $ycrop + $j, 1, 1);
+                foreach ($itr as $row => $pixels) {
+                    foreach ($pixels as $col => $pixel) {
+                        $val = $pixel->getColor();
+                    }
+                }
+                $beta += $val & 0xFF;
+            }
+
+            $area = $wcrop * $hcrop;
+            $betanorm = $beta / ($n * pow($area, $gamma - 1));
+
+            // best image found, save the params
+            if ($betanorm > $maxbetanorm) {
+                $maxbetanorm = $betanorm;
+                $maxparam['w'] = $wcrop;
+                $maxparam['h'] = $hcrop;
+                $maxparam['x'] = $xcrop;
+                $maxparam['y'] = $ycrop;
+            }
+        }
+
+        // Crop to best
+        $this->_image->imagick->cropImage($maxparam['w'],
+                                          $maxparam['h'],
+                                          $maxparam['x'],
+                                          $maxparam['y']);
+        $this->_image->imagick->scaleImage($w, $h);
+        $img->destroy();
+    }
+
+}
\ No newline at end of file
index c890172..99ca97d 100644 (file)
@@ -70,6 +70,7 @@ Initial Horde 4 package
         <file name="TextWatermark.php" role="php" />
         <file name="Unsharpmask.php" role="php" />
         <file name="LiquidResize.php" role="php" />
+        <file name="SmartCrop.php" role="php" />
        </dir> <!-- /lib/Horde/Image/Effect/Imagick -->
        <file name="Border.php" role="php" />
       </dir> <!-- /lib/Horde/Image/Effect -->
@@ -174,6 +175,7 @@ Initial Horde 4 package
    <install as="Horde/Image/Effect/Imagick/TextWatermark.php" name="lib/Horde/Image/Effect/Imagick/TextWatermark.php" />
    <install as="Horde/Image/Effect/Imagick/Unsharpmask.php" name="lib/Horde/Image/Effect/Imagick/Unsharpmask.php" />
    <install as="Horde/Image/Effect/Imagick/LiquidResize.php" name="lib/Horde/Image/Effect/Imagick/LiquidResize.php" />
+   <isntall as="Horde/Image/Effect/Imagick/SmartCrop.php" name="lib/Horde/Image/Effect/Imagick/SmartCrop.php" />
    <install as="Horde/Image/Exif/Base.php" name="lib/Horde/Image/Exif/Base.php" />
    <install as="Horde/Image/Exif/Bundled.php" name="lib/Horde/Image/Exif/Bundled.php" />
    <install as="Horde/Image/Exif/Exiftool.php" name="lib/Horde/Image/Exif/Exiftool.php" />