Извлекаем изображение из реквизита типа ХранилищеЗначения: форма, печать, файл

· 9 мин

Содержание

Картинку из хранилища значений 1С получают одной строкой на сервере: Значение = Хранилище.Получить(). Дальше всё зависит от того, что лежит внутри: объект Картинка или ДвоичныеДанные. Для формы результат кладут во временное хранилище и показывают по адресу, для печати присваивают рисунку табличного документа, для выгрузки пишут в файл.

Ниже рабочие примеры кода для 1С 8.3 под каждый случай: форма, макет, СКД, файл на диске. Отдельно разобраны ошибки, из-за которых Получить() возвращает не то, что ждёшь, и приёмы, которые не дают картинкам тормозить список.

Как 1С хранит изображение в реквизите ХранилищеЗначения

ХранилищеЗначения — это контейнер. В базе он лежит как сериализованный набор байт, а внутрь можно положить почти любое значение: картинку, двоичные данные, структуру, таблицу значений. Платформа не знает, что внутри, пока вы не вызовете Получить().

Что важно для работы с графикой:

  • 📦 Тип содержимого определяет тот, кто записывал. Один разработчик пишет Новый ХранилищеЗначения(Картинка), другой — Новый ХранилищеЗначения(ДвоичныеДанные). Читать надо с проверкой типа.
  • 🗜️ Сжатие задаётся при записи. Второй параметр конструктора — Новый СжатиеДанных(9). Для JPEG и PNG выигрыш минимальный: эти форматы уже сжаты.
  • 🔒 В запросе поле нельзя сравнивать, группировать и сортировать. Выбрать его можно, отбор по нему — нет.
  • 🧠 Значение поднимается в память целиком. Картинка на 5 МБ — это 5 МБ на сервере в каждом вызове.

Запись выглядит так:

// Сервер: сохраняем файл картинки в реквизит
ДвоичныеДанные = Новый ДвоичныеДанные(ПолноеИмяФайла);
Картинка = Новый Картинка(ДвоичныеДанные);
ТоварОбъект.ФотоТовара = Новый ХранилищеЗначения(Картинка, Новый СжатиеДанных(9));
ТоварОбъект.Записать();

Заведите правило в команде: в хранилище кладём только ДвоичныеДанные. Объект Картинка из них собирается одной строкой, а двоичные данные проще выгрузить в файл, посчитать хеш или передать через HTTP.

Код получения картинки из хранилища на встроенном языке

Базовое чтение с проверкой типа

Универсальная функция, которая вернёт Картинка независимо от того, как значение записали:

&НаСервереБезКонтекста
Функция КартинкаИзХранилища(Хранилище)

    Если ТипЗнч(Хранилище) <> Тип("ХранилищеЗначения") Тогда
        Возврат Неопределено;
    КонецЕсли;

    Значение = Хранилище.Получить();

    Если ТипЗнч(Значение) = Тип("Картинка") Тогда
        Возврат Значение;
    ИначеЕсли ТипЗнч(Значение) = Тип("ДвоичныеДанные") Тогда
        Возврат Новый Картинка(Значение);
    КонецЕсли;

    Возврат Неопределено; // пусто или внутри не изображение

КонецФункции

Пустой реквизит возвращает Неопределено. Если не проверить, следующая строка Новый Картинка(Значение) упадёт с исключением.

Чтение одного реквизита без загрузки объекта

Обращение Ссылка.ФотоТовара через точку читает данные объекта. Для одного поля надёжнее запрос:

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Номенклатура.ФотоТовара КАК Фото
|ИЗ
|    Справочник.Номенклатура КАК Номенклатура
|ГДЕ
|    Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаТовар);

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Картинка = КартинкаИзХранилища(Выборка.Фото);
КонецЕсли;

В конфигурациях на БСП то же самое делает ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "ФотоТовара") — вернёт ХранилищеЗначения, дальше вызываете Получить().

Показ на управляемой форме

Поле картинки на форме не умеет показывать ХранилищеЗначения напрямую. Ему нужна строка: адрес во временном хранилище или навигационная ссылка.

  1. Добавьте реквизит формы АдресФото с типом Строка.
  2. Перетащите его в элементы формы и задайте вид элемента «Поле картинки».
  3. Заполните адрес на сервере при создании формы:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    Хранилище = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Объект.Ссылка, "ФотоТовара");
    Картинка = КартинкаИзХранилища(Хранилище);
    Если Картинка <> Неопределено Тогда
        АдресФото = ПоместитьВоВременноеХранилище(Картинка, УникальныйИдентификатор);
    КонецЕсли;

КонецПроцедуры

Второй параметр УникальныйИдентификатор привязывает данные к форме: при закрытии формы платформа освободит память сама.

Второй способ — навигационная ссылка на реквизит. Он не гоняет байты через временное хранилище:

АдресФото = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "ФотоТовара");

Навигационная ссылка показывает то, что записано в базе. Если пользователь выбрал новую картинку, но ещё не записал объект, показывайте её через временное хранилище, иначе на форме останется старое фото.

Выгрузка картинки в файл

На сервере достаточно двух строк:

ДД = Картинка.ПолучитьДвоичныеДанные();
ДД.Записать(КаталогВременныхФайлов() + "foto.png");

Чтобы отдать файл пользователю на клиент, положите ДвоичныеДанные во временное хранилище и вызовите на клиенте ФайловаяСистемаКлиент.СохранитьФайл(...) из БСП, передав адрес и имя файла.

Расширение подберите по формату, иначе JPEG сохранится как .png и откроется не везде:

Формат = Картинка.Формат();
Если Формат = ФорматКартинки.JPEG Тогда
    Расширение = "jpg";
ИначеЕсли Формат = ФорматКартинки.PNG Тогда
    Расширение = "png";
ИначеЕсли Формат = ФорматКартинки.BMP Тогда
    Расширение = "bmp";
Иначе
    Расширение = "bin";
КонецЕсли;

Картинки в макетах печатных форм и в СКД

Рисунок в табличном документе

Типичный сценарий: логотип организации или фото товара в печатной форме. В макете заранее рисуют прямоугольник-рисунок и задают ему имя, например Логотип. Код подставляет в него картинку:

Макет = ПолучитьМакет("ПФ_MXL_Карточка");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");

Картинка = КартинкаИзХранилища(Выборка.ФотоТовара);
Если Картинка <> Неопределено Тогда
    ОбластьШапка.Рисунки.Логотип.Картинка = Картинка;
КонецЕсли;

ТабДок.Вывести(ОбластьШапка);

Рисунок должен лежать внутри области, которую вы получаете. Если он выходит за границы Шапка, в коллекции Рисунки области его не будет, и обращение по имени даст ошибку.

💡 Размер и пропорции задаются свойствами рисунка в макете. Растягивание включается свойством размера картинки рисунка, а не пересчётом в коде.

Картинка из макета типа «Двоичные данные»

Если изображение хранится не в базе, а в самой конфигурации, его кладут в макет с типом «Двоичные данные». Получение:

ДД = ПолучитьМакет("ЛоготипПоУмолчанию");
Картинка = Новый Картинка(ДД);

Для иконок интерфейса уместнее общая картинка: БиблиотекаКартинок.ИмяКартинки. Хранилище значений здесь не нужно вовсе.

Вывод изображений через СКД

Поле типа ХранилищеЗначения из запроса СКД картинкой не нарисует. Рабочие варианты:

СпособКак сделатьКогда подходит
Набор данных «Объект»Соберите таблицу значений, в колонку с типом Картинка положите результат КартинкаИзХранилища(), передайте как внешний наборОтчёт на десятки строк с фото
Дорисовка после выводаСформируйте табличный документ через СКД, затем добавьте рисунки кодомНужна точная раскладка
Отдельная печатная формаТабличный документ из макета, без СКДКарточка товара, каталог

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

Ошибки при извлечении картинки и как их обработать

Диагностика: выберите симптом

Надёжная обёртка для массовой обработки, где одна битая запись не должна остановить цикл:

Попытка
    Картинка = КартинкаИзХранилища(Выборка.ФотоТовара);
Исключение
    Картинка = Неопределено;
    ЗаписьЖурналаРегистрации("ИзвлечениеКартинки",
        УровеньЖурналаРегистрации.Ошибка,
        Метаданные.Справочники.Номенклатура,
        Выборка.Ссылка,
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;

Ошибки попадают в журнал регистрации с привязкой к элементу справочника. Потом их легко найти отбором по событию ИзвлечениеКартинки.

Обратный путь: пользователь выбирает фото, форма пишет его в хранилище

Получение картинки почти всегда идёт в паре с загрузкой. Если загрузку сделать неправильно, читать потом будет нечего или придёт не тот тип. Схема для управляемой формы элемента справочника:

  1. Пользователь нажимает команду «Загрузить фото», на клиенте открывается диалог выбора файла.
  2. Файл уходит во временное хранилище, адрес записывается в реквизит формы АдресФото. Поле картинки сразу показывает новое изображение.
  3. При записи объекта сервер забирает данные по адресу и кладёт их в реквизит хранилища.
&НаКлиенте
Процедура ЗагрузитьФото(Команда)
    Оповещение = Новый ОписаниеОповещения("ЗагрузитьФотоЗавершение", ЭтотОбъект);
    НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры

&НаКлиенте
Процедура ЗагрузитьФотоЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
    Если Результат Тогда
        АдресФото = Адрес;
        Модифицированность = Истина;
    КонецЕсли;
КонецПроцедуры

&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    Если ЭтоАдресВременногоХранилища(АдресФото) Тогда
        ДД = ПолучитьИзВременногоХранилища(АдресФото);
        Если ТипЗнч(ДД) = Тип("ДвоичныеДанные") Тогда
            ТекущийОбъект.ФотоТовара = Новый ХранилищеЗначения(ДД, Новый СжатиеДанных(9));
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Проверка ЭтоАдресВременногоХранилища нужна потому, что в АдресФото может лежать навигационная ссылка на уже записанную картинку. Без проверки форма при каждой записи будет перезаписывать фото тем же самым содержимым.

Перед записью стоит убедиться, что пользователь выбрал именно изображение. Создайте Новый Картинка(ДД) и сравните Формат() с ФорматКартинки.НеизвестныйФормат: так в реквизит не попадёт PDF или архив, переименованный в .jpg.

Как работать с картинками из хранилища без тормозов

Хранилище значений удобно, пока картинок мало и они маленькие. Когда в справочнике 20 000 позиций с фото по 2-3 МБ, база растёт на десятки гигабайт, а формы начинают открываться заметно дольше.

Что помогает:

  1. Не выбирайте поле хранилища в динамических списках. Если реквизит не нужен в колонках, не добавляйте его в запрос списка. Каждая лишняя строка с фото — это мегабайты по сети.
  2. Храните миниатюру отдельно. Заведите второй реквизит с превью на 20-50 КБ. В списке и подборе показывайте превью, оригинал — только в карточке.
  3. Уменьшайте фото до загрузки. В платформе нет простого метода изменить размер картинки, поэтому сжимайте изображение до записи в базу внешним средством или на стороне сайта, откуда приходят фото.
  4. Извлекайте на сервере, на клиент передавайте адрес. Байты картинки не должны ходить туда-обратно в параметрах серверных вызовов.
  5. Освобождайте временное хранилище. Если данные клали без привязки к форме, после использования вызовите УдалитьИзВременногоХранилища(Адрес).
  6. Подумайте о присоединённых файлах. В конфигурациях на БСП картинки удобнее хранить как присоединённые файлы: их можно вынести в тома на диске, и они не раздувают основную базу.

Перенос картинок из реквизита в присоединённые файлы или тома — это массовая запись в справочник. Делайте резервную копию базы и запускайте обработку вне рабочего времени: она блокирует изменяемые элементы и нагружает сервер.

В БСП двоичные данные присоединённого файла получают функцией РаботаСФайлами.ДвоичныеДанныеФайла(ПрисоединенныйФайл). Дальше всё как выше: Новый Картинка(ДД).

Что класть в хранилище: Картинка или ДвоичныеДанные

Конкретный выбор влияет на то, как потом будет выглядеть код получения.

КритерийВнутри КартинкаВнутри ДвоичныеДанные
Получение для формы и печатиСразу готово к выводуНужна строка Новый Картинка(ДД)
Выгрузка в файлЧерез ПолучитьДвоичныеДанные()Сразу ДД.Записать()
Хранение не-изображений (PDF, DOCX)НевозможноМожно, один код для всех файлов
Проверка форматаКартинка.Формат()Сначала создать Картинка
Хеш, Base64, отправка по HTTPЧерез двоичные данныеНапрямую
Обмен между базами в XMLСериализуетсяСериализуется

Отдельно о самих типах, чтобы не путать роли:

  • 🗄️ ХранилищеЗначения — тип реквизита в базе. Контейнер со сжатием для любого сериализуемого значения.
  • 🧱 ДвоичныеДанные — байты в памяти. Работа с файлами, потоками, HTTP, хешами.
  • 🖼️ Картинка — объект для показа и печати. Знает формат, ширину и высоту.

Вывод простой: для фото товаров и сканов документов храните ДвоичныеДанные. Объект Картинка в хранилище имеет смысл, если значение пишет чужой код и вы его не контролируете, тогда просто читайте с проверкой типа.

Частые вопросы

Можно ли достать картинку из хранилища прямо в запросе?
Нет. Запрос вернёт поле типа ХранилищеЗначения, а метод Получить() вызывается уже в коде, после выборки.
Почему ХранилищеЗначения нельзя положить в реквизит формы и показать?
Поле картинки на управляемой форме принимает адрес: временного хранилища или навигационную ссылку. Само значение хранилища элемент формы не отображает.
Как узнать, что лежит в хранилище, если код записи неизвестен?
Вызовите Получить() и проверьте ТипЗнч результата. Для быстрой проверки в отладчике посмотрите тип значения в окне вычисления выражений.
Можно ли вызвать Получить() на клиенте?
Лучше этого не делать: данные хранилища читают на сервере, а на клиент передают адрес во временном хранилище. Так меньше трафика и меньше ошибок контекста.
Сколько места занимает фото в хранилище?
Примерно столько же, сколько исходный файл JPEG или PNG: эти форматы уже сжаты, и СжатиеДанных почти ничего не выигрывает. BMP при сжатии уменьшается в разы.