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


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

Для формирования изображения был выбран php-класс Imagick.

Весь процесс разбивается на несколько этапов:

Подготовительный этап

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

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

Обработчик на языке JavaScript выглядит примерно так:

var maxRows = false;
     var Form;
    $(document).on('click','button#broshureStart',function () {
            Form = $(this).parents('form');
            var num = 1;
            var wait = BX.showWait('loading');
            var persentsContainer = Form.find('p.persents');

            function work_with_row(num, d){
                var Persents = (parseInt(num))*100 / parseInt(maxRows);
                persentsContainer.find('span.pv').html(Math.round(Persents));
                persentsContainer.find('span.rv').html(parseInt(num));
                $.ajax({
                    type: "POST",
                    url: location.href,
                    data: {num: num, action: 'createProducts',sessid: Form.find('input[name="sessid"]').val() },
                    dataType: "json",
                    success: function (data) {
                        d && d.resolve();
                    },
                    onfailure: function(){
                        d && d.resolve();
                    }
                });
            }

            $.ajax({
                type: "POST",
                url: location.href,
                data: {num: num, action: 'createProducts',sessid: Form.find('input[name="sessid"]').val() },
                dataType: "json"
            }).then(function (data) {
                if(!maxRows && data.CNT!==undefined){
                    maxRows = data.CNT;

                    var deferreds = [];
                    var i=10;

                    persentsContainer.find('span.maxrows').html(maxRows);
                    persentsContainer.show();
                    for(var index = num+1; index<=maxRows; index++){
                        (function (index) {
                            var d = new $.Deferred();
                            window.setTimeout(function() { work_with_row(index,d) }, 1000*index+(i++));
                            deferreds.push(d);
                        })(index);
                    }

                    $.when.apply($, deferreds).done(function () {
                        $('p.persents').html('Обработка завершена');
                        BX.closeWait('loading',wait);
                    });
                }

            }, function (reason) {
                console.debug(reason);
            });

            return false;
        });

Таким образом, будут отправлены запросы для обработки всех товаров в каталоге.

Скрипт обработки ajax-запроса

Для обработки запросов на этой же странице в самом начале добавляем обработчик ajax-запросов:

$POST_RIGHT = $APPLICATION->GetGroupRight("catalog");
// если нет прав - отправим к форме авторизации с сообщением об ошибке
if ($POST_RIGHT == "D")
	$APPLICATION->AuthForm(GetMessage("ACCESS_DENIED"));
$context = Application::getInstance()->getContext();
$request = $context->getRequest();
if ($request->isPost() && $request->get('action') && $POST_RIGHT == "W" && check_bitrix_sessid())
{
	$arResult = array();
	$arPostData = $request->getPostList()->toArray();
    if($request->get('action')=='createProducts'){
        $pageNum = $request->get('num');
        if(intval($pageNum)<1) $pageNum = 1;
        $processor = new \Pai\Broshure\CProcessor($arSettings);
        $processor->setNPage($pageNum);
        $processor->GetIblockElementData();
        $arResult = $processor->ProcessElementData();
        $arResult['CNT'] = $processor->ReturnElementsCnt();

        $arResult['CNT'] = $processor->ReturnElementsCnt();

        if($request->isAjaxRequest()){
            $APPLICATION->RestartBuffer();
            header('Content-type: application/json');
            echo \Bitrix\Main\Web\Json::encode($arResult);
		exit();
    }
    }

Таким образом, при первом запросе к серверу будет получено общее количество элементов инфоблока и можно будет запустить пошаговый обработчик всех товаров.

Создание изображения с информацией о товаре

Сам обработчик товаров представляет собой класс с основными методами. Рассмотрим их:

Выборка товаров из каталога

protected function GetIblockElementItems(
		$arParams = array('filter' => array(), 'select' => false, 'sort' => array('name' => 'asc'),
			'page_params' => false, 'group' => false),
		$life_time = 3600)
	{
		if (!isset($arParams['filter']) || empty($arParams['filter'])) return false;
		$arFilter = $arParams['filter'];
		$arSelect = (isset($arParams['select'])) ? $arParams['select'] : false;
		$arSort = (isset($arParams['sort'])) ? $arParams['sort'] : array('name' => 'asc');
		$pageParams = (isset($arParams['page_params'])) ? $arParams['page_params'] : false;
		$groupParams = (isset($arParams['group'])) ? $arParams['group'] : false;

		$return = false;
		$cache_params = array();
		foreach (array_keys($arParams) as $array_key)
		{
			if (is_array($arParams[$array_key]))
			{
				foreach ($arParams[$array_key] as $key => $value)
				{
					$cache_params[$array_key . '-' . $key] = $value;
				}
			} elseif (is_bool($arParams[$array_key]))
			{
				$cache_params[$array_key] = $arParams[$array_key] ? 1 : 0;
			}
		}
		$cache_id = md5(serialize($cache_params));

		$cache_dir = __CLASS__ . '/' . __FUNCTION__;
		$cache = \Bitrix\Main\Data\Cache::createInstance();
		if ($life_time < 0)
		{
			$cache->clean($cache_id, $cache_dir);
		}

		if ($cache->initCache($life_time, $cache_id, $cache_dir))
		{
			$return = $cache->getVars();
		} elseif ($cache->startDataCache() && \Bitrix\Main\Loader::includeModule('iblock'))
		{
			if(
				is_array($arSelect)
				&& in_array('IBLOCK_ID',$arSelect)
				&& in_array('ID',$arSelect)
			){
				$filterProperties = array();
				$filterFields = array();
				foreach ($arSelect as $select)
				{
					if(strpos($select,'PROPERTY_')!==false){
						$filterProperties[] = str_replace('PROPERTY_','',$select);
					} else {
						$filterFields[] = $select;
					}
				}
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $filterFields);
				while ($arElement = $rsItems->GetNextElement())
				{
					$arFields = $arElement->GetFields();
					if(!empty($filterProperties)){
						foreach ($filterProperties as $arFilterCode){
							if(!isset($arFields['PROPERTIES'])) $arFields['PROPERTIES'] = array();
							$arFields['PROPERTIES'] = array_merge($arFields['PROPERTIES'],$arElement->GetProperties(false,array(
								'CODE'=>$arFilterCode
							)));
							// $arFields['PROPERTIES'][$arFilterCode] = ;
						}
						// $arFields['PROPERTIES'] = $arElement->GetProperties(false,array('CODE'=>$filterProperties));
					}
					$return[] = $arFields;
				}
			} elseif (empty($arSelect)){
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
				while ($arElement = $rsItems->GetNextElement())
				{
					$arFields = $arElement->GetFields();
					$arFields['PROPERTIES'] = $arElement->GetProperties();
					$return[] = $arFields;
				}
			} else {
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
				while ($arElement = $rsItems->GetNext())
				{
					$return[] = $arElement;
				}
			}

			if (!empty($return))
			{
				foreach ($return as $key => $arItem)
				{
					if (!empty($arItem['PROPERTIES']))
					{
						foreach ($arItem['PROPERTIES'] as $pCode => $arProperty)
						{
							$return[$key]['PROPERTIES'][$pCode] = \CIBlockFormatProperties::GetDisplayValue(
								array('ID' => $arItem['ID'], 'NAME' => $arItem['NAME']), $arProperty, '');
						}
					}
				}
			}

			if(isset($pageParams) && !empty($pageParams) && $pageParams['nPageSize']==1 && !empty($return[0])){
				$return = $return[0];
			}

			$return = array(
				'ITEMS'=>$return,
				'MAX_CNT'=>$rsItems->NavPageCount
			);
			$cache->endDataCache($return);
		}

		$this->settings['ELEMENTS_CNT'] = $return['MAX_CNT'];
		return $return['ITEMS'];
	}

Получение информации об одном товаре

protected function GetIblockElementItems(
		$arParams = array('filter' => array(), 'select' => false, 'sort' => array('name' => 'asc'),
			'page_params' => false, 'group' => false),
		$life_time = 3600)
	{
		if (!isset($arParams['filter']) || empty($arParams['filter'])) return false;
		$arFilter = $arParams['filter'];
		$arSelect = (isset($arParams['select'])) ? $arParams['select'] : false;
		$arSort = (isset($arParams['sort'])) ? $arParams['sort'] : array('name' => 'asc');
		$pageParams = (isset($arParams['page_params'])) ? $arParams['page_params'] : false;
		$groupParams = (isset($arParams['group'])) ? $arParams['group'] : false;

		$return = false;
		$cache_params = array();
		foreach (array_keys($arParams) as $array_key)
		{
			if (is_array($arParams[$array_key]))
			{
				foreach ($arParams[$array_key] as $key => $value)
				{
					$cache_params[$array_key . '-' . $key] = $value;
				}
			} elseif (is_bool($arParams[$array_key]))
			{
				$cache_params[$array_key] = $arParams[$array_key] ? 1 : 0;
			}
		}
		$cache_id = md5(serialize($cache_params));

		$cache_dir = __CLASS__ . '/' . __FUNCTION__;
		$cache = \Bitrix\Main\Data\Cache::createInstance();
		if ($life_time < 0)
		{
			$cache->clean($cache_id, $cache_dir);
		}

		if ($cache->initCache($life_time, $cache_id, $cache_dir))
		{
			$return = $cache->getVars();
		} elseif ($cache->startDataCache() && \Bitrix\Main\Loader::includeModule('iblock'))
		{
			if(
				is_array($arSelect)
				&& in_array('IBLOCK_ID',$arSelect)
				&& in_array('ID',$arSelect)
			){
				$filterProperties = array();
				$filterFields = array();
				foreach ($arSelect as $select)
				{
					if(strpos($select,'PROPERTY_')!==false){
						$filterProperties[] = str_replace('PROPERTY_','',$select);
					} else {
						$filterFields[] = $select;
					}
				}
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $filterFields);
				while ($arElement = $rsItems->GetNextElement())
				{
					$arFields = $arElement->GetFields();
					if(!empty($filterProperties)){
						foreach ($filterProperties as $arFilterCode){
							if(!isset($arFields['PROPERTIES'])) $arFields['PROPERTIES'] = array();
							$arFields['PROPERTIES'] = array_merge($arFields['PROPERTIES'],$arElement->GetProperties(false,array(
								'CODE'=>$arFilterCode
							)));
						}
					}
					$return[] = $arFields;
				}
			} elseif (empty($arSelect)){
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
				while ($arElement = $rsItems->GetNextElement())
				{
					$arFields = $arElement->GetFields();
					$arFields['PROPERTIES'] = $arElement->GetProperties();
					$return[] = $arFields;
				}
			} else {
				$rsItems = \CIBlockElement::GetList($arSort, $arFilter, $groupParams, $pageParams, $arSelect);
				while ($arElement = $rsItems->GetNext())
				{
					$return[] = $arElement;
				}
			}

			if (!empty($return))
			{
				foreach ($return as $key => $arItem)
				{
					if (!empty($arItem['PROPERTIES']))
					{
						foreach ($arItem['PROPERTIES'] as $pCode => $arProperty)
						{
							$return[$key]['PROPERTIES'][$pCode] = \CIBlockFormatProperties::GetDisplayValue(
								array('ID' => $arItem['ID'], 'NAME' => $arItem['NAME']), $arProperty, '');
						}
					}
				}
			}

			if(isset($pageParams) && !empty($pageParams) && $pageParams['nPageSize']==1 && !empty($return[0])){
				$return = $return[0];
			}

			$return = array(
				'ITEMS'=>$return,
				'MAX_CNT'=>$rsItems->NavPageCount
			);
			$cache->endDataCache($return);
		}

		$this->settings['ELEMENTS_CNT'] = $return['MAX_CNT'];
		return $return['ITEMS'];
	}

Создание объекта IMagick

protected function ReadImage(){
		try{
			$this->_Imagick = new \Imagick();
			$this->_Imagick->readImage($this->_absPath);
		} catch (Exception $e) {
			\CAdminMessage::ShowMessage($e->getMessage()); die();
		}

	}

Создание результирующего изображения

public function CreateResultImage($width,$height){
		// для начала заменим размеры изображения
		$blur = max(
			$this->_Imagick->getImageWidth() / $width,
			$this->_Imagick->getImageHeight()/$height);
		if($blur!==1){
			$this->_Imagick->resizeImage($width,$height,null,$blur);
		}
	}

Создание миниатюр изображений в других цветах:

public function AddSamplesToMasterPhoto($arImages,$sampleWidth){

		$arThumbs = array();
		$labels = array();
		$this->imgWidthStart = $sampleWidth* .1;
		foreach ($arImages as $arImage)
		{
			$thumb = new \Imagick(Application::getDocumentRoot().$arImage['SRC']);
			$thumb->adaptiveResizeImage($sampleWidth*.9, 0);
			$arThumbs[] = $thumb;

			$labels[] = $this->DrawImageColor($arImage,$sampleWidth*.9);
		}

		$maxHeight = 0;
		$maxLabelHeight = 0;
		foreach ($arThumbs as $num => $imagick)
		{
			$imgHeight = $imagick->getImageHeight();
			$maxHeight = max($maxHeight,$imgHeight);
			$maxLabelHeight = max($maxLabelHeight, $labels[$num]->getImageHeight());
		}

		// местоположение семплов должно быть на треть их высоты от края
		$offsetY = $this->_Imagick->getImageHeight() - $maxHeight*2;
		$labelOffsetY = $offsetY + $maxHeight*1.1;
		$offsetX = $sampleWidth*.1;
		foreach ($arThumbs as $num=>$imagick)
		{
			$this->_Imagick->compositeImage($imagick,\Imagick::COMPOSITE_OVER, $offsetX, $offsetY);
			$this->_Imagick->compositeImage($labels[$num],\Imagick::COMPOSITE_OVER, $offsetX, $labelOffsetY);
			$offsetX += $sampleWidth*1.05;
		}

		$this->imgLastPos = $offsetY;
	}

Функция наложения текста на изображении

protected function DrawText($text,$size,$color,$bg=false,$maxWidth = false){
		$draw = new \ImagickDraw();
		$draw->setFillColor($color);
		$draw->setFontSize($size);
		$metrics = $this->_Imagick->queryFontMetrics($draw, $text);
		$image = new \Imagick();
		if($bg){
			$image->newImage($metrics['textWidth'], $metrics['textHeight'], $bg);
		} else {
			$image->newImage($metrics['textWidth'], $metrics['textHeight'], new \ImagickPixel('transparent'));
		}

		$draw->annotation(0, $metrics['ascender'], $text);
		$image->drawImage($draw);

		if($maxWidth && $metrics['textWidth'] > $maxWidth){
			$image->adaptiveResizeImage($maxWidth,0);
		}

		return $image;
	}

В данной функции кроме непосрдественного вывода текста на изображении, реализован механизм отрисовки фоновой подложки под изображением. Если цвет фона задан - будет выведен он.

Сохранение полученных результатов

public function SaveResultImage($folder,$fileName){
		if(!file_exists(Application::getDocumentRoot().$folder)){
			mkdir(Application::getDocumentRoot().$folder);
		}
		$this->_Imagick->setImageFormat("jpg");
		$this->_Imagick->writeImage(Application::getDocumentRoot().$folder.'/'.$fileName);
	}

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

Количество показов: 1965
01.05.2018




Разработка сайта

Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...

Сопровождение сайта

Вы можете подать заявку на сопровождение вашего сайта на базе 1С-Битрикс. Сопровождение включает в себя: проверка актуальности обновлений сайта, проверка актуальности резервной копии, консультации по сайту. Опишите в заявке, какие еще объемы планируются на сопровождении и на какой срок вы планируете заключить договор на сопровождение - мы подберем подходящий вам бюджет на сопровождение

Работы по сайту

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