Intervention / image

PHP Image Processing
https://image.intervention.io
MIT License
13.88k stars 1.5k forks source link

[Proposal] Filter Plugin Architecture #34

Closed erj1 closed 10 years ago

erj1 commented 11 years ago

[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:

Hopefully, this provides an idea of what I am thinking. If not we can discuss more.

erj1 commented 11 years ago

This change could potentially break backward compatibility. So maybe for version 2?

olivervogel commented 11 years ago

Great ideas! I would love to see this as part of the library.

I planing for version 2 at the moment. The major change will be Intervention Image will use the Imagine Image Library under the hood for image transformation like Anahkiasen suggested in this thread.

This step will provide the following pros:

I like Imagine as a very robust and feature-rich library, but would rather use the Intervention interface, which is IMO more intuitive and easier to use for quick web-app tasks.

I'm not 100% sure but I think Imagine has a Filter architecture, too.

andrew13 commented 10 years ago

This would be amazing. I was wanting to add a "orient" function so when reading off of exif data it would auto rotate. Using a filter to do the job would be ideal.

My code below in case someone happens to need it:

function orient()
    {
        $img = $this->exif('Orientation');
        if (!empty($img)) {
            switch ($img) {
                case 3:
                    $angle = 180 ;
                    break;
                case 6:
                    $angle = -90;
                    break;
                case 8:
                    $angle = 90;
                    break;
                default:
                    $angle = 0;
                    break;
            }
        }
        $this->rotate($angle);
    }
wells commented 10 years ago

This sounds great! It'll be good to have an unsharp mask/sharpen filter in this library v2 as well. I'll help however I can. Let me know if you need assistance @olivervogel

nivv commented 10 years ago

As you said it would be really great if you could add imagick as an optional driver. GD is supported by most hosts and is easy to install on all operating systems. However imagick has the upper hand when it comes to customizability and speed. Would be a great addition to be able to choose what driver you want to use.

olivervogel commented 10 years ago

Filters as proposed by @erj1 are now part of Intervention Image 2.0

wells commented 10 years ago

@olivervogel Thanks!

maknz commented 10 years ago

@wells have you implemented sharpening with the library? Interested to find out your approach.

olivervogel commented 10 years ago

Sharpen will come in the next days. It's already in the pipeline.

wells commented 10 years ago

@olivervogel You're awesome.

shadowhand commented 9 years ago

In case anyone else ends up here, this is now a built-in command called orientate which was added in version 2.0.2.