Skip to content

Instantly share code, notes, and snippets.

@jaymecd
Forked from stojg/ContentAwareImage.php
Created March 28, 2014 08:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaymecd/9827852 to your computer and use it in GitHub Desktop.
Save jaymecd/9827852 to your computer and use it in GitHub Desktop.
<?php
/**
* ContentAwareImage
*
* @author stig
*/
class ContentAwareImage {
protected static $start_time = 0.0;
public static function start() {
self::$start_time = microtime(true);
}
public static function mark() {
$end_time = (microtime(true) - self::$start_time) * 1000;
return sprintf("%.1fms" ,$end_time);
}
/**
*
* @param Imagick $image
* @return int
*/
protected function area(Imagick $image) {
$size = $image->getImageGeometry();
return $size['height'] * $size['width'];
}
/**
* Calculate the entropy for this image
*
* @param Imagick $image
* @param int $area - the amount of pixels for this image
* @return float
*/
protected function grayscaleEntropy(Imagick $image) {
$area = $this->area($image);
$histogram = $image->getImageHistogram();
$value = 0.0;
for($idx = 0; $idx < count($histogram); $idx++) {
$p = $histogram[$idx]->getColorCount() / $area;
$value = $value + $p * log($p, 2);
}
return -$value;
}
/**
*
* @param type $r
* @param type $g
* @param type $b
* @return type
*/
protected function colorEntropy(Imagick $image) {
$area = $this->area($image);
$histogram = $image->getImageHistogram();
$value = 0.0;
$newHistogram = array();
for($idx = 0; $idx < count($histogram); $idx++) {
$colors = $histogram[$idx]->getColor();
$grey = (($colors['r']*0.299)+($colors['g']*0.587)+($colors['b']*0.114));
if(!isset($result[$grey])) {
$newHistogram[$grey] = $histogram[$idx]->getColorCount();
} else {
$newHistogram[$grey] += $histogram[$idx]->getColorCount();
}
}
foreach($newHistogram as $colorCount) {
$p = $colorCount / $area;
$value = $value + $p * log($p, 2);
}
return -$value;
}
/**
*
* @param Imagick $image
* @param int $targetHeight
* @param int $targetHeight
* @param int $sliceSize
* @return array
*/
protected function getAdaptiveOffset(Imagick $image, $targetWidth, $targetHeight) {
$size = $image->getImageGeometry();
$originalWidth = $scanWidth = $size['width'];
$originalHeight = $scanHeight = $size['height'];
$goalY = $goalX = 0;
$sliceSize = ceil($this->area($image) / (1024 * 2));
$firstImage = $otherImage = null;
while($scanHeight-$goalY > $targetHeight) {
$slizeSize = min(array($scanHeight - $goalY - $targetHeight, $sliceSize));
if(!$firstImage) {
$firstImage = clone($image);
$firstImage->cropImage($originalWidth, $slizeSize, 0, $goalY);
}
if(!$otherImage) {
$otherImage = clone($image);
$otherImage->cropImage($originalWidth, $slizeSize, 0, $scanHeight - $slizeSize);
}
if($this->grayscaleEntropy($firstImage) < $this->grayscaleEntropy($otherImage)) {
$goalY += $slizeSize; $firstImage = null;
} else {
$scanHeight -= $slizeSize; $otherImage = null;
}
}
$firstImage = $otherImage = null;
while($scanWidth-$goalX > $targetWidth) {
$sliceSize = min(array(($scanWidth-$goalX-$targetWidth), $sliceSize));
if(!$firstImage) {
$firstImage = clone($image);
$firstImage->cropImage($sliceSize, $originalHeight, $goalX, 0);
}
if(!$otherImage) {
$otherImage = clone($image);
$otherImage->cropImage($sliceSize, $originalHeight, $scanWidth - $sliceSize, 0);
}
// $firstImage has more entropy, so keep $otherImage
if($this->grayscaleEntropy($firstImage) < $this->grayscaleEntropy($otherImage)) {
$goalX += $sliceSize; $firstImage = null;
} else {
$scanWidth -= $sliceSize; $otherImage = null;
}
}
return array('x' => $goalX, 'y' => $goalY);
}
/**
*
* @param Imagick $original
* @param int $targetWidth
* @param int $targetHeight
* @return array
*/
protected function getOffset(Imagick $original, $targetWidth, $targetHeight) {
$image = clone($original);
// Enhance edges within the image
#$image->edgeimage($radius = 1);
// Turn image into a grayscale
#$image->modulateImage(100, 0, 100);
// Force all pixels below this to be totally black
#$image->blackThresholdImage("#0f0f0f");
// Turn image into a grayscale
$image->modulateImage(100, 0, 100);
// Force all pixels below this to be totally black
$image->modulateImage(100, 0, 100);
#$image->blackThresholdImage("#000000");
$image->whitethresholdimage("#9f9f9f");
$image->edgeimage($radius = 1);
#$original->modulateImage(100, 0, 100);
#$original->blackThresholdImage("#5f5f5f");
#$original->whitethresholdimage("#9f9f9f");
#$original->contrastStretchImage(0,1);
#$original->edgeimage($radius = 0.5);
return $this->getAdaptiveOffset($image, $targetWidth, $targetHeight);
}
/**
*
* @param Imagick $image
* @param int $targetWidth
* @param int $targetHeight
* @return array
*/
protected function getMiddleOffset($image, $targetWidth, $targetHeight) {
$size = $image->getImageGeometry();
$originalWidth = $size['width'];
$originalHeight = $size['height'];
$goalX = (int)(($originalWidth-$targetWidth)/2);
$goalY = (int)(($originalHeight-$targetHeight)/2);
return array('x' => $goalX, 'y' => $goalY);
}
/**
*
* @param Imagick $image
* @param int $targetWidth
* @param int $targetHeight
* @return array
*/
protected function getOutsideCropSize(Imagick $image, $targetWidth, $targetHeight) {
$source = $image->getImageGeometry();
if(($source['width'] / $source['height']) < ($targetWidth / $targetHeight)) {
$scale = $source['width'] / $targetWidth;
} else {
$scale = $source['height'] / $targetHeight;
}
return array('width' => (int) ($source['width'] / $scale), 'height' => (int) ($source['height'] / $scale));
}
/**
*
* @param string $imagePath
* @param int $targetWidth
* @param int $targetHeight
* @return boolean|\Imagick
*/
public function crop($imagePath, $targetWidth, $targetHeight) {
try {
$image = new Imagick($imagePath);
} catch(ImagickException $e) {
return false;
}
// First get the size that we can use to safely trim down the image to
// without cropping any sides
$crop = $this->getOutsideCropSize($image, $targetWidth, $targetHeight);
$image->resizeImage($crop['width'], $crop['height'], Imagick::FILTER_CATROM, 0.5);
// Get the offset for cropping the image further
#$offset = $this->getOffset($image, $targetWidth, $targetHeight);
$offset = $this->getMiddleOffset($image, $targetWidth, $targetHeight);
$image->cropImage($targetWidth, $targetHeight, $offset['x'], $offset['y']);
$image->setImageCompression(imagick::COMPRESSION_JPEG);
$image->setImageCompressionQuality(75);
$image->stripImage();
return $image;
}
}
<?php
class RootURLController extends Controller {
public function index() {
$from = ASSETS_PATH.'/real/';
$to = ASSETS_PATH.'/thumbs/';
$files = glob($from.'/*');
ContentAwareImage::start();
foreach($files as $file) {
$fileInfo = pathinfo($file);
$coolImage = new ContentAwareImage();
$image = $coolImage->crop($file, 100, 100);
if(!$image) { continue; }
$thumbName = $fileInfo['filename']."-cropped.jpg";
$image->writeImage($to.'/'.$thumbName);
echo '<img src="'.ASSETS_DIR.'/thumbs/'.$thumbName.'" />';
}
echo ContentAwareImage::mark();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment