Xeon303
Новичок
Архитектура модуля для обработки изображений
Всем доброго времени суток.
Я разрабатываю систему, которая служит своего рода абстрактным слоем, целью которой является обработка изображений, в независимости от используемой библиотеки (будь то ImageMagick, GD или любая другая). Т.е. выбор библиотеки, которая будет обрабатывать изображение, происходит в соответствии с возможностями той или иной библиотеки, конфигурацией, а также с помощью правил, которые могут задаваться внутри системы и с помощью настроек.
Например, на сервере установлено две библиотеки: ImageMagick (IM) и GD. Как известно работа с библиотекой ImageMagick'а может происходить посредством различных API. Самые используемые на данной момент два интерфейса: командная строка, а также PECL-модуль под названием IMagick (либо MagickWand). Собственно по причине такого разнообразия API, я и решил реализовать абстрактный слой. Или, с помощью GD мы можем например уменьшать/увеличивать изображения, а с помощью IM будет проходить конвертирование файлов. Так же не будет трудностей, если какая-либо библиотека будет отсутствовать - достаточно лишь поменять Handler в конфигах.
В целом, я спроектировал основной объекто-ориентированный "костяк" этой системы, где есть несколько групп классов:
1. ImageManager
Этот класс является самым верхнем уровнем в системе - он реализуют самые общие операции над изображениями: от создания превьюшек, заканчивая конвертированием и получением информации о графических файлах. В общем, всё что может понадобиться в приложении.
2. Image_Handler
Эта группа классов, которая реализует обработчики изображений для любой графической библиотеки: будь то IM, GD и т.д. Для IM может быть созданно хоть три обработчика, каждый из которых умеет работать со своим API.
Во главе этих обработчиков, т.е. на высшем уровне иерархии стоит абстрактный класс, определяющий интерфейс и реализующий некоторые базовые операции: задание Фильтров (о них чуть ниже), задание входного файла и файла, куда сохранится результат обработки, и собственно абстрактная функция process, которую реализуют обработчики.
С этими обработчиками имеет дело непосредственно 1-й компонент (Image Manager), именно он определяет какой обработчик использовать, задает параметры каждого обработчика и т.д., кроме того он задает Фильтры, требуемые для произведение того или иного действия.
3. Image_Filters
А это уже самое интересное и самое сложное место в нашей системе. Объясню маленько о принятом мною понятии Фильтр. Фильтр - это по сути любая "атомарная" операция над изображением. Т.е. такая функция, дробление которой на более мелкие части не имеет никакого смысла. Набор фильтров - это полная функциональная база нашей системы. Выстраивая цепочку Фильтров мы полностью совершаем операцию над изображением.
Например, нам нужно обработать загруженное изображение так, чтобы его размер не превышал 600x600 пикселей, а также нужно немного "пошарпить" изображение, после чего обрезать и добавить какую-нибудь рамку. Последовательность фильтров будет следующая:
1. Применяем фильтр Resize
2. Теперь фильтр Sharpen
3. Фильтр Crop
4. И наконец Border
Вот собственно и вся идея моих фильтров. Нужно еще сказать, что фильтр это не только набор параметров, он может также содержать логику, которая может каким-либо образом проверять параметры работы фильтра, а также их как-либо обрабатывать. Внешний интерфейс фильтров должен быть абстрактным, т.е. его нельзя привязывать к конкретной реализации под графическую библиотеку (но это только внешне).
Теперь по поводу реализации этих фильтров. Здесь самое сложно место, с которым я столкнулся. Дело в том, что каждый обработчик может иметь свой набор фильтров, т.к. допустим GD не имеет такой возможности, как Sharpen, а IM имеет. Набор таких фильтров у каждого обработчика можно считать возможностями самой библиотеки. Ясно дело, что у всех они разные...
В итоге я к пришел к следующей архитектуре фильтров:
Image_Filter_Abstract - базовый абстрактны класс для фильтров, от которого наследуются все классы фильтров.
Далее сами абстракции фильтров, созданные с одной целью - принимать и обрабатывать параметры фильтра, проверяя их. К слову сказать, параметры могут быть любыми (от координат и размеров до цветовых параметров), эти фильтры тоже не несут в себе конкретной реализации под конкретную библиотеку, а созданы лишь как бы для описательного характера.
И заключительный слой - непосредственная реализация фильтров под граф. библиотеку. Для каждой граф. библиотеки может быть неплоный набор фильтров, а только тех, которые она может реализовать.
В качестве иллюстрации этого, вот код:
Эти классы пока имеют вид болванок, но мне получилось добиться предъявленных требований к системе: она независима от наличия графических библиотек, а также легко расширяема путем добавления новых фильтров и обработчиков.
Вот, допустим, как выглядели бы в общих чертах классы фильтров под библиотеку GD:
Прощу прощения за "болваночность" классов, реализацией классов я займусь потом, когда выясню то, за чем я всё таки начал писать в этой теме.
Подведу итог:
Камнем преткновения для меня здесь является класс Image_Filter_Abstract с его "фабричным методом". Надо сказать что фабричный метод не является статическим по тому, что необходимо передать собственную инстанцию классу, реализующему фильтр.
Вы скажете, что нужно было наследовать Image_Filter_IMagickShell_Resize от Image_Filter_Resize? Да, я тоже так считаю, но это и есть главная проблема с которой я столкнулся... Всё дело в том, что когда я инстанцирую фильтр, чтобы его задать обработчику, я не знаю какой конкретно обработчик используется... то ли IMagickShell, то ли GD. По сути это должен решать класс ImageManager, но как тогда создать инстанцию класса Image_Filter_Resize, чтобы он превратился в Image_Filter_IMagickShell_Resize? Вот в чем подвох... Вопрос ко всем знатокам ООП в PHP, можно ли сделать это как-то более красиво, чем есть на данный момен?
Вот какую картину вижу я. Мне нужно сделать так:
из объекта Image_Filter_Color нужно сделать как-то дочерний класс Image_Filter_IMagickShell_Color, но как???
Название обработчика не известно до вызова метода createThumb(), поэтому нельзя создавать сразу инстанцию класса Image_Filter_IMagickShell_Color.
Я пока что сделал это так, без наследования, что очень жалко. Когда обработчик получил фильтры, выполняется следующий код:
Если вы знаете как можно переделать структуру классов Фильтров, буду очень благодарен за ваш совет.
Заранее спасибо, тем кто хотя бы просто осилил и прочитал мой пост, который я писал 2 часа)) Еще большее спасибо, если вы выскажете свои соображения.
Всем доброго времени суток.
Я разрабатываю систему, которая служит своего рода абстрактным слоем, целью которой является обработка изображений, в независимости от используемой библиотеки (будь то ImageMagick, GD или любая другая). Т.е. выбор библиотеки, которая будет обрабатывать изображение, происходит в соответствии с возможностями той или иной библиотеки, конфигурацией, а также с помощью правил, которые могут задаваться внутри системы и с помощью настроек.
Например, на сервере установлено две библиотеки: ImageMagick (IM) и GD. Как известно работа с библиотекой ImageMagick'а может происходить посредством различных API. Самые используемые на данной момент два интерфейса: командная строка, а также PECL-модуль под названием IMagick (либо MagickWand). Собственно по причине такого разнообразия API, я и решил реализовать абстрактный слой. Или, с помощью GD мы можем например уменьшать/увеличивать изображения, а с помощью IM будет проходить конвертирование файлов. Так же не будет трудностей, если какая-либо библиотека будет отсутствовать - достаточно лишь поменять Handler в конфигах.
В целом, я спроектировал основной объекто-ориентированный "костяк" этой системы, где есть несколько групп классов:
1. ImageManager
Этот класс является самым верхнем уровнем в системе - он реализуют самые общие операции над изображениями: от создания превьюшек, заканчивая конвертированием и получением информации о графических файлах. В общем, всё что может понадобиться в приложении.
2. Image_Handler
Эта группа классов, которая реализует обработчики изображений для любой графической библиотеки: будь то IM, GD и т.д. Для IM может быть созданно хоть три обработчика, каждый из которых умеет работать со своим API.
Во главе этих обработчиков, т.е. на высшем уровне иерархии стоит абстрактный класс, определяющий интерфейс и реализующий некоторые базовые операции: задание Фильтров (о них чуть ниже), задание входного файла и файла, куда сохранится результат обработки, и собственно абстрактная функция process, которую реализуют обработчики.
С этими обработчиками имеет дело непосредственно 1-й компонент (Image Manager), именно он определяет какой обработчик использовать, задает параметры каждого обработчика и т.д., кроме того он задает Фильтры, требуемые для произведение того или иного действия.
3. Image_Filters
А это уже самое интересное и самое сложное место в нашей системе. Объясню маленько о принятом мною понятии Фильтр. Фильтр - это по сути любая "атомарная" операция над изображением. Т.е. такая функция, дробление которой на более мелкие части не имеет никакого смысла. Набор фильтров - это полная функциональная база нашей системы. Выстраивая цепочку Фильтров мы полностью совершаем операцию над изображением.
Например, нам нужно обработать загруженное изображение так, чтобы его размер не превышал 600x600 пикселей, а также нужно немного "пошарпить" изображение, после чего обрезать и добавить какую-нибудь рамку. Последовательность фильтров будет следующая:
1. Применяем фильтр Resize
2. Теперь фильтр Sharpen
3. Фильтр Crop
4. И наконец Border
Вот собственно и вся идея моих фильтров. Нужно еще сказать, что фильтр это не только набор параметров, он может также содержать логику, которая может каким-либо образом проверять параметры работы фильтра, а также их как-либо обрабатывать. Внешний интерфейс фильтров должен быть абстрактным, т.е. его нельзя привязывать к конкретной реализации под графическую библиотеку (но это только внешне).
Теперь по поводу реализации этих фильтров. Здесь самое сложно место, с которым я столкнулся. Дело в том, что каждый обработчик может иметь свой набор фильтров, т.к. допустим GD не имеет такой возможности, как Sharpen, а IM имеет. Набор таких фильтров у каждого обработчика можно считать возможностями самой библиотеки. Ясно дело, что у всех они разные...
В итоге я к пришел к следующей архитектуре фильтров:
Image_Filter_Abstract - базовый абстрактны класс для фильтров, от которого наследуются все классы фильтров.
Далее сами абстракции фильтров, созданные с одной целью - принимать и обрабатывать параметры фильтра, проверяя их. К слову сказать, параметры могут быть любыми (от координат и размеров до цветовых параметров), эти фильтры тоже не несут в себе конкретной реализации под конкретную библиотеку, а созданы лишь как бы для описательного характера.
И заключительный слой - непосредственная реализация фильтров под граф. библиотеку. Для каждой граф. библиотеки может быть неплоный набор фильтров, а только тех, которые она может реализовать.
В качестве иллюстрации этого, вот код:
PHP:
abstract class Image_Filter_Abstract
{
public function factory($handlerName)
{
$filterName = "Image_Filter_".$handlerName.str_replace("Image_Filter", "", get_class($this));
if(class_exists($filterName)) {
return new $filterName($this);
}
else {
throw new Exception("Filter ".$filterName." not found.");
}
}
}
class Image_Filter_Crop extends Image_Filter_Abstract
{
protected $_geometry = array();
public function __construct($width, $height, $top, $left)
{
$this->_geometry['width'] = $width;
$this->_geometry['height'] = $height;
$this->_geometry['top'] = $top;
$this->_geometry['left'] = $left;
}
}
interface Image_Filter_Interface_IMagickShell
{
public function GetCommandLine();
}
class Image_Filter_IMagickShell_Crop implements Image_Filter_Interface_IMagickShell
{
const command = "test-test";
protected $_filterBase;
public function __construct(Image_Filter_Crop $filter_base)
{
$this->_filterBase = $filter_base;
}
public function GetCommandLine()
{
return self::command;
}
}
class Image_Filter_IMagickShell_Resize implements Image_Filter_Interface_IMagickShell
{
protected $_filterBase;
public function __construct(Image_Filter_Resize $filter_base)
{
$this->_filterBase = $filter_base;
}
public function GetCommandLine()
{
return $this->_filterBase->width."x".$this->_filterBase->height;
}
}
Вот, допустим, как выглядели бы в общих чертах классы фильтров под библиотеку GD:
PHP:
interface Image_Filter_Interface_GD
{
public function process($param);
}
class Image_Filter_GD_Resize implements Image_Filter_Interface_GD
{
public function __construct(Image_Filter_Resize $filter_base)
{
$this->_filterBase = $filter_base;
}
public function process($param)
{
throw new Exception("Method not implemented");
}
}
Подведу итог:
Камнем преткновения для меня здесь является класс Image_Filter_Abstract с его "фабричным методом". Надо сказать что фабричный метод не является статическим по тому, что необходимо передать собственную инстанцию классу, реализующему фильтр.
Вы скажете, что нужно было наследовать Image_Filter_IMagickShell_Resize от Image_Filter_Resize? Да, я тоже так считаю, но это и есть главная проблема с которой я столкнулся... Всё дело в том, что когда я инстанцирую фильтр, чтобы его задать обработчику, я не знаю какой конкретно обработчик используется... то ли IMagickShell, то ли GD. По сути это должен решать класс ImageManager, но как тогда создать инстанцию класса Image_Filter_Resize, чтобы он превратился в Image_Filter_IMagickShell_Resize? Вот в чем подвох... Вопрос ко всем знатокам ООП в PHP, можно ли сделать это как-то более красиво, чем есть на данный момен?
Вот какую картину вижу я. Мне нужно сделать так:
PHP:
$imageManager = new ImageManager("/path/to/image");
$filters = array(); // Дополнительные фильтры или фильтр
$filters[0] = new Image_Filter_Color(255, 255, 255);
$imageManager->createThumb($width, $height, $filters);
...
// Допустим, система определила нужный обработчик и нужно как-то совершить приведение типа
// Что-то похожее на следующее:
$imagickFilter = (Image_Filter_IMagickShell_Color)$filter
// Т.е. объект типа Image_Filter_Color конвертировать в Image_Filter_IMagickShell_Color
Название обработчика не известно до вызова метода createThumb(), поэтому нельзя создавать сразу инстанцию класса Image_Filter_IMagickShell_Color.
Я пока что сделал это так, без наследования, что очень жалко. Когда обработчик получил фильтры, выполняется следующий код:
PHP:
// Переменная $filter - это объект Image_Filter_Color
$imagickFilter = $filter->factory("IMagickShell");
// На выходе получили то что нужно -> Image_Filter_IMagickShell_Color
Заранее спасибо, тем кто хотя бы просто осилил и прочитал мой пост, который я писал 2 часа)) Еще большее спасибо, если вы выскажете свои соображения.
Просто у меня сложность с моими фильтрами. Оно работает, но хочется чтобы работало по-другому. С помощью возможностей PHP, либо паттерн какой-то искать, но не так как сейчас.