Извлекаем изображение из ХранилищаЗначения и присоединённых файлов 1С 8.3: от одной картинки до выгрузки всего справочника
Содержание
Чтобы получить картинку из хранилища в 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 (
/,\,:). Если такие есть, замените их черезСтрЗаменить()перед записью. - 🔁 Если у разных товаров один и тот же файл картинки, он запишется несколько раз под разными кодами. Для каталога сайта это обычно и нужно.
- 🧾 Ошибки по отдельным товарам не останавливают выгрузку: они попадают в сообщения, и по ним видно, какие файлы битые или недоступны в томе.
В конфигурации без реквизита ФайлКартинки откройте справочник номенклатуры в конфигураторе и посмотрите, как у вас называется реквизит со ссылкой на файл. Запрос меняется только в этом имени.
Клиент, сервер и временное хранилище
Здесь ломается большинство первых попыток. Тип ХранилищеЗначения и его метод Получить() недоступны в тонком и веб-клиенте. Код вида Объект.Изображение.Получить() в процедуре &НаКлиенте выдаст ошибку.
Правильная схема:
- На сервере достать двоичные данные.
- Положить их во временное хранилище:
ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификатор). - Вернуть на клиент строку-адрес.
- На клиенте показать картинку по адресу или сохранить файл на компьютер пользователя.
Сохранить файл на компьютер пользователя
&НаСервереБезКонтекста
Функция АдресКартинки(Ссылка, ИдентификаторФормы)
Данные = Ссылка.Изображение.Получить();
Если ТипЗнч(Данные) = Тип("Картинка") Тогда
Данные = Данные.ПолучитьДвоичныеДанные();
КонецЕсли;
Если ТипЗнч(Данные) <> Тип("ДвоичныеДанные") Тогда
Возврат Неопределено;
КонецЕсли;
Возврат ПоместитьВоВременноеХранилище(Данные, ИдентификаторФормы);
КонецФункции
&НаКлиенте
Асинх Процедура СохранитьКартинку(Команда)
Адрес = АдресКартинки(Объект.Ссылка, УникальныйИдентификатор);
Если Адрес = Неопределено Тогда
Возврат;
КонецЕсли;
Ждать ПолучитьФайлССервераАсинх(Адрес, "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) | В десятичном виде |
|---|---|---|
| PNG | 89 50 4E 47 | 137, 80, 78, 71 |
| JPEG | FF D8 FF | 255, 216, 255 |
| GIF | 47 49 46 | 71, 73, 70 |
| BMP | 42 4D | 66, 77 |
| TIFF | 49 49 2A 00 или 4D 4D 00 2A | 73, 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 даёт больше, чем любое сжатие хранилища.
Почему картинка не достаётся: разбор сбоев
Получить() выполняется на клиенте. Перенесите получение данных в серверную процедуру и передайте на клиент адрес временного хранилища.Картинка или структура. Проверьте ТипЗнч() результата и при необходимости вызовите ПолучитьДвоичныеДанные(). Сверьте расширение с сигнатурой.Частые вопросы
Можно ли выгрузить картинки без конфигуратора и программирования?
Работает ли код в веб-клиенте?
Записать() в веб-клиенте недоступна, поэтому сохраняйте файл через ПолучитьФайлССервераАсинх.Сколько живёт адрес во временном хранилище?
Можно ли хранить картинку прямо в табличной части?
ХранилищеЗначения, и читается он так же через Получить(). Но строки табличной части загружаются вместе с объектом, и документ с десятками фото будет открываться медленно.