Архитектура модуля для обработки изображений

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 - базовый абстрактны класс для фильтров, от которого наследуются все классы фильтров.
Далее сами абстракции фильтров, созданные с одной целью - принимать и обрабатывать параметры фильтра, проверяя их. К слову сказать, параметры могут быть любыми (от координат и размеров до цветовых параметров), эти фильтры тоже не несут в себе конкретной реализации под конкретную библиотеку, а созданы лишь как бы для описательного характера.

И заключительный слой - непосредственная реализация фильтров под граф. библиотеку. Для каждой граф. библиотеки может быть неплоный набор фильтров, а только тех, которые она может реализовать.

В качестве иллюстрации этого, вот код:
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
из объекта 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 часа)) Еще большее спасибо, если вы выскажете свои соображения.
 

dimagolov

Новичок
Или, с помощью GD мы можем например уменьшать/увеличивать изображения, а с помощью IM будет проходить конвертирование файлов. Так же не будет трудностей, если какая-либо библиотека будет отсутствовать - достаточно лишь поменять Handler в конфигах.
с самого начала у тебя противоречие. ты или отделяешь остальное приложение от знаний о GT/IM/etc или страдаешь фигней. если хочешь сделать таки абстракцию от конкретных либ, то твой класс должен сам (исходя из анализа окружения или конфига) решать, кто будет обрабатывать изображения, а приложение ничего об этом знать не должно
 

Xeon303

Новичок
dimagolov
всё так и есть. Приложение знает только о существовании класса ImageManager и абстрактных фильтров, которые могут дополнительно использоваться.

Вот пример:
PHP:
$imageManager = new ImageManager("/path/to/image");
$filters = array(); // Дополнительные фильтры или фильтр
$filters[0] = new Image_Filter_Color(255, 255, 255);
$imageManager->createThumb($width, $height, $filters);
-~{}~ 23.04.09 00:35:

К слову сказать, давно когда еще изучал C++ там было некое понятие "повышения и понижения типа". Мне нужно как раз это, не знаю как это сделать на PHP только. В общем, мне нужно понизить тип от более общего к его специализации. Это должно делаться Handler-ом.
 

DiMA

php.spb.ru
Команда форума
> Прощу прощения за "болваночность" классов, реализацией классов я займусь потом, когда выясню то, за чем я всё таки начал писать в этой теме.

Откровенной ху-й ты занят. Кому это надо? В твоем "потом" основная суть, а не в ооп-мути.

Когда я пишу много кода с GD, то ограничиваюсь лишь массивчиком-объектом из 3х элементов: ссылка на объект картинки, ширина, высота. Это избавляет от кучи левых переменных.
 

Xeon303

Новичок
DiMA
может быть и тем самым занят) Но я так не считаю. Иначе спрашивается нахрена нам вообще какие-то абстракции? Зачем всякие DB Abstraction Layer? Давайте уж тогда делать системы работающие с одним только MySQL? А вдруг кого-то приспичит использовать PostgreSQL?

Так можно и дальше лететь ликвидировать абстракции - "зачем PHP + Apache?" далее > "Ну и нафига нам C/С++ будем писать на ассемблере!"
 

dimagolov

Новичок
Xeon303, ты сначала разработай набор методов, которые можно было бы реализовать как через GD, так и через IM прозрачно от окружения.
 

AmdY

Пью пиво
Команда форума
Xeon303
и сколько раз ты переводил рабочий проект с одной бд на другую?

ты абстракцию используешь не там
PHP:
$image = new Image(Image :: DRIVER_GD);
$image->load($path);
$image->resize($newHeigth, $newWidth);
$image->save();

class Image {
const DRIVER_GD = 'Image_Driver_Gd';
/** @var Image_Driver_Interface */
private $_driver;
public function __construct($driver) {
$this->_driver = new $driver;
}
public function resize() {
$this->_driver->resize();
}
}
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
вопрос "Зачем всякие DB Abstraction Layer" поднимался многократно, и почти всегда ответ "не нужны и даже вредят".

перевод с MySQL на PostgreSQL я делаю переписывая "слой работы с БД" - т.е. всю папку классов, которые работают с базой, а в них и тексты запросов, и вызовы, и получение результата, и предварительный разбор иногда - т.е. все что, касается БД
 

Xeon303

Новичок
Автор оригинала: AmdY
и сколько раз ты переводил рабочий проект с одной бд на другую?
AmdY
честно сознаюсь, ни разу не переводил. Тут думаю лишь будет сложность с адаптацией дампа под другую СУБД, если в проекте использовались специфические фишки старой БД. В большинстве случаев, думаю, прокатит нормально, если это не какой-нибудь тяжелый случай, либо СУБД различаются слишком сильно.

ты абстракцию используешь не там
У меня примерно так же внутри как у вашего класса Image. Только название не driver, а handler :) Просто у меня сложность с моими фильтрами. Оно работает, но хочется чтобы работало по-другому. С помощью возможностей PHP, либо паттерн какой-то искать, но не так как сейчас.

Вот нейтральный пример того, что я хочу. Он отношения не имеет к моей системе.

PHP:
<?php

function MyHandler(Base $object)
{
	$special = (BaseSpecial) $object;
	$special->process();
}

class Base{
	protected $param;
	
	public $some = 'sdfsdf';
	
	public function __construct($param)
	{
		$this->param = $param;
	}
}

class BaseSpecial extends Base {
	public $special = "special variable";
	
	public function process()
	{
		echo "Processing ".$this->param;	
	}
}

$base = new Base("My options");
MyHandler($base);

?>
Надо "тупо" как-то сконвертировать объект из Base в BaseSpecial. Код этот выкидывает Parse Error естественно, просто он иллюстрирует "то что я хочу". Если это невозможно в PHP, то можно попробовать найти паттерн, который поможет, либо оставить как есть сейчас - благо работает. Но вот так как оно работает - не дас мне заснуть сегодня :)

-~{}~ 23.04.09 02:14:

grigori
хм... а как же тогда CMS работают с поддержкой двух и более БД?
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
вообще, конечно прикол

опытные люди неделями пишут обработку картинок на image magic, плюются на утечки памяти и переходят с либы на вызов коммандной строки с параметрами, а потом отказываются от проектов, лишь увидев упоминание этой бесценной либы в ТЗ ...
ТС, вероятно, предстоит умереть от глобального недосыпа :)

-~{}~ 23.04.09 01:21:

>а как же тогда CMS работают с поддержкой двух и более БД?
а сам не пробовал глянуть код?
не, если хочешь продолжать славное дело пейсателей Joomla - вперед, ток без нас (а топик в мусорку) :)
 

AmdY

Пью пиво
Команда форума
зачем что-то придумывать
отбрось затею с интерфейсом для фильтров, это лишнее, используй простое делегирование.
$this->_driver->doAnything($params);
$this->_driver->doOther($params);
а уже драйвер через интерфейс нужно обязать иметь такие методы, а как уж это будет внутри не важно. не связывай себе руки.
причём можно ещё добавить метод, чтобы заюзать специфические функции драйверов
PHP:
public function __call($method, $args = array()) {
        if (method_exists($this->_driver, $method)) {
            return call_user_func_array(array($this->_driver, $method), $args);
        } else {
            throw new Exception_Warning('call undefined method: '.$method);
        }
    }
 

Xeon303

Новичок
grigori
Да ладно, Image Magick довольно мощная библиотека, если из неё не пытаться сто-этажный небоскреб соорудить. Не знаю чем она там страдает и какие у неё утечки памяти, но и я не собираюсь писать "а ля фотожоп на PHP с поддержкой GD и IM в одном флаконе". Не у меня всё равно более тривиальные задачки.

-~{}~ 23.04.09 02:42:

а сам не пробовал глянуть код?
не, если хочешь продолжать славное дело пейсателей Joomla - вперед, ток без нас (а топик в мусорку) :)
Пробовал. Давно...
Не знаю как всякие "пейсатели", но я прекрасно понимаю что у разных БД разные возможности, особенности, фишки, что угодно. Отсюда и SQL-запросы разные. Поэтому я не стану утверждать что если всё пашет на MySQL, то и на других "что-нибудь получится".

Всё таки, я считаю нельзя быть такими консервативными во всем. Либо, судорожно бояться каких-то оверхедов из-за "абстракции". А то так послушаешь вас, так сразу на ум еще и ORM всплывает. Типа зря старались, да? Правильно, а тех кто вообще это понятие придумал - так всех давно нужно дворниками отправить работать, а то ишь чего напридумывали! А за ними следом и ООП в одно место послать...

Ладно, на сегодня хватит) Пора и честь знать. Завтра вернусь :)
 

cDLEON

Онанист РНРСlub
Я вообще не понимаю нахера в этой схеме менеджер.
Имхо лучше иметь базовый класс, а от него от наследовать расширенный, для работы именно с ИМ....
Зачем пытаться уместить воедино квадратное и круглое мне понять не дано :))
А сколько текста...Бр...
 

Xeon303

Новичок
Alexandre
А что делать если в момент создания не известен нужный подкласс? Т.е. и нужная фабрика тоже не известна? Можно ли как-либо преобразовать объект одного класса в другой, в более специализированный?
 

Alexandre

PHPПенсионер
А что делать если в момент создания не известен нужный подкласс?
что значить неизвестен нужный подкласс?
$image = imageCless->getInstance( 'IM' );
$image = imageCless->getInstance( 'GD' );
или на крайней случай
$image = imageCless->getInstance( $imageTypeLibrary );

$image->load(...);
$image->resize(...);

фабрика как раз и нужна для того, что мы не знаем на момент создания объекта - какой класс будет использоваться
 

Lightning

Трудоголик
вопрос "Зачем всякие DB Abstraction Layer" поднимался многократно, и почти всегда ответ "не нужны и даже вредят".
Интересно. А как вредят?
перевод с MySQL на PostgreSQL я делаю переписывая "слой работы с БД" - т.е. всю папку классов, которые работают с базой, а в них и тексты запросов, и вызовы, и получение результата, и предварительный разбор иногда - т.е. все что, касается БД
Прикольно. Это выходит быстрее, чем переписать один класс прослойки абстрактного доступа к БД?
 

dimagolov

Новичок
Lightning, не бывает абстрактного доступа к БД нигде, кроме разве что визиток на 3 странички. в более сложных случаях SQL идет с заточкой под конкретную СУБД, более того, СУБД выбирается под задачу.
 

Lightning

Трудоголик
dimagolov
Бывает, просто чтобы написать хорошую прослойку нужно очень хорошо знать детали различий между разными СУБД. Вполне реально спроектировать приложение с учетом перехода на более мощную СУБД в дальнейшем.

-~{}~ 23.04.09 17:52:

СУБД выбирается под задачу.
Железо тоже выбирается под задачу, и что?
 

Alexandre

PHPПенсионер
н-у начался оффтоп
вопрос-то про абстрактный слой графического класса
 
Сверху