Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image convolution implementation #409

Closed
wants to merge 13 commits into from
12 changes: 12 additions & 0 deletions lib/Imagine/Effects/EffectsInterface.php
Expand Up @@ -12,6 +12,7 @@
namespace Imagine\Effects;

use Imagine\Exception\RuntimeException;
use Imagine\Image\Effects\ConvolutionMatrixInterface;
use Imagine\Image\Palette\Color\ColorInterface;

/**
Expand Down Expand Up @@ -77,4 +78,15 @@ public function sharpen();
* @throws RuntimeException
*/
public function blur($sigma);

/**
* Convolves the image
*
* @param ConvolutionMatrixInterface $matrix The convolution kernel
*
* @return EffectsInterface
*
* @throws RuntimeException
*/
public function convolve(ConvolutionMatrixInterface $matrix);
}
12 changes: 12 additions & 0 deletions lib/Imagine/Gd/Effects.php
Expand Up @@ -13,6 +13,7 @@

use Imagine\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Effects\ConvolutionMatrixInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Image\Palette\Color\RGB as RGBColor;

Expand Down Expand Up @@ -106,4 +107,15 @@ public function blur($sigma = 1)

return $this;
}

/**
* {@inheritdoc}
*/
public function convolve(ConvolutionMatrixInterface $matrix)
{
if (false === imageconvolution($this->resource, $matrix->getMatrix(), 1, 0)) {
throw new RuntimeException('Failed to convolve the image');
}
return $this;
}
}
9 changes: 9 additions & 0 deletions lib/Imagine/Gmagick/Effects.php
Expand Up @@ -13,6 +13,7 @@

use Imagine\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Effects\ConvolutionMatrixInterface;
use Imagine\Image\Palette\Color\ColorInterface;
use Imagine\Exception\NotSupportedException;

Expand Down Expand Up @@ -103,4 +104,12 @@ public function blur($sigma = 1)

return $this;
}

/**
* {@inheritdoc}
*/
public function convolve(ConvolutionMatrixInterface $matrix)
{
throw new NotSupportedException('Gmagick does not support convolve yet');
}
}
113 changes: 113 additions & 0 deletions lib/Imagine/Image/Effects/ConvolutionMatrix.php
@@ -0,0 +1,113 @@
<?php

/*
* This file is part of the Imagine package.
*
* (c) Amri Sannang <amri.sannang@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Imagine\Image\Effects;

use InvalidArgumentException;

/**
* The convolution kernel class
*/
class ConvolutionMatrix implements ConvolutionMatrixInterface
{
/**
* The convolution kernel
*
* @var array|float[]
*/
protected $kernel;

/**
* Constructor
*
* @param float $topLeft
* @param float $topCenter
* @param float $topRight
* @param float $centerLeft
* @param float $center
* @param float $centerRight
* @param float $bottomLeft
* @param float $bottomCenter
* @param float $bottomRight
*/
public function __construct(
$topLeft,
$topCenter,
$topRight,
$centerLeft,
$center,
$centerRight,
$bottomLeft,
$bottomCenter,
$bottomRight
) {
$kernel = func_get_args();
// Test types are all floats
foreach ($kernel as $i => $val) {
if (!is_numeric($val)) {
throw new InvalidArgumentException('All values must be numeric');
}
}
$this->setKernel($kernel);
}

/**
* {@inheritdoc}
*/
public function getDivisor()
{
return array_sum($this->getKernel());
}

/**
* Returns a copy, leaving the original kernel untouched.
*
* {@inheritdoc}
*/
public function normalize()
{
$normalizedMatrix = array();
$divisor = $this->getDivisor();
foreach ($this->getKernel() as $val) {
$normalizedMatrix[] = $val / $divisor;
}
$clone = clone $this;
$clone->setKernel($normalizedMatrix);
return $clone;
}

/**
* @return array|array[]
*/
public function getMatrix()
{
return array_chunk($this->kernel, 3);
}

/**
* @return array|float[]
*/
public function getKernel()
{
return $this->kernel;
}

/**
* @param array|float[] $kernel
*
* @return $this
*/
protected function setKernel($kernel)
{
$this->kernel = $kernel;
return $this;
}
}
41 changes: 41 additions & 0 deletions lib/Imagine/Image/Effects/ConvolutionMatrixInterface.php
@@ -0,0 +1,41 @@
<?php

/*
* This file is part of the Imagine package.
*
* (c) Amri Sannang <amri.sannang@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Imagine\Image\Effects;

/**
* The convolution kernel interface
*/
interface ConvolutionMatrixInterface
{
/**
* Normalizes this convolution kernel. Depending on implementation, may
* return a copy instead of modifying this object.
*
* @return ConvolutionMatrixInterface
*/
public function normalize();

/**
* Returns convolution as a matrix (array of array of floats),
* as used by `imageconvolution()`.
*
* @return array|array[]
*/
public function getMatrix();

/**
* Returns convolution as a kernel, as used by `Imagick::convolveImage`.
*
* @return array|float[]
*/
public function getKernel();
}
14 changes: 14 additions & 0 deletions lib/Imagine/Imagick/Effects.php
Expand Up @@ -13,6 +13,7 @@

use Imagine\Effects\EffectsInterface;
use Imagine\Exception\RuntimeException;
use Imagine\Image\Effects\ConvolutionMatrixInterface;
use Imagine\Image\Palette\Color\ColorInterface;

/**
Expand Down Expand Up @@ -110,4 +111,17 @@ public function blur($sigma = 1)

return $this;
}

/**
* {@inheritdoc}
*/
public function convolve(ConvolutionMatrixInterface $matrix)
{
try {
$this->imagick->convolveImage($matrix->getKernel());
} catch (\ImagickException $e) {
throw new RuntimeException('Failed to convolve the image');
}
return $this;
}
}
Binary file added tests/Imagine/Fixtures/convolution/trans-blur.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion tests/Imagine/Test/Effects/AbstractEffectsTest.php
Expand Up @@ -12,11 +12,13 @@
namespace Imagine\Test\Effects;

use Imagine\Image\Box;
use Imagine\Image\Effects\ConvolutionMatrix;
use Imagine\Image\Point;
use Imagine\Image\ImagineInterface;
use Imagine\Image\Palette\RGB;
use Imagine\Test\ImagineTestCase;

abstract class AbstractEffectsTest extends \PHPUnit_Framework_TestCase
abstract class AbstractEffectsTest extends ImagineTestCase
{

public function testNegate()
Expand Down Expand Up @@ -133,6 +135,24 @@ public function testBlur()
$this->assertNotEquals(255, $pixel->getBlue());
}


public function testConvolution()
{
$imagine = $this->getImagine();
$image = $imagine->open('tests/Imagine/Fixtures/trans.gif');
$matrix = new ConvolutionMatrix(
0, 0.5, 0,
0.5, 1, 0.5,
0, 0.5, 0
);
$image->effects()->convolve($matrix->normalize());

$this->assertImageEquals(
$image,
$imagine->open('tests/Imagine/Fixtures/convolution/trans-blur.gif')
);
}

/**
* @return ImagineInterface
*/
Expand Down
6 changes: 6 additions & 0 deletions tests/Imagine/Test/Gmagick/EffectsTest.php
Expand Up @@ -35,4 +35,10 @@ protected function getImagine()
{
return new Imagine();
}

public function testConvolution()
{
$this->setExpectedException('RuntimeException');
parent::testConvolution();
}
}
50 changes: 50 additions & 0 deletions tests/Imagine/Test/Image/Effects/ConvolutionMatrixTest.php
@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Imagine package.
*
* (c) Amri Sannang <amri.sannang@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Imagine\Test\Image\Effects;

use Imagine\Image\Effects\ConvolutionMatrix;

class ConvolutionMatrixTest extends \PHPUnit_Framework_TestCase
{
public function testNormalize()
{
$matrix = new ConvolutionMatrix(
-1, -1, -1,
-1, 16, -1,
-1, -1, -1
);
$normalizedMatrix = $matrix->normalize();
$this->assertEquals(
array(-.125, -.125, -.125, -.125, 2, -.125, -.125, -.125, -.125),
$normalizedMatrix->getKernel()
);
$this->assertEquals(
array(
array(-.125, -.125, -.125),
array(-.125, 2, -.125),
array(-.125, -.125, -.125)
),
$normalizedMatrix->getMatrix()
);
$this->assertEquals(
array(-1, -1, -1, -1, 16, -1, -1, -1, -1),
$matrix->getKernel()
);
$this->assertEquals(
array(
array(-1, -1, -1),
array(-1, 16, -1),
array(-1, -1, -1)
),
$matrix->getMatrix()
);
}
}