Введение
Поступило задание сделать отчет на СКД с выводом нескольких картинок в одну ячейку. В лоб такая задача не решается. Статей на эту тему достаточно, принцип самого простого варианта обычно такой:
- Программная компоновка отчета.
- Получение картинок из хранилищ значений.
- Сохранение картинок во временные файлы.
- Склейка картинок в одну с помощью стороннего ПО.
- Загрузка полученной картинки из файла в табличный документ.
Чтобы получить склеенную картинку, обычно используют команду magic montage из набора утилит ImageMagic (есть версия COM-объект). Для обеспечения быстродействия и частого использования применяют RAM-диск для работы с файлами, что также позволяет сберечь ресурс SSD.
Казалось бы, дело за малым, тем более опыт такой уже был. Но у заказчика есть особые требования: никаких сторонних программ и внешних компонент, код должен быть на 100% на 1С. Что ж, вызов принят!
Реализация
Идея проста: разбираем картинки (бруски) на пиксели и собираем их на одном большом холсте на низком уровне средствами 1С!
В объекте Картинка нет прямого доступа к пикселям (растру), но зато есть двоичные данные. Через них и будем работать с растром.
Для начала приведем картинки к формату BMP, так как в нем растр хранится в несжатом виде и с бубнами танцевать не придется. Глубину цвета приведем к 24 битам, чтобы формат двоичных данных растра был одинаковым. (К сожалению, 1С не поддерживает 32-битные BMP, так что про альфа-канал придется забыть. Но при острой необходимости можно определить какой-нибудь цвет как прозрачный и не переносить его на холст).
Ну а дальше — дело техники:
- Создаем холст (объект Картинка) нужного размера и цвета.
- Получаем из холста буфер двоичных данных.
- В цикле обрабатываем массив картинок: получаем из них поток в памяти и копируем данные пикселей в буфер холста, руководствуясь описанием формата BMP.
- Получаем холст (объект Картинка) из буфера двоичных данных.
Инструкция
Функция СоединитьКартинки
Функция склеивания расположена в модуле объекта обработки; для удобства может быть размещена в общем серверном модуле. Если нужно склеивать на тонком клиенте, то придется выпилить масштабирование, так как ОбрабатываемаяКартинка будет недоступна (Почему-то. Жалко, что ли? Логично же уменьшать избыточно большие картинки перед отправкой на сервер). Соответственно, в этом случае для нормального отображения размер исходных картинок должен быть близок к нужному и не сильно отличаться между собой.
Первый параметр — обязательный, все остальные имеют значения по умолчанию и могут быть установлены в Неопределено. Для числовых параметров неопределенными считаются значения меньше единицы.
Не передавайте много больших картинок (более 100) при большом размере бруска или без изменения размера, устанете ждать.
Параметр | Тип | Описание |
---|---|---|
Картинки | Массив из Картинка | Коллекция картинок для склеивания. Если размеры картинки меньше размеров бруска (картинки на холсте), то картинка центрируется относительно него. |
ИзменитьРазмер | Булево, Неопределено | Признак использования масштабирования. По умолчанию Истина. Если Ложь, то параметры Ширина и Высота игнорируются, и будут использованы максимальные размеры картинок из коллекции. |
Ширина | Число, Неопределено | Ширина бруска в пикселях. По умолчанию 0. * |
Высота | Число, Неопределено | Высота бруска в пикселях. По умолчанию 0. * |
Плитка | Число, Неопределено | Количество брусков в строке по горизонтали. По умолчанию 0 — все бруски в одну строку. Если 1 — все бруски в одну колонку. |
Бордюр | Число, Неопределено | Ширина бордюра вокруг брусков в пикселях. По умолчанию 0. |
ЦветБордюра | Цвет, Неопределено | Цвет бордюра. По умолчанию Неопределено — прозрачный. |
Фон | Цвет, Картинка, Неопределено | Фон для новой картинки. По умолчанию Неопределено — белый. ** |
Формат | ФорматКартинки, Неопределено | Формат возвращаемой картинки. По умолчанию Неопределено — ФорматКартинки.PNG *** |
* Если оба параметра не заданы, то будут использованы максимальные размеры картинок из коллекции и все картинки будут масштабироваться пропорционально. Если задан один из параметров, то все картинки масштабируются пропорционально, а второй параметр вычисляется как максимальный у масштабированных картинок. Если заданы оба параметра, то картинки масштабируются без учета пропорций.
** Фон используется для создания холста и будет виден при прозрачном бордюре, в пустых брусках и местах, где картинка меньше бруска. Если передать картинку, то она будет масштабирована без сохранения пропорций. Если в параметр Картинки передан пустой массив, то картинка фона отобразится в своем истинном размере и пропорциях.
*** Внутри функции вся обработка происходит в формате BMP, если указать его, конвертирования результата не будет, но следует учесть что такая картинка занимает в разы больше места по сравнению с другими форматами. Глубина цвета всегда 24 бита. Предпочитаемый формат PNG, так как имеет хорошее сжатие и не теряет качество.
Возвращаемое значение: Картинка — размер зависит от размера и количества брусков.
Служебные функции
Функция ПолучитьКартинкиИзХранилищ(МассивХранилищ)
Подготавливает массив картинок для склеивания. Возвращает массив с картинками на основе массива с типом ХранилищеЗначения, содержащими тип Картинка. Если элемент массива Неопределено или ХранилищеЗначения пустое, в результат попадет картинка 1х1 пиксель серого цвета.
Функция Получить1пикс(Цвет = Неопределено)
Возвращает картинку 1х1 пиксель заданного цвета. По умолчанию (для всех значений, отличных от типа Цвет) — белый. Поддерживается любой тип цвета 1С, но ЦветаWindows могут воспроизводиться некорректно. Эту функцию можно использовать для различных целей, например, для раскраски ячейки таблицы значений УФ без использования условного оформления.
Самое интересное
А теперь расскажу, как во время отладки столкнулся с различными проблемами реализации и особенностями платформы, которые повлекли за собой критические проблемы с быстродействием при тестах на больших картинках в количестве более 20. Информация, на мой взгляд, ценная, особенно для любителей покритиковать 1С. Впрочем, возможно, это особенности моей платформы — 8.3.27.1508 для Linux, база файловая, внешняя обработка.
-
Позиция в потоке. При создании ПотокВПамяти на основании буфера позиция в новом потоке установлена в 0. Почти всегда, в 99% случаев. В документации этот момент не отражен. Так что перед работой с таким потоком не надейтесь на позицию 0 и на всякий случай установите ее явно. С потоками, созданными на основании длины, такого не заметил, но кто знает...
-
Получение ДвоичныеДанные из БуферДвоичных. Казалось бы, что проще? Даже есть специальная функция, но делает она это через, простите, временный файл! Исправляем:
//Возврат ПолучитьДвоичныеДанныеИзБуфераДвоичныхДанных(Буфер); Поток = Новый ПотокВПамяти(Буфер); Возврат Поток.ЗакрытьИПолучитьДвоичныеДанные();
-
ОбрабатываемаяКартинка не работает в тонком клиенте. Об этом уже говорил. Но тогда зачем при получении из нее картинки создается временный файл? В ее защиту: делает она это не всегда и в зависимости от размера картинки, возможно чегото еще. Как это исправить? Как ни странно, вот так:
//Картинка = ОбрабатываемаяКартинка.ПолучитьКартинку(); // - здесь создается врем файл //Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(Картинка.ПолучитьДвоичныеДанные()); Буфер = ПолучитьБуферДвоичныхДанныхИзДвоичныхДанных(ОбрабатываемаяКартинка.ПолучитьКартинку().ПолучитьДвоичныеДанные());
Следуя этой логике, чтобы получить картинку, нужно создать новую картинку из двоичных данных от картинки, полученной от другой картинки, и тогда временный файл не нужен. Ну, это в теории; на практике такой спецэффект только с буфером. Короче как получить ДД из буфера "напрямую" вы уже знаете, а там и картинка рядом.
-
Чтение картинки из файла. Казалось бы, даже джун знает, как получить Картинка из файла. И что вы думаете? Для чтения файла нужен... Вы правильно догадались — временный файл! И да, при получении ДвоичныеДанные — та же история. Исправляем:
//Возврат Новый Картинка(Файл.ПолноеИмя); // Картинка ////Возврат Новый ДвоичныеДанные(Файл.ПолноеИмя); // ДД ФайловыйПоток = Новый ФайловыйПоток(Файл.ПолноеИмя, РежимОткрытияФайла.Открыть); Обещание = ФайловыйПоток.РазмерАсинх(); Размер = Ждать Обещание; Поток = Новый ПотокВПамяти(Размер); Обещание = ФайловыйПоток.КопироватьВАсинх(Поток, Размер); Ждать Обещание; Обещание = ФайловыйПоток.ЗакрытьАсинх(); Ждать Обещание; Возврат Новый Картинка(Поток.ЗакрытьИПолучитьДвоичныеДанные()); // Картинка //Возврат Поток.ЗакрытьИПолучитьДвоичныеДанные(); // ДД
Все работает и на порядок быстрее. Если код исполняется на сервере, нужно использовать синхронные методы.
-
Проблемы с прозрачностью. Если преобразуете картинку с прозрачным фоном в BMP, то он станет черным. Что бы этого избежать, не делайте так. Или храните картинки без прозрачных цветов. (Ну если очень хочется и там и тут, замените прозрачный цвет на нужный вам, но с минимальной альфой). При загрузке картинки BMP из файла или ДД установка свойства ПрозрачныйФон не срабатывает. Но если вы загрузили JPEG без использования прозрачного фона... Вроде все нормально, картинка с "монолитным" фоном. Везде, кроме демонического списка при работе по навссылке. Там она ведет себя так, будто у нее прозрачный фон, взятый по цвету правого нижнего пикселя, как говорит СП. Учитывая чистоту цвета после компрессии JPEG, картинка обрастает красивыми артефактами, особенно заметными при выделении строки.
-
ВременноеХранилище и Временные файлы. При помещении больших картинок во временное хранилище (при достижении сумарного объема ~10 Мб) начинается активная работа с временными файлами. При отправке на сервер больших картинок — тоже, причем неважно, ВременноеХранилище или Картинка.
-
Функция ПоказатьЗначение. открывает красивый диалог просмотра картинки с масштабом, предварительно сохранив ее во временный файл и считав оттуда.
Вот, живите теперь с этим. Очень правы те, кто выносит каталог временных файлов на RAM-диск. И на клиентах тоже.
Не, ну что это? Может так работает "Тонкий клиент (файловый вариант)"? Т.е. они это буквально? Знатоки, пишите.
ЗЫ
Отлаживаю обработку, сижу такой думаю: "Может как-нибудь взять исходники ImageMagic и собрать нормальную ВК для работы с графикой, с возможностью работы через потоки..." и при получении ОбрабатываемаяКартинка из какой-то очередной картинки получаю: "Ошибка при выполнении операции ImageMagick: 1cv8c: NoDecodeDelegateForThisImageFormat `TMP' @ error/constitute.c/ReadImage/746".
Т.е. 1С ВСЁ ЭТО ВРЕМЯ ПРЯТАЛИ ImageMagick под капотом?!
Всё, я умываю руки.
Проверено на следующих конфигурациях и релизах:
- 1С:Деньги, релизы 2.0.44.40
Вступайте в нашу телеграмм-группу Инфостарт