Created
May 31, 2013 21:15
-
-
Save felipecrv/5688064 to your computer and use it in GitHub Desktop.
A PHP Imagick wrapper class that makes image manipulation simpler by making some decisions and by adding new functionality. The `getMainBorderColor()` method is specially interesting. With it you can calculate which color is the most frequent in the lateral borders of an image. I used it to set a background for an area that could possibly be lef…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class SimpleImage { | |
private $img; | |
public function __construct($path) { | |
$path = Filesystem::resolvePath($path); | |
Filesystem::assertExists($path); | |
Filesystem::assertIsFile($path); | |
Filesystem::assertReadable($path); | |
$this->img = new Imagick($path); | |
} | |
/** | |
* Use consciously. | |
* | |
* @return Imagick | |
*/ | |
public function getImagick() { | |
return $this->img; | |
} | |
public function getWidth() { | |
return $this->img->getImageWidth(); | |
} | |
public function getHeight() { | |
return $this->img->getImageHeight(); | |
} | |
public function isVertical() { | |
return $this->getWidth() <= $this->getHeight(); | |
} | |
public function isHorizontal() { | |
return $this->getWidth() >= $this->getHeight(); | |
} | |
public function resize($width, $height) { | |
$filter = Imagick::FILTER_MITCHELL; | |
Enforce::isTrue($this->img->resizeImage($width, $height, $filter, 1)); | |
return $this; | |
} | |
public function crop($width, $height, $left, $top) { | |
Enforce::isTrue($this->img->cropImage($width, $height, $left, $top)); | |
return $this; | |
} | |
public function resizeToWidth($width) { | |
$factor = $width / $this->getWidth(); | |
$height = floor($this->getHeight() * $factor); | |
return $this->resize($width, $height); | |
} | |
public function resizeToBoundingBox($bound_width, $bound_height) { | |
$w = $this->getWidth(); | |
$h = $this->getHeight(); | |
if ($w <= $bound_width && $h <= $bound_height) { | |
return $this; | |
} | |
$factor = min($bound_width / $w, $bound_height / $h); | |
return $this->resize(floor($factor * $w), floor($factor * $h)); | |
} | |
public function cropThumbnailImage($width, $height) { | |
$this->img->cropThumbnailImage($width, $height); | |
return $this; | |
} | |
public function cropCenter($width, $height) { | |
return $this->crop( | |
$width, | |
$height, | |
floor(($this->getWidth() - $width) / 2), | |
floor(($this->getHeight() - $height) / 2)); | |
} | |
/** | |
* @param arr $c A normalized RGB color | |
* @return arr | |
*/ | |
private static function rgbToYuv($c) { | |
$r = $c['r']; | |
$g = $c['g']; | |
$b = $c['b']; | |
return array( | |
/* $y = */ 0.299 * $r + 0.587 * $g + 0.114 * $b, | |
/* $u = */ -0.147 * $r - 0.289 * $g + 0.436 * $b, | |
/* $v = */ 0.615 * $r - 0.515 * $g - 0.100 * $b); | |
} | |
private static function clamp($w) { | |
$v = (int) ($w * 255); | |
if ($v < 0) { | |
$v = 0; | |
} else if ($v > 255) { | |
$v = 255; | |
} | |
return $v; | |
} | |
/** | |
* @param arr $c | |
* @return arr RGB components in [0, 255] | |
*/ | |
private static function yuvToRgb($c) { | |
list($y, $u, $v) = $c; | |
$r = self::clamp($y + 1.13983 * $v); | |
$g = self::clamp($y - 0.39465 * $u - 0.5806 * $v); | |
$b = self::clamp($y + 2.03211 * $u); | |
return array( | |
'r' => $r, | |
'g' => $g, | |
'b' => $b); | |
} | |
private static function yuvColorDistance($c1, $c2) { | |
return sqrt($c1[0] * $c2[0] + $c1[1] * $c2[1] + $c1[2] * $c2[2]); | |
} | |
private static function yuvIsSimilar($c1, $c2) { | |
// threshold distance: 0.1 | |
return $c1[0] * $c2[0] + $c1[1] * $c2[1] + $c1[2] * $c2[2] < 0.01; | |
} | |
private static function rgbColorToHex($c) { | |
$r_hex = dechex($c['r']); | |
$g_hex = dechex($c['g']); | |
$b_hex = dechex($c['b']); | |
$c_str = | |
($c['r'] < 16 ? '0'.$r_hex : $r_hex). | |
($c['g'] < 16 ? '0'.$g_hex : $g_hex). | |
($c['b'] < 16 ? '0'.$b_hex : $b_hex); | |
return $c_str; | |
} | |
public function getMainBorderColor() { | |
$img = clone $this->img; | |
$img->adaptiveResizeImage(0, 40); | |
$pixel_colors = array(); // YUV color for each pixel | |
$first_col_it = | |
$img->getPixelRegionIterator(0, 0, 1, $img->getImageWidth()); | |
foreach ($first_col_it as $col) { | |
foreach ($col as $pixel) { | |
$pixel_colors[] = self::rgbToYuv($pixel->getColor(true)); | |
} | |
} | |
$first_col_it->destroy(); | |
$last_col_it = $img->getPixelRegionIterator( | |
$img->getImageWidth() - 1, 0, 1, $img->getImageWidth()); | |
foreach ($last_col_it as $col) { | |
foreach ($col as $pixel) { | |
$pixel_colors[] = self::rgbToYuv($pixel->getColor(true)); | |
} | |
} | |
$last_col_it->destroy(); | |
$img->destroy(); | |
$color_groups = array(array($pixel_colors[0])); | |
for ($i = 1; $i < count($pixel_colors); $i++) { | |
$color = $pixel_colors[$i]; | |
foreach ($color_groups as &$group) { | |
foreach ($group as $color_in_group) { | |
if (self::yuvIsSimilar($color, $color_in_group)) { | |
$group[] = $color; | |
break 2; | |
} | |
} | |
} | |
$color_groups[] = array($color); | |
} | |
$max_group_size = 1; | |
$max_group = $color_groups[0]; | |
foreach ($color_groups as $g) { | |
$g_size = count($g); | |
if ($g_size > $max_group_size) { | |
$max_group_size = $g_size; | |
$max_group = $g; | |
} | |
} | |
$mean = array(0.0, 0.0, 0.0); | |
foreach ($max_group as $c) { | |
$mean[0] += $c[0]; | |
$mean[1] += $c[1]; | |
$mean[2] += $c[2]; | |
} | |
$mean[0] /= $max_group_size; | |
$mean[1] /= $max_group_size; | |
$mean[2] /= $max_group_size; | |
return self::rgbColorToHex(self::yuvToRgb($mean)); | |
} | |
public function save($path, $format = null) { | |
$path = Filesystem::resolvePath($path); | |
$dir = dirname($path); | |
Filesystem::assertExists($dir); | |
Filesystem::assertIsDirectory($dir); | |
if ($format) { | |
$this->img->setImageFormat($format); | |
} | |
$this->img->writeImage($path); | |
return $this; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment