Работа с двоичными данными на примере чтения файлов изображений. Новые возможности 8.3.9



В статье приводятся новые функции по работе с двоичными данными, появившимися в версии платформы 8.3.9 , на примере анализа формата и размера изображений.
А также пример отправки изображения через API ВКонтакте с помощью новых объектов (без использования ОбъединитьФайлы())

В данной статье рассмотрим применение новых методов платформы 8.3.9  по работе с двоичными данными. Сначала немного теории.

Как было раньше

Ранее 1С предоставляла для работы с двоичными данными одноименный тип и некоторые методы работы с файлами. Думаю, многим известна ситуация, когда для отправки файла в формате multipart/form-data использовался метод «ОбъединитьФайлы». Это было связано  с отсутствием методов по работе  с внутренним содержимым двоичных данных.

Что нового

В версии 8.3.9 эта ситуация меняется. Разработчики платформы предоставили в наше распоряжение несколько новых типов и методов. Основным из них является обобщенный объект Поток. Потоков бывает три типа: Поток, ФайловыйПоток и ПотокВПамяти.

Основным преимуществом потоков является их способность работать с данными произвольного объема. Но в то же время они предоставляют ограниченные возможности. 

Для получения расширенных возможностей необходимо на основе потока создать объект ЧтениеДанных. С помощью него можно читать отдельные байты, символы, числа.

Побайтовые операции

Для анализа работы новых методов возьмем  за пример анализ параметров изображений для загрузки через API ВКонтакте.
Требования для загрузки фото в товар группы:

Допустимые форматы: JPG, PNG, GIF. 
Ограничения: минимальный размер фото — 200x200px, сумма высоты и ширины не более 14000px, файл объемом не более 50 МБ.
 
Нас будет интересовать формат файла, его разрешение и вес.
Внимание! Формат файла JPG не обрабатывается в обработке.

Вес файла

Сначала на основе выбранного файла создадим ФайловыйПоток и сможем сразу получить Размер файла в байтах:

ПотокИсходный = ФайловыеПотоки.ОткрытьДляЧтения(Объект.ИмяФайла);

///1. Размер файла
Объект.РазмерФайла = Строка(ПотокИсходный.Размер() / 1024) + " Кб";

Формат файла

 Каждый формат файла отличается друг от друга внутренней структурой. Отличительными особенностями каждого формата будем называть сигнатурами.
Зададим сигнатуры для каждого формата:

///Зададим сигнатуры нужных форматов
МассивДопустимыхФорматов = Новый Соответствие;
МассивДопустимыхФорматов.Вставить("PNG",  РазложитьСтрокуВМассивПодстрок("137,80,78,71,13,10,26,10"));
//МассивДопустимыхФорматов.Вставить("JPEG",  РазложитьСтрокуВМассивПодстрок("255,216"));
МассивДопустимыхФорматов.Вставить("GIF1",  РазложитьСтрокуВМассивПодстрок("71,73,70,56,57,97"));
МассивДопустимыхФорматов.Вставить("BMP",  РазложитьСтрокуВМассивПодстрок("66,77"));
МассивДопустимыхФорматов.Вставить("TIF",  РазложитьСтрокуВМассивПодстрок("73,73,42,0"));
МассивДопустимыхФорматов.Вставить("TIF1",  РазложитьСтрокуВМассивПодстрок("77,77,0,42"));

Каким образом получены сигнатуры?

В hex-записи сигнатура PNG файла выглядит так и состоит из 8 байт:
89 50 4E 47 0D 0A 1A 0A

Это можно проверить, открыв любой PNG файл в HEX-редакторе. Например, возьмем иконку месседжера Telegram размером 32*32.

Переведем эти значения из 16-ной системы в 10-ную и получим значения:
"137,80,78,71,13,10,26,10"

Аналогично поступаем с остальными форматами.

Проверяем формат файла

Создаем объект ЧтениеДанных на основе потока:
ЧтениеДанных = Новый ЧтениеДанных(ПотокИсходный, КодировкаТекста.ANSI, ПорядокБайтов.BigEndian);
ИскомыйТипФайла = ПроверитьСоответствиеТипаФайла(ЧтениеДанных, МассивДопустимыхФорматов);

Если ИскомыйТипФайла = Неопределено Тогда
Сообщить("Не поддерживаемый тип файла");
Возврат;
КонецЕсли;

&НаСервере
Функция ПроверитьСоответствиеТипаФайла(ЧтениеДанных, МассивДопустимыхФорматов)

МаксРазмер = 0;
Для Каждого ТипФайла Из МассивДопустимыхФорматов Цикл
МаксРазмер = Макс(МаксРазмер, ТипФайла.Значение.Количество());
КонецЦикла;

//прочитаем в новый буфер максимальный размер сигнатуры
БуферПроверка = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных(МаксРазмер);

Для Каждого ТипФайла Из МассивДопустимыхФорматов Цикл
//Хотелось бы конечно напрямую сравнить Буфер = Буфер, но так не работает
//Приходится сравнивать каждый байт

ДвоичнаяСигнатураФормата = ПолучитьСигнатуруФорматаВДвоичномВиде(ТипФайла.Значение);
БуферПроверкаТипаФайла = БуферПроверка.Прочитать(0, ДвоичнаяСигнатураФормата.Размер);

Сч = 0;
Равны = Истина;
Для Каждого Счет Из БуферПроверкаТипаФайла Цикл
Если Счет <> ДвоичнаяСигнатураФормата[Сч] Тогда
Равны = Ложь;
Прервать;
КонецЕсли;
Сч = Сч + 1;
КонецЦикла;
Если Равны Тогда
Возврат ТипФайла.Ключ;
КонецЕсли;
КонецЦикла;

Возврат НЕопределено;

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

Т.к. буферы нельзя сравнить между собой, пришлось сравнивать байт за байтом. Если кто знает, как сделать красивее, пишите в комментариях.

Ищем размеры изображения

В каждом формате файла размеры изображения хранятся в разных местах. Например, в PNG они находятся после маркера «IHDR». Зная это, с помощью новых методов, мы можем переместить указатель на нужную позицию :
ЧтениеДанных.ПропуститьДо("IHDR", КодировкаТекста.ANSI);

Так же из спецификации формата PNG известно, что ширина и высота изображения занимают по 4 байта. Сначала прочитаем все 8 байтов, а потом отдельно ширину и высоту:

БуферЗаголовок     = ЧтениеДанных.ПрочитатьВБуферДвоичныхДанных(8);
ОБъект.ШиринаИзображения  = БуферЗаголовок.Прочитать(0, 4).ПрочитатьЦелое32(0, ПорядокБайтов.BigEndian);
Объект.ВысотаИзображения  = БуферЗаголовок.Прочитать(4, 4).ПрочитатьЦелое32(0, ПорядокБайтов.BigEndian);

В методе ПрочитатьВБуферДвоичныхДанных() мы указываем общее количество байтов, которое хотим прочитать в буфер двоичных данных. Далее этот буфер читаем методом Прочитать(0, 4) – где 0 это позиция, а 4 – количество байтов для чтения.

С GIF ситуация немного другая. Т.к. мы использовали  исходный объект ЧтениеДанных для проверки соответствия форматам изображений, то указатель в этом экземпляре объекта переместился на какое-то количество позиций. 

Если мы продолжим читать этот экземпляр, то не сможем найти размеры изображения. Поэтому используем метод потока Перейти() и создаем новое ЧтениеДанных на основе нашего исходного потока. Дальше как обычно читаем размеры.

Как выглядит внутренняя структура файла GIF:
W и H — это ширина и высота. Чтобы до них добраться читаем в буфер 10 байт и получаем ширину и высоту с позиций 6 и 8, прочитав в каждом случае по 2 байта.
 
ТекПозиция   = ПотокИсходный.ТекущаяПозиция();
ПотокИсходный.Перейти(-ТекПозиция, ПозицияВПотоке.Текущая);
ЧтениеДанныхGIF = Новый ЧтениеДанных(ПотокИсходный, КодировкаТекста.ANSI, ПорядокБайтов.LittleEndian);
БуферРазмеры     = ЧтениеДанныхGIF.ПрочитатьВБуферДвоичныхДанных(10);
Объект.ШиринаИзображения  = БуферРазмеры.Прочитать(6,2).ПрочитатьЦелое16(0, ПорядокБайтов.LittleEndian);
Объект.ВысотаИзображения  = БуферРазмеры.Прочитать(8,2).ПрочитатьЦелое16(0, ПорядокБайтов.LittleEndian);

 Таким образом, зная спецификации нужных форматов, мы проверяем формат файла, размеры изображения.

Отправка изображения через API ВКонтакте

Привожу пример работающего кода  отправки сообщения multipart/form-data (проверено на отправке фото товара в группу ВКонтакте) (в обработке не приведен этот код):

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


Функция СобратьИзображениеИзДвоичныхДанных(ДвоичныеДанныеИзображения, Boundary, ИмяФайлаДляЗагрузки)

ПотокТело = Новый ПотокВПамяти();
ЗаписьДанных = Новый ЗаписьДанных(ПотокТело);

ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);
ЗаписьДанных.ЗаписатьСтроку("Content-Disposition: form-data; name=""file""; filename=""" + ИмяФайлаДляЗагрузки + """");

ЗаписьДанных.ЗаписатьСтроку("Content-Type: image/jpeg");
ЗаписьДанных.ЗаписатьСтроку("");

ЗаписьДанных.Записать(ДвоичныеДанныеИзображения);
//Завершение раздела двоичных данных
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary);

//Завершение сообщения для сервера
ЗаписьДанных.ЗаписатьСтроку("--" + Boundary + "--");

ЗаписьДанных.Закрыть();

ДвоичныеДанныеТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();

Возврат ДвоичныеДанныеТело;


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

 

22 Comments

  1. CyberCerber

    Добрый день

    Спасибо за информацию, записывать файл в multipart/form-data стало теперь намного красивее.

    Вот только:

    Объект.РазмерФайла = Строка(ПотокИсходный.Размер() / 1000) + » Кб»;

    Почему делите на 1000, а не 1024?

    Reply
  2. Anton64

    (1) CyberCerber, Спасибо, ошибка закралась. Исправил

    Reply
  3. Armando

    Подсчет количества страниц TIFF

    &НаКлиенте
    Процедура Команда1(Команда)
    
    ПапкаСФайлами = «»;
    МассивФайлов = НайтиФайлы(ПапкаСФайлами, «*.tif*»);
    Для Каждого Файл Из МассивФайлов Цикл
    ФайловыйПоток = ФайловыеПотоки.ОткрытьДляЧтения(Файл.ПолноеИмя);
    Буфер = Новый БуферДвоичныхДанных( 4 );
    ФайловыйПоток.Прочитать(Буфер, 0, Буфер.Размер);
    Если Буфер[0] = 73 И Буфер[1] = 73 И Буфер[2] = 42 И Буфер[3] = 0 Тогда
    ПорядокБ = ПорядокБайтов.LittleEndian;
    ИначеЕсли Буфер[0] = 77 И Буфер[1] = 77 И Буфер[2] = 0 И Буфер[3] = 42 Тогда
    ПорядокБ = ПорядокБайтов.BigEndian;
    Иначе
    Сообщить(Файл.ПолноеИмя + «: формат не поддерживается»);
    Продолжить;
    КонецЕсли;
    ФайловыйПоток.Перейти(4, ПозицияВПотоке.Начало);
    Буфер = Новый БуферДвоичныхДанных( 8 );
    ФайловыйПоток.Прочитать(Буфер, 0, Буфер.Размер);
    СмещениеПервогоIFD = Буфер.ПрочитатьЦелое32(0, ПорядокБ);
    
    Количество = Команда1Фрагмент(СмещениеПервогоIFD, ФайловыйПоток, 0, ПорядокБ);
    Сообщить(Файл.ПолноеИмя + «: » + Количество);
    
    ФайловыйПоток.Закрыть();
    КонецЦикла;
    
    КонецПроцедуры
    
    &НаКлиенте
    Функция Команда1Фрагмент(СмещениеСледущего, Знач ФайловыйПоток, Счетчик, ПорядокБ)
    
    Перем Буфер, КоличествоТэгов, СмещениеСледущегоСмещения;
    
    Пока СмещениеСледущего > 0 Цикл
    Если Счетчик = 999 Тогда
    Прервать; // что-то не так
    КонецЕсли;
    Счетчик = Счетчик + 1;
    ФайловыйПоток.Перейти(СмещениеСледущего, ПозицияВПотоке.Начало);
    Буфер = Новый БуферДвоичныхДанных( 8 );
    ФайловыйПоток.Прочитать(Буфер, 0, Буфер.Размер);
    КоличествоТэгов = Буфер.ПрочитатьЦелое16(0, ПорядокБ);
    СмещениеСледущегоСмещения = СмещениеСледущего + 2 + (КоличествоТэгов * 12);
    ФайловыйПоток.Перейти(СмещениеСледущегоСмещения, ПозицияВПотоке.Начало);
    Буфер = Новый БуферДвоичныхДанных( 8 );
    ФайловыйПоток.Прочитать(Буфер, 0, Буфер.Размер);
    СмещениеСледущего = Буфер.ПрочитатьЦелое32(0, ПорядокБ);
    Команда1Фрагмент(СмещениеСледущего, ФайловыйПоток, Счетчик, ПорядокБ);
    КонецЦикла;
    
    Возврат Счетчик;
    
    КонецФункции

    Показать

    Reply
  4. kredko
    Переведем эти значения из 16-ной системы в двоичную и получим значения:

    «137,80,78,71,13,10,26,10»

    Может всё же в десятиричную систему счисления?

    Reply
  5. Anton64

    (4) kredko, да, всё верно, спасибо

    Reply
  6. Serj1C

    Как же мне не хватало работы с двоичными данными в 2010 году…

    Но BMP читать и писать уже тогда получалось средствами платформы) http://infostart.ru/public/77713/

    Reply
  7. sml

    Плюсанул за подробное описание форматов файлов

    Reply
  8. mixperm

    Отправка картинки в ВК не удалась. Буду очень благодарен если сообщите кусок кода где на upload_url отправляется эта картинка

    Reply
  9. Anton64
    Reply
  10. Yashazz

    Ай-ай, такие прогрессивные вещи описываете, а всё РазложитьСтрокуВМассивПодстрок вместо СтрРазделить пользуете)))

    Если честно, не особенно понимаю прикладную пользу этих новшеств. Где это реально сильно и позарез надо?

    Reply
  11. Godman

    Ну и зачем это надо? Теперь вирусы писать станет проще — прямо средствами нативного языка. Как щас вижу баннер от Касперского: «Надежная защита от 1С. Доступно и всерьёз!»

    Reply
  12. DrAku1a

    (10) Разбор кода POST-запроса для HTTP или WEB сервисов.

    Reply
  13. fokin

    бьюсь в поисках

    а есть способ определить цветное это изображение или оттенки-серого или ч/б ?

    Reply
  14. DenisCh

    Я себе через бинарные файлы вот эту фигню http://ftsc.org/docs/fsc-0048.002 читаю )))

    Reply
  15. fokin

    (14) а можно поподробнее? что это?

    Reply
  16. fokin

    чем фидо мне поможет?

    Reply
  17. fokin

    (16) чем фидо мне поможет?

    Reply
  18. DenisCh

    (18) Тебе ничем. я просто привёл пример того, что я делаю с бинарными файлами )))

    Reply
  19. kadild

    (2) Почему не исправили в прикрепленной обработке?

    Reply
  20. kadild

    Скачал обработку. JPEG файлы неправильно определяет ширину высоту. Зря потраченные стартмани.

    Например, прикрепил картинку 800×600. Твоя обработка показывает 55653х62772. Неужели за 2 года никто не проверил?

    Reply
  21. Anton64

    (21) Убрал описание JPEG файлов из публикации. Вернул ваши кровно заработанные зря потраченные стартмани в двойном размере.

    Reply

Leave a Comment

Ваш адрес email не будет опубликован. Обязательные поля помечены *