Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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…
<?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
You can’t perform that action at this time.