При разработке отчетов и прайс-листов в 1С на базе системы компоновки данных (СКД) или через ручной вывод в ТабличныйДокумент часто возникает ситуация, когда добавление графических изображений приводит к критическому падению производительности. Отчет может формироваться часами или вызывать аварийное завершение работы клиентского приложения.
Проанализируем причины такого поведения платформы 1С:Предприятие, разберем типичные ошибки в программном коде и рассмотрим эффективные способы оптимизации формирования графических прайс-листов.
Выясним причину, по которой даже небольшой каталог товаров может парализовать работу платформы при выводе изображений:
Рисунки табличного документа представляет собой оверлей над сеткой ячеек. При добавлении сотен графических объектов и вызове метода Расположить() платформа пересчитывает координаты и привязки каждого объекта. Вставка свойства Картинка в цикле порождает колоссальную нагрузку на графический интерфейс.Разберем главное правило: для отображения в табличном документе ячейка имеет фиксированный размер (например, 150х100 точек), поэтому выгружать в нее полноразмерный исходный файл бессмысленно. Исходные файлы должны оставаться в хранилище, а в отчет необходимо передавать миниатюры.
Для генерации превью «на лету» без сторонних библиотек используем объект ОбрабатываемаяКартинка:
// Пример уменьшения исходного изображения до заданных размеров
ДвоичныеДанныеФайла = Выборка.ХранимыйФайл.Получить();
Если ДвоичныеДанныеФайла <> Неопределено Тогда
ИсходнаяКартинка = Новый Картинка(ДвоичныеДанныеФайла);
ОбработкаИзображения = Новый ОбрабатываемаяКартинка(ИсходнаяКартинка);
ОбработкаИзображения.УстановитьРазмер(150, 100, СпособМасштабированияКартинки.Авто);
КартинкаДляОтчета = ОбработкаИзображения.ПолучитьКартинку();
КонецЕсли;
Практическая рекомендация: Чтобы не тратить процессорное время сервера на масштабирование при каждом формировании отчета, сохраняйте миниатюры заранее. Создайте дополнительный реквизит или регистр сведений, куда превью записывается однократно в момент загрузки основного изображения номенклатуры.
Рассмотрим исходный код постобработки отчета в процедуре ПриКомпоновкеРезультата. Вызов функции ПолучитьИзображение внутри цикла по строкам таблицы для каждой позиции порождает отдельный запрос к базе данных:
// Неоптимальный вариант: запрос внутри цикла
Для Ст = 7 По КоличествоСтрокТаблицы Цикл
ТекОбласть = ДокументРезультат.Область(Ст, 9);
// ...
Рисунок = ПолучитьИзображение(СсылкаНаФайл); // Выполняет запрос к БД на каждой итерации!
// ...
КонецЦикла;
Оптимизируем логику: выберем все необходимые изображения одним пакетным запросом заранее перед обходом строк табличного документа или добавим поле с двоичными данными непосредственно в набор данных СКД.
Посмотрим на пример с предварительной выборкой изображений в таблицу соответствия:
// Оптимальный вариант: предварительный сбор ссылок и пакетный запрос
МассивФайлов = Новый Массив;
// Первый проход: собираем только ссылки на файлы
Для Ст = 7 По ДокументРезультат.ВысотаТаблицы Цикл
ТекОбласть = ДокументРезультат.Область(Ст, 9);
Если ТекОбласть.Расшифровка <> Неопределено Тогда
ЭлементРасшифровки = ДанныеРасшифровки.Элементы[ТекОбласть.Расшифровка];
Если ТипЗнч(ЭлементРасшифровки) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПолей") Тогда
Поля = ЭлементРасшифровки.ПолучитьПоля();
Для Каждого Поле Из Поля Цикл
Если Поле.Поле = "ФайлСсылка" И ЗначениеЗаполнено(Поле.Значение) Тогда
МассивФайлов.Добавить(Поле.Значение);
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Получаем все миниатюры одним запросом к базе данных
СоответствиеКартинок = Новый Соответствие;
Если МассивФайлов.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПрисоединенныеФайлы.ПрисоединенныйФайл КАК Ссылка,
| ПрисоединенныеФайлы.ХранимыйФайл КАК ХранимыйФайл
|ИЗ
| РегистрСведений.ПрисоединенныеФайлы КАК ПрисоединенныеФайлы
|ГДЕ
| ПрисоединенныеФайлы.ПрисоединенныйФайл В(&МассивФайлов)";
Запрос.УстановитьПараметр("МассивФайлов", МассивФайлов);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ДвоичныеДанные = Выборка.ХранимыйФайл.Получить();
Если ДвоичныеДанные <> Неопределено Тогда
СоответствиеКартинок.Вставить(Выборка.Ссылка, Новый Картинка(ДвоичныеДанные, Истина));
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Второй проход: быстрая расстановка картинок из памяти
Для Ст = 7 По ДокументРезультат.ВысотаТаблицы Цикл
ТекОбласть = ДокументРезультат.Область(Ст, 9);
Если ТекОбласть.Расшифровка <> Неопределено Тогда
// Получаем ссылку на файл из расшифровки
// ...
КартинкаТовара = СоответствиеКартинок[ТекущаяСсылка];
Если КартинкаТовара <> Неопределено Тогда
Рис = ДокументРезультат.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Картинка);
Рис.РазмерКартинки = РазмерКартинки.Пропорционально;
Рис.Картинка = КартинкаТовара;
Рис.Расположить(ТекОбласть);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Рассмотрим альтернативный вариант, позволяющий избежать ручного управления коллекцией Рисунки. Система компоновки данных умеет самостоятельно выводить графические данные через макеты полей:
Картинка.При таком подходе платформа встраивает изображения непосредственно в процессе компоновки результата, что исключает затраты на повторный полный обход ячеек документа.