[Proposal] Plugin Filter Architecture
As I have been working with the Image library, I have wanted to add additional methods that modified the image resource in a particular way. For purely illustration purposes, say I wanted to modify an image to be half grayscale and half color (HalfAndHalf).
class Image
{
# existing class code
public function halfAndHalf($direction = 'vertical')
{
# method code here
return $this;
}
}
To implement this functionality in the current architecture, I would have to modify the Image Class signature. I think this defeats the “Open and Closed” principle of SOLID (please correct me if I am mistaken).
I could extend the Image Class and add the new halfAndHalf functionality. But to me that gives off a code “smell”, because I would have to extend the class each time I wanted to add a new way to modify an image.
class HalfAndHalfImage extends Image
{
public function halfAndHalf($direction = 'vertical')
{
# method code here
return $this;
}
}
So my proposal is to create a “Plugin Filter Architecture”. In Gimp, there is menu option called “filters”, which apply things like blurs, distortions, etc. So I am calling classes that modify an image in some way, a filter to stay consistent with terminology.
The idea would include creating an ImageInterface with a simple signature that can be generally applied to the Image class. Example methods would be save, filter (more on filter below), and others. The Intervention\Image\Image would implement this interface.
The filter method should be implemented to type-hint for FilterInterface $objects.
In addition, a FilterInterface should be created with at least a single method, applyFilter. This method should be implemented to type-hint for objects that implement the ImageInterface.
So to tie the these two interfaces together see the code below:
interface ImageInterface
{
public function save($path = null, $quality = 90);
public function filter(FilterInterface $filter);
}
class Image implements ImageInterface
{
public function save($path = null, $quality = 90)
{
# method code
}
public function filter(FilterInterface $filter)
{
return $filter->applyFilter($this);
}
}
interface FilterInterface
{
public function applyFilter(ImageInterface $image);
}
class HalfAndHalfFilter implements FilterInterface
{
protected $direction;
public function __construct($direction = “vertical”)
{
$this->direction = $direction;
}
public function applyFilter(ImageInterface $image)
{
# apply filter to incoming image
return $image;
}
}
# to apply the filter to an image
$image = Image::make(‘img/foo.jpg’);
$image->filter(new HalfAndHalfFilter(‘horizontal’));
# plus method chaining one filter to another
$image->filter(new Filter())->filter(new AnotherFilter())->filter(new CropFilter());
# ----- or ------
$filter = new HalfAndHalfFilter(‘vertical’);
# a collection of image objects that need to have the filter applied
foreach ($imageCollection as $num => $img) {
$filter->applyFilter($img)->save(‘filtered_image.jpg’);
}
Some of the benefits of this approach would be:
- Having a growing library of filters that can be used with the Image Class without changing the Image class’s signature.
- It should be simpler to test in isolation as failing filters won’t effect the image class unit tests.
Hopefully, this provides an idea of what I am thinking. If not we can discuss more.