Извлекаем изображение из ХранилищаЗначения и присоединённых файлов 1С 8.3: от одной картинки до выгрузки всего справочника

· 10 мин

Содержание

Чтобы получить картинку из хранилища в 1С, на сервере вызовите у значения типа ХранилищеЗначения метод Получить(): вернутся ДвоичныеДанные (или объект Картинка), которые записываются в файл методом Записать() или передаются на клиент через временное хранилище. Если картинка лежит в присоединённых файлах (так устроены БП 3.0, УТ 11, ЗУП 3.1 и другие конфигурации на БСП), двоичные данные отдаёт функция РаботаСФайлами.ДвоичныеДанныеФайла().

Код из примеров запускается в консоли кода и во внешней обработке на платформе 8.3.2x. Главное ограничение: хранилище читается только на сервере, на клиент картинка попадает через временное хранилище.

Где именно 1С держит изображения

Картинка в базе 1С может храниться тремя способами. От способа зависит, каким кодом её доставать.

Где хранитсяКак выглядит в конфигуратореЧем извлекать
Реквизит типа ХранилищеЗначенияРеквизит справочника или документа, например ИзображениеРеквизит.Получить()
Присоединённые файлы БСП, в базеСправочник вида НоменклатураПрисоединенныеФайлы, данные в отдельном регистреРаботаСФайлами.ДвоичныеДанныеФайла()
Присоединённые файлы БСП, в томах на дискеТот же справочник, а сам файл лежит в каталоге томаТа же функция БСП

Как понять свой случай:

  • 🔍 Откройте объект в конфигураторе и найдите реквизит с картинкой. Тип ХранилищеЗначения — первый вариант.
  • 📎 Если реквизит ссылается на справочник ...ПрисоединенныеФайлы (в УТ 11 и БП 3.0 у номенклатуры это ФайлКартинки), значит, картинка — присоединённый файл.
  • 💾 Хранить файлы в базе или в томах, решает администратор в настройках работы с файлами раздела Администрирование. Для кода это не важно, если пользоваться функцией БСП.

В присоединённых файлах не лезьте напрямую в регистр с двоичными данными. Его структура менялась от редакции к редакции БСП, а для файлов в томах там вообще пусто. Функция РаботаСФайлами.ДвоичныеДанныеФайла() сама поймёт, откуда читать.

Нужна одна картинка без программирования? Откройте карточку товара, перейдите к присоединённым файлам (значок скрепки или ссылка «Файлы»), выделите файл и выберите в его меню сохранение на компьютер. Код нужен, когда картинок сотни или это собственный реквизит-хранилище, у которого нет интерфейса выгрузки.

Код для консоли: достаём картинку на сервере

Все примеры ниже выполняются на сервере: в консоли кода в режиме «на сервере», во внешней обработке в процедуре с директивой &НаСервере или в общем модуле.

Реквизит типа ХранилищеЗначения

Ссылка = Справочники.МойСправочник.НайтиПоКоду("000000001");
Хранилище = Ссылка.Изображение;
Данные = Хранилище.Получить();

Если Данные = Неопределено Тогда
    Сообщить("Хранилище пустое");
ИначеЕсли ТипЗнч(Данные) = Тип("Картинка") Тогда
    ДвоичныеДанные = Данные.ПолучитьДвоичныеДанные();
ИначеЕсли ТипЗнч(Данные) = Тип("ДвоичныеДанные") Тогда
    ДвоичныеДанные = Данные;
Иначе
    Сообщить("В хранилище лежит " + Строка(ТипЗнч(Данные)));
КонецЕсли;

Проверка типа здесь не для красоты. В ХранилищеЗначения кладут что угодно. Одни разработчики сохраняют ДвоичныеДанные файла, другие — объект Картинка, третьи — структуру с именем файла и данными. Без проверки код упадёт на первом же нестандартном объекте.

Присоединённый файл БСП

Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00-00000015");
ФайлКартинки = Номенклатура.ФайлКартинки;

Если ЗначениеЗаполнено(ФайлКартинки) Тогда
    ДвоичныеДанные = РаботаСФайлами.ДвоичныеДанныеФайла(ФайлКартинки);
    Расширение = ФайлКартинки.Расширение;
КонецЕсли;

Реквизит Расширение карточки файла хранит расширение без точки: jpg, png. Оно пригодится при записи на диск.

Выборка запросом

Поле типа ХранилищеЗначения можно выбрать запросом, но нельзя использовать в условиях ГДЕ, в сортировке и группировке. Фильтруйте по другим полям, а пустые хранилища отсекайте уже в цикле.

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|    Т.Ссылка КАК Ссылка,
|    Т.Изображение КАК Изображение
|ИЗ
|    Справочник.МойСправочник КАК Т
|ГДЕ
|    НЕ Т.ПометкаУдаления";

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Данные = Выборка.Изображение.Получить();
    Если Данные = Неопределено Тогда
        Продолжить;
    КонецЕсли;
    // обработка картинки
КонецЦикла;

Каждая картинка целиком читается в память сервера. Выборка тысяч фото по 3–5 МБ за один проход нагружает рабочий процесс. Делите выгрузку на порции по 200–500 элементов (ВЫБРАТЬ ПЕРВЫЕ с отбором по коду или ссылке).

Потоки, буфер и Base64

С платформы 8.3.9 у двоичных данных есть потоковое API. Оно нужно, когда картинку надо прочитать частично, склеить с другими данными или переложить в файл без лишней копии в памяти.

Основные объекты:

  • 📖 ДвоичныеДанные.ОткрытьПотокДляЧтения() — поток для чтения уже полученных данных;
  • 🧠 ПотокВПамяти — собирает данные в оперативной памяти, в конце ЗакрытьИПолучитьДвоичныеДанные();
  • 📁 ФайловыйПоток — пишет и читает файл на диске;
  • 🧩 БуферДвоичныхДанных — массив байтов фиксированной длины, к которому можно обращаться побайтно.

Переложить картинку из памяти прямо в файл потоком:

ПотокЧтения = ДвоичныеДанные.ОткрытьПотокДляЧтения();
ПотокЗаписи = Новый ФайловыйПоток("D:\Выгрузка\photo.jpg", РежимОткрытияФайла.Создать);
ПотокЧтения.КопироватьВ(ПотокЗаписи);
ПотокЗаписи.Закрыть();
ПотокЧтения.Закрыть();

Закрывайте потоки всегда, в том числе при ошибке. Незакрытый ФайловыйПоток держит файл заблокированным, пока рабочий процесс не освободит объект. Для надёжности оберните запись в Попытка ... Исключение и закрывайте потоки в обеих ветках.

Картинка в виде строки Base64

Для HTML-поля, письма или передачи в REST-сервис картинку переводят в текст:

Строка64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ДвоичныеДанные);
Строка64 = СтрЗаменить(Строка64, Символы.ВК, "");
Строка64 = СтрЗаменить(Строка64, Символы.ПС, "");
HTML = "<img src=""data:image/png;base64," + Строка64 + """>";

Переносы строк платформа может вставить в длинный результат. Браузеры и многие сервисы их не принимают, поэтому их вырезают. Обратное преобразование — ПолучитьДвоичныеДанныеИзBase64Строки().

Base64 раздувает объём примерно на треть: фото в 3 МБ превращается в строку около 4 МБ. Для больших файлов лучше передавать двоичные данные как есть.

Запись картинки в файл: одна и все сразу

Одна картинка

ДвоичныеДанные.Записать("D:\Выгрузка\photo.jpg");

Метод Записать() создаёт файл и перезаписывает существующий с тем же именем без вопросов. Каталог должен существовать заранее.

Выгрузка всех фото номенклатуры

Пример для конфигураций на БСП, где у номенклатуры есть реквизит ФайлКартинки. Файлы называются по коду товара.

Каталог = "D:\Выгрузка\Фото\";
СоздатьКаталог(Каталог);

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

Выборка = Запрос.Выполнить().Выбрать();
Выгружено = 0;
Пока Выборка.Следующий() Цикл
    Попытка
        Данные = РаботаСФайлами.ДвоичныеДанныеФайла(Выборка.ФайлКартинки);
        ИмяФайла = Каталог + СокрЛП(Выборка.Код) + "." + Выборка.Расширение;
        Данные.Записать(ИмяФайла);
        Выгружено = Выгружено + 1;
    Исключение
        Сообщить("Код " + СокрЛП(Выборка.Код) + ": "
            + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
    КонецПопытки;
КонецЦикла;

Сообщить(СтрШаблон("Выгружено файлов: %1", Формат(Выгружено, "ЧГ=0")));

Что учесть:

  • 🏷️ В коде номенклатуры бывают символы, запрещённые в именах файлов Windows (/, \, :). Если такие есть, замените их через СтрЗаменить() перед записью.
  • 🔁 Если у разных товаров один и тот же файл картинки, он запишется несколько раз под разными кодами. Для каталога сайта это обычно и нужно.
  • 🧾 Ошибки по отдельным товарам не останавливают выгрузку: они попадают в сообщения, и по ним видно, какие файлы битые или недоступны в томе.

В конфигурации без реквизита ФайлКартинки откройте справочник номенклатуры в конфигураторе и посмотрите, как у вас называется реквизит со ссылкой на файл. Запрос меняется только в этом имени.

Клиент, сервер и временное хранилище

Здесь ломается большинство первых попыток. Тип ХранилищеЗначения и его метод Получить() недоступны в тонком и веб-клиенте. Код вида Объект.Изображение.Получить() в процедуре &НаКлиенте выдаст ошибку.

Правильная схема:

  1. На сервере достать двоичные данные.
  2. Положить их во временное хранилище: ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор).
  3. Вернуть на клиент строку-адрес.
  4. На клиенте показать картинку по адресу или сохранить файл на компьютер пользователя.

Сохранить файл на компьютер пользователя

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

&НаКлиенте
Асинх Процедура СохранитьКартинку(Команда)
    Адрес = АдресКартинки(Объект.Ссылка, УникальныйИдентификатор);
    Если Адрес = Неопределено Тогда
        Возврат;
    КонецЕсли;
    Ждать ПолучитьФайлССервераАсинх(Адрес, "picture.jpg");
КонецПроцедуры

Асинх/Ждать и ПолучитьФайлССервераАсинх доступны в платформе с номером не ниже 8.3.18. Метод показывает пользователю диалог сохранения и работает и в тонком, и в веб-клиенте. На более ранних платформах ту же задачу решает НачатьПолучениеФайлаССервера с описанием оповещения.

Показать картинку на форме

На форме создайте реквизит АдресКартинки типа «Строка» и поле картинки с путём к данным АдресКартинки. При открытии формы заполните адрес одним из способов:

// Вариант 1: навигационная ссылка на реквизит-хранилище
АдресКартинки = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "Изображение");

// Вариант 2: временное хранилище (данные из любого источника)
АдресКартинки = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);

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

Где окажется файл при записи с сервера

В клиент-серверном варианте ДвоичныеДанные.Записать("D:\...") на сервере пишет на диск сервера 1С, а не на компьютер пользователя. Писать будет пользователь, под которым запущена служба сервера (по умолчанию USR1CV8).

  • 🔐 У этого пользователя должны быть права на запись в целевой каталог.
  • 🌐 Для сетевой папки указывайте UNC-путь \\server\share\...: подключённые буквы дисков службе не видны.
  • 🧪 В файловой базе и в толстом клиенте сервер и клиент — один компьютер, поэтому ошибка с «пропавшим» файлом проявляется только после переезда на сервер.

Формат, конвертация и сжатие

Как узнать реальный формат

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

Функция РасширениеПоСигнатуре(ДвоичныеДанные)
    Поток = ДвоичныеДанные.ОткрытьПотокДляЧтения();
    Буфер = Новый БуферДвоичныхДанных(4);
    Прочитано = Поток.Прочитать(Буфер, 0, 4);
    Поток.Закрыть();
    Если Прочитано < 4 Тогда
        Возврат "bin";
    КонецЕсли;

    Б0 = Буфер.Получить(0);
    Б1 = Буфер.Получить(1);
    Б2 = Буфер.Получить(2);
    Б3 = Буфер.Получить(3);

    Если Б0 = 137 И Б1 = 80 И Б2 = 78 И Б3 = 71 Тогда
        Возврат "png";
    ИначеЕсли Б0 = 255 И Б1 = 216 И Б2 = 255 Тогда
        Возврат "jpg";
    ИначеЕсли Б0 = 71 И Б1 = 73 И Б2 = 70 Тогда
        Возврат "gif";
    ИначеЕсли Б0 = 66 И Б1 = 77 Тогда
        Возврат "bmp";
    ИначеЕсли (Б0 = 73 И Б1 = 73 И Б2 = 42 И Б3 = 0)
        Или (Б0 = 77 И Б1 = 77 И Б2 = 0 И Б3 = 42) Тогда
        Возврат "tif";
    КонецЕсли;
    Возврат "bin";
КонецФункции
ФорматПервые байты (hex)В десятичном виде
PNG89 50 4E 47137, 80, 78, 71
JPEGFF D8 FF255, 216, 255
GIF47 49 4671, 73, 70
BMP42 4D66, 77
TIFF49 49 2A 00 или 4D 4D 00 2A73, 73, 42, 0 / 77, 77, 0, 42

Объект Картинка тоже умеет сообщить формат методом Формат(), но сигнатура работает с любыми двоичными данными и не требует создавать картинку.

Конвертация и уменьшение размера

Платформа хорошо показывает изображения, но для пакетного изменения размера и пересжатия JPEG она не лучший инструмент. Практичный путь — выгрузить файлы на сервер и обработать их внешней утилитой, например ImageMagick:

magick D:\Выгрузка\Фото\00-00000015.bmp -resize 800x800 -quality 85 D:\Выгрузка\Сайт\00-00000015.jpg

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

Сжатие при сохранении в хранилище

При записи картинки в реквизит можно включить сжатие:

Объект.Изображение = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));

Уровень 9 — максимальный. Чтение не меняется: Получить() сама распакует данные. Реальная экономия есть для BMP и TIFF без сжатия: они уменьшаются в разы. JPEG и PNG уже сжаты, на них выигрыш обычно в пределах нескольких процентов, а запись и чтение медленнее.

Если база распухла от картинок, сначала посмотрите на формат. Одна BMP-фотография 4000×3000 занимает около 36 МБ, тот же кадр в JPEG — 2–4 МБ. Перевод таких файлов в JPEG даёт больше, чем любое сжатие хранилища.

Почему картинка не достаётся: разбор сбоев

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

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

Можно ли выгрузить картинки без конфигуратора и программирования?
Да, по одной: в карточке объекта откройте присоединённые файлы и сохраните нужный на компьютер. Массовая выгрузка без кода в типовых конфигурациях обычно недоступна, нужна внешняя обработка.
Работает ли код в веб-клиенте?
Серверная часть работает одинаково. На клиенте запись на диск методом Записать() в веб-клиенте недоступна, поэтому сохраняйте файл через ПолучитьФайлССервераАсинх.
Сколько живёт адрес во временном хранилище?
Если при помещении передан уникальный идентификатор формы, данные живут, пока открыта форма. Без идентификатора — до конца текущего серверного вызова, поэтому для показа на форме идентификатор передавайте всегда.
Можно ли хранить картинку прямо в табличной части?
Да, реквизит табличной части тоже может иметь тип ХранилищеЗначения, и читается он так же через Получить(). Но строки табличной части загружаются вместе с объектом, и документ с десятками фото будет открываться медленно.
Почему в выгрузке у части товаров нет файлов?
У них не заполнен реквизит со ссылкой на основную картинку, хотя фото могут лежать среди других присоединённых файлов. Такие файлы выбирают запросом к справочнику присоединённых файлов по владельцу.