Пример работы jQuery UI + PHP и GD. Нанесение аппликаций на изображение

    Вступление


    Всем привет! Здороваюсь с хабром я в первый, и надеюсь не последний, раз. Не смотря на то, что читаю хабр довольно давно, идея написать что-то полезное появилась совсем недавно, когда на работе я столкнулся с весьма интересной задачей — разработка он-лайн редактора коллажей. Поскольку особого ассортимента инструментов разработки не было, решили делать средствами js+jQuery и php GD. Процесс реализации задуманного оказался весьма интересным, и куча полученных положительных эмоций и новых навыков подтолкнули меня на написание статьи на хабр. В этой статейке я постараюсь рассказать о некоторых интересных моментах, с которыми столкнулся при разработке он-лайн редактора.

    Задача


    По изначальному плану статьи я хотел описать весь процесс разработки, но потом передумал, поскольку статья получилась бы слишком длинной и имела бы много очевидных и итак всем понятных вещей. Поэтому план статьи был переработан, и я решил оставить только самые интересные и важные, как мне кажется, моменты.
    Итого: речь пойдет об использовании jQuery UI в связке с PHP библиотекой GD. В статье я постараюсь, как можно доходчивее, показать и рассказать об использовании таких возможностей jQuery UI, как перетаскивание и ресайз элементов. А также формирование картинки из созданных и обработанных пользователем элементов (картинок).
    Чтобы было более понятней и наглядней думаю будет не плохо сделать рабочий пример(посмотреть можно тут). В примере реализована одна из частей он-лайн редактора, а именно работа с аппликациями, в которой пользователь может наложить на картинку дополнительные элементы, перетаскивать их как угодно и ресайзить, после чего все это «искусство» должно собраться в единую картинку.
    Что-то я много говорю, пора уже и к делу приступить, начнем.

    Решение


    Для нетерпеливых сразу выложу рабочий пример: ссылка
    И исходники: ссылка

    Для начала стоит определиться, что мы будем использовать и где.
    На стороне клиента (в браузере), будем пользоваться, не нуждающийся в представлении, библиотекой jQuery и несколькими ее плагинами, а именно Draggable и Resizable.
    На сервере будем использовать php и библиотеку GD, которая установлена практически на каждом сервере и хостинге, в отличии от более продвинутого аналога ImageMagick.

    Процесс разработки начнем с клиентской части. Нам потребуется создать рабочую область, в которой пользователь сможет таскать и ресайзить картинки. А также необходимо добавить на страницу панель, в которой будет находится несколько вариантов аппликаций.

    Покажу небольшой кусочек html, для лучшего понимания:
    <div class="work_area">
    	<img src="/resources/images/angelina.jpg" width="500" height="600" id="main_img_big" />
    </div>
    <div class="applications_div">
    	<img src="/resources/applications/1.png" width="64" height="64" />
    	<img src="/resources/applications/2.png" width="64" height="64" />
    	<img src="/resources/applications/3.png" width="64" height="64" />
    	<img src="/resources/applications/4.png" width="64" height="64" />
    	<img src="/resources/applications/5.png" width="64" height="64" />
    	<img src="/resources/applications/6.png" width="64" height="64" />
    	<img src="/resources/applications/7.png" width="64" height="64" />
    </div>
    


    Нам необходимо добавлять элементы аппликации на рабочую область, когда пользователь кликнет по приглянувшейся картинке-аппликации. Поскольку количество добавленных элементов ни как нельзя предугадать, добавление нужно сделать динамическим, для этого можно использовать следующий код:

    var num_elem = 0;
    // добавление аппликации в рабочую область
    function addApplication(element){
    	var applicImg = element.clone(); // создаем копию элемента
    	// удаляем аттрибуты размеров картинки
    	applicImg.removeAttr('width');
    	applicImg.removeAttr('height');
    	// добавляем родительский див для аппликации в рабочую область
    	var allElement = '<div class="applic_new_el_div" id="move_applic_'+num_elem+'"><span class="close_applic"></span></div>';
    	$('.work_area').append(allElement);
    	 // добавляем класс для перетаскивания
    	applicImg.addClass('applic_new_el');
    	// задаем место появления в рабочей области
    	$('#move_applic_'+num_elem).css({
    		'top': '0px',
    		'left': '0px'
    	});
    	applicImg.attr('id', 'applic_'+num_elem); 
    	// добавляем элемент
    	$('#move_applic_'+num_elem).append(applicImg);	
    	init_drag(num_elem); // задаем перетаскивание 
    	init_resize(num_elem); // задаем резайз 
    	num_elem ++; // увиличение счетчика для аппликаций
    }
    

    Как вы уже обратили внимание, добавляется не только картинка, но еще и вместе с дивом, в который обернута и span`ом. Span будет выполнять роль «крестика», который будет при необходимости удалять не нужную аппликацию из рабочей области.
    После добавления аппликации, необходимо вызвать плагины, для добавления возможности ресайза и перемещения картиночки.
    Для возможности ресайза будем использовать плагин Resizable, вызовем его с помощью такой функции:
    // ресайз для аппликаций
    function init_resize(num_el){
    	$('#move_applic_'+num_el).resizable({
    		aspectRatio: true, // сохранять пропорции
    		handles:     'ne, nw, se, sw', // имена классов для угловых блоков
    		alsoResize: "#applic_"+num_el // расайзим еще и родительский див - рамку
    	});
    }
    

    Чтобы картинка еще и перемещалась по рабочей области, воспользуемся плагином Draggable, для его вызова напишем вот такую функцию:
    // задаем перетаскивание для апликации
    function init_drag(num_el){
    	$('#move_applic_'+num_el).draggable({
    		cursor: 'move', // вид курсора
    		containment: '.work_area', // ограничение перемещения
    		scroll: false, // автоскроллинг
    		drag: null // событие при перемещении		
    	});
    }
    

    На этом работа с аппликацией закончена. Все что остается сделать на клиентской стороне – это собрать все данные о добавленных аппликациях и отправить на сервер для обработки. Чтобы создать на сервере такую же картинку, как и в браузере, нам потребуется знать путь до каждой картинки-аппликации, размер (ширину и высоту) и положение на странице, относительно рабочей области.
    Чтобы не делать перезагрузку страницы, а также для удобства передачи данных, воспользуемся ajax`ом. Код для сбора данных об аппликациях и отправки не сервер выглядит вот так:
    // создание картинки с наложением аппликации. Запрос на сервер
    function ajaxMakeImage(){
    	// объявляем необходимые массивы
    	var arrayWidth = [];
    	var arrayHeight = [];
    	var arraySrc = [];
    	var arrayTop = [];
    	var arrayLeft = [];
    	var srcImage = $('#main_img_big').attr('src');	
    	var workAreaTop = $('.work_area').offset().top;
    	var workAreaLeft = $('.work_area').offset().left;	
    	var num = 0;
    	$('.applic_new_el_div').each(function(e) {
    		arrayWidth[num] = $(this).width();
    		arrayHeight[num] = $(this).height();
    		arraySrc[num] = $(this).children('.applic_new_el').attr('src');
    		arrayTop[num] = $(this).offset().top;
    		arrayLeft[num] = $(this).offset().left;
    		num++;
    	});	
    	// отправляем данные на сервер
    	$.ajax({
    		type: "POST",
    		url: "/ajax_action.php",
    		data: {
    			'arraySrc': arraySrc, // массив путей для аппликаций
    			'arrayWidth': arrayWidth, // массив длин аппликаций
    			'arrayHeight': arrayHeight,// массив ширины аппликаций
    			'arrayTop': arrayTop, // массив отступов сверху для аппликаций
    			'arrayLeft': arrayLeft, // массив отступов слева для аппликаций
    			'srcImage': srcImage, // ссылка на фотографию(главная картинка)
    			'workAreaTop': workAreaTop, // отступ сверху до рабочей области
    			'workAreaLeft': workAreaLeft, // отступ слева до рабочей области
    		},
    		dataType: "json",
    		success: function(data){
    			if(data.result == 'success'){	
    				// если все прошло успешно
    				// выводим готовую картинку
    				$('#test_show').attr('src', data.imgSrc);	
    				alert('Картинка создана');
    			}else{
    				// error
    				// @todo вывод ошибки
    			}
    		}
    	});
    }
    


    Работа в браузере закончена. Теперь необходимо написать на сервере скрипт, который будет обрабатывать полученные данные и опираясь на них генерировать картинку.
    Как я уже говорил, для работы с картинками будем использовать библиотеку GD.
    Поскольку аппликации могут быть абсолютно любого размера, то нужно каждую полученную аппликацию нужно ресайзить. Напишем для этого небольшую функцию:
    function resizePhotoPNG($source, $path, $height, $width){
    	$rgb = 0xffffff; //цвет заливки фона
    	$size = getimagesize($source);//узнаем размеры исходной картинки
    	$xRatio = $width / $size[0]; //пропорция ширины
    	$yRatio = $height / $size[1]; //пропорция высоты
    	$ratio = min($xRatio, $yRatio);
    	$kRatio = ($xRatio == $ratio); //соотношения ширины к высоте
    	$new_width = $kRatio  ? $width  : floor($size[0] * $ratio); //ширина
    	$new_height = !$kRatio ? $height : floor($size[1] * $ratio); //высота
    	// расхождение с заданными параметрами по ширине
    	$new_left = $kRatio  ? 0 : floor(($width - $new_width) / 2);
    	// расхождение с заданными параметрами по высоте
    	$newTop = !$kRatio ? 0 : floor(($height - $new_height) / 2);
    	//создаем вспомогательное изображение пропорциональное картинке
    	$img = imagecreatetruecolor($width, $height);
    	imagealphablending($img, false); 
    	imagesavealpha($img, true);		
    	$photo = imagecreatefrompng($source); //достаем наш исходник
    	imagecopyresampled($img, $photo, $new_left, $newTop, 0, 0, $new_width, $new_height, $size[0], $size[1]); //копируем на него превью с учетом расхождений
    	imagepng($img, $path); //сохраняем результат
    	// Очищаем память после выполнения скрипта
    	imagedestroy($img);
    	imagedestroy($photo);
    	// вернем путь для картинки
    	return $path;
    }
    

    Теперь, имея функцию ресайза, остается только обработать все аппликации с ее помощью и сохранить во временной папке. После этого необходимо на главную картинку по очереди нанести каждую аппликацию, конечно не забывая их смещать по осям X и Y. Смещения по осям будут равны отступам слева и сверху в браузере, относительно рабочей области.
    Нанесение со смещением можно сделать следующим образом:
    // $arrayApplication – массив путей до уже отресайзеных аппликаций
    // $mainImg – это главная рабочая картинка
    foreach($arrayApplication as $k=>$oneAppl){
    	//Загружаем одну аппликацию и задаем прозрачность
    	$imageFon = imagecreatefrompng($oneAppl);
    	imagealphablending($imageFon, false); 
    	imagesavealpha($imageFon, true);				
    	// совмещаем картинки
    	imagecopy($mainImg, $imageFon, $applX[$k]-$imgX, $applY[$k]-$imgY, 0, 0, imagesx($imageFon), imagesy($imageFon));
    }
    

    На этом обработка картинок закончена, остается только сохранить получившуюся картинку и передать путь до нее в браузер.
    Для сохранения используем следующий код:
    imageJpeg($mainImg, $pathForImg, 100); // сохранение картинки в папку $pathForImg и с качеством 100
    // и не забудем очистить память
    imagedestroy($mainImg);
    

    Теперь отправляем в браузер ответ, в котором передадим путь до созданной картинки:
    $result = array(
    		'result' => 'success', // результат работы скрипта
    		'imgSrc' => $resultSrc // путь до картинке
    	);	
    echo json_encode($result); // кодируем массив в JSON и передаем данные браузеру
    

    На этом наложение аппликаций на картинку закончено.

    Если меня совсем уж не заминусуют, и статья окажется хоть кому-то полезной, я постараюсь написать еще несколько статей по созданию коллажа (загрузка фотографий, наложение фильтров, наложение текста и тд).
    Список полезных ссылок, опираясь на которые я писал статью:
    Обработка изображений и GD
    Плагин jQuery Draggable
    Плагин jQuery Resizable
    Рабочий пример
    Исходники примера

    Спасибо за внимание!
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 13

      +4
      Вау! Мне понравилось.

        +3
        Ребят, а карму-то за что минусовать? Первый пост, я вы без аргументов в минус сразу :( Напишите в чем не прав, чтоб в будущем не допускать ошибок
          +4
          «апликации» не вращаются
            0
            Для вращения элементов в браузере можно воспользоваться плагином JQuery UI Rotatable. Но я не стал описывать вращение элементов, поскольку с этим возникают проблемы в отображении и в высчитывании угла поворота(для передачи на сервер) в браузерах IE 7-8. Статья и так получилась большая и я подумал, что не стоит переполнять ее кодом. Я хочу написать по этому поводу отдельную статью, которая будет продолжением затронутой темы.
          0
          Давно над Джоли так не издевались.
            +1
            У Вас в html все аппликации имеют одинаковый id, что противоречит самой сути идентификаторов. Думаю, Вы просто неуследили.
            А статья хорошая!
              0
              Спасибо за замечание. Да, действительно id у аппликаций, которые выводились на панели изначально, были одинаковые. Исправил.
              Но это не влияло на работу скрипта, поскольку при клике по аппликации на панели, в рабочей области создается новая картинка уже с уникальным id.
              +3
              Было бы круто добавить вращение и текстовые поля (спич баббл).
              Картинки справа мне почему то хочется перетянуть как drag&drop, а они не тягаются.
                0
                // отступ сверху до робочей области // отступ слева до робочей области

                Конечно, это маловажно, но «робочей» мозолит глаза.
                  0
                  Спасибо, поправил
                  +1
                  Для коллажа изображений не хватает возможности Drag'n'Drop изображений с панели элементов, возможности обрезания изображения (довольно непростая задача по поддержке ресайза и кропа изображения, чтобы можно было откропить ресайзенное изображение и отресайзить уже кропнутое, при этом эффект обрезания изображения достигается overflow:hiden у контейнера, а само изображение позиционируется с отрицательными координатами).

                  Также в браузерах есть поддержка эффекта размывания изображения (к сожалению, с помощью JavaScript можно добиться только примерной схожести размывания), чёрно-белые фильтры, управление слоями (изображение на передний/задний фон, на слой выше/ниже), возможность поворота изображения (поворот можно осуществлять только для необрезанных изображений), добавление тени (не поддерживается в IE для повёрнутого изображения); добавление блоков текста с настраиваемым фоном, прозрачностью, WYSIWYG-редактором и ещё много другого.

                  Грамотно-оформленная статья, описывающая хотя бы половину из вышеупомянутого функционала, была бы хорошей. А эта статья, имхо, нечто среднее между пошаговым руководством для начинающих (не хватает подробного описания шагов и возможных опций) и обзором возможностей (мало возможностей затронуто и описано).
                    0
                    Я несколько раз упомянул в статье, что описываю только часть работы с коллажем, а именно «Нанесение аппликаций на изображение».
                    Как Вы и сами верно заметили, создание всего функционала коллажа довольно непростая задача, поэтому я решил разбить описание на несколько статей. А эта статья «пилотная», так сказать, проба пера. Также я хотел узнать на сколько тема интересна и актуальна — имеет ли смысл писать про коллажи дальше. Теперь я вижу, что люди интересуются этой темой, и планирую в ближайшем будущем написать продолжение.

                    возможность поворота изображения (поворот можно осуществлять только для необрезанных изображений)

                    Почему только для не обрезанных? Повороты можно делать на любом этапе.

                    Также в браузерах есть поддержка эффекта размывания изображения (к сожалению, с помощью JavaScript можно добиться только примерной схожести размывания),

                    Можно накладывать любые эффекты, если делать все действия на сервере, отправляя данные о картинке и об операции над ней с помощью аякса. Но этот способ будет работать медленней, чем обработка средствами браузера. Но есть и плюсы — гораздо больше возможностей.
                      0
                      Почему только для не обрезанных? Повороты можно делать на любом этапе.

                      Я не совсем верно выразился. После поворота изображения, обрезать его уже будет если не невозможно, то слишком затруднительно по той причине, что вычисление обрезания повёрнутого изображения — задача нетривиальная. Я вижу способ обрезать только изображения, повёрнутые на 90/180/270/360 градусов. В остальных случаях обрезание невозможно. Но было бы очень интересно посмотреть на реализацию кропа изображения, повёрнутого на угол, отличный от 90/180/270, и сохранения на сервере средствами PHP.

                      Можно накладывать любые эффекты, если делать все действия на сервере, отправляя данные о картинке и об операции над ней с помощью аякса. Но этот способ будет работать медленней, чем обработка средствами браузера.

                      Вышенаписанные эффекты достигаются JavaScript-ом без необходимости задействования сервера в промежуточных этапах, т.к. обработка изображений слишком дорогостоящая операция. На сервере генерируется только конечное изображение коллажа.

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое