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

27.06.18

Разработка - Универсальные функции

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

Скачать файлы

Наименование Файл Версия Размер
Работа с двоичными данными на примере чтения файлов изображений
.epf 8,25Kb
25
.epf 8,25Kb 25 Скачать

В данной статье рассмотрим применение новых методов платформы 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 + "--");	
	
	ЗаписьДанных.Закрыть();
	
	ДвоичныеДанныеТело = ПотокТело.ЗакрытьИПолучитьДвоичныеДанные();
	
	Возврат ДвоичныеДанныеТело;
	
	
КонецФункции      

 

двоичные данные формат файла размер файла API Вконтакте

См. также

Планы обмена VS История данных

Обмен между базами 1C Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    6175    dsdred    36    

110

1С-ная магия

Механизмы платформы 1С Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    18207    SeiOkami    46    

116

Валидация JSON через XDTO (включая массивы)

WEB-интеграция Универсальные функции Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    8569    YA_418728146    6    

139

Печать непроведенных документов для УТ, КА, ERP. Настройка печати по пользователям, документам и печатным формам

Пакетная печать Печатные формы Адаптация типовых решений Универсальные функции Платформа 1С v8.3 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Абонемент ($m)

Расширение для программ 1С:Управление торговлей, 1С:Комплексная автоматизация, 1С:ERP, которое позволяет распечатывать печатные формы для непроведенных документов. Можно настроить, каким пользователям, какие конкретные формы документов разрешено печатать без проведения документа.

2 стартмани

22.08.2023    2024    21    progmaster    7    

3

Расширение глобального поиска 1С, или Глобальный поиск "на максималках"

Механизмы платформы 1С Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Мало кто знает, что поле "Глобального поиска" в 1С можно доработать. Добавить свои варианты поиска, кнопочки в результатах и даже целые пользовательские меню.

27.03.2023    6883    SeiOkami    10    

140

Версионирование объектов VS История данных

Механизмы платформы 1С Платформа 1С v8.3 Бесплатно (free)

Давайте разберемся в механизме «История данных» и поэкспериментируем для наглядности. Сравним «Версионирование объектов» и «Историю данных».

06.03.2023    18482    dsdred    54    

191

Практическая шпаргалка по новым возможностям языка запросов 1С

Механизмы платформы 1С Запросы Платформа 1С v8.3 Запросы Конфигурации 1cv8 Бесплатно (free)

В предлагаемой статье решил привести примеры применения новых возможностей языка запросов 1С, начиная с версии платформы 8.3.20.

21.11.2022    23191    quazare    36    

121

Расширение: Быстрые отборы через буфер [Alt+C] Копировать список, [Alt+V] Вставить список, [Ctrl+C] Копировать из файлов

Инструментарий разработчика Универсальные функции Платформа 1С v8.3 Конфигурации 1cv8 1С:Розница 2 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Зарплата и Управление Персоналом 3.x Абонемент ($m)

Копирует в буфер значения из списков, из ячеек отчетов, таблиц, настроек списков, других отборов и вставляет в выбранную настройку отбора. Работает с Объект не найден. Работает как в одной так и между разными базами 1С. Использует комбинации [Alt+C] Копировать список, [Alt+V] Вставить список. Также для копирования данных используется стандартная [Ctrl+C] (например из открытого xls, mxl, doc и т.п. файла скопировать список наименований)

1 стартмани

13.10.2022    16019    131    sapervodichka    112    

129
Вознаграждение за ответ
Показать полностью
Комментарии
В избранное Подписаться на ответы Сортировка: Древо развёрнутое
Свернуть все
1. CyberCerber 851 14.11.16 09:27 Сейчас в теме
Добрый день

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

Вот только:

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

Почему делите на 1000, а не 1024?
maksa2005; корум; frkbvfnjh; aximo; +4 Ответить
2. Anton64 215 14.11.16 10:33 Сейчас в теме
(1) CyberCerber, Спасибо, ошибка закралась. Исправил
20. kild 89 25.06.18 17:52 Сейчас в теме
(2) Почему не исправили в прикрепленной обработке?
3. Armando 1399 14.11.16 11:14 Сейчас в теме
Подсчет количества страниц 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Фрагмент(СмещениеСледущего, ФайловыйПоток, Счетчик, ПорядокБ);
	КонецЦикла;
	
	Возврат Счетчик;
	
КонецФункции
Показать
kraynev-navi; swiss-garant; VasilVtoroy; Anton64; DrAku1a; +5 Ответить
4. kredko 20 15.11.16 04:32 Сейчас в теме
Переведем эти значения из 16-ной системы в двоичную и получим значения:
"137,80,78,71,13,10,26,10"

Может всё же в десятиричную систему счисления?
5. Anton64 215 15.11.16 07:23 Сейчас в теме
(4) kredko, да, всё верно, спасибо
6. Serj1C 483 16.11.16 08:06 Сейчас в теме
Как же мне не хватало работы с двоичными данными в 2010 году...
Но BMP читать и писать уже тогда получалось средствами платформы) http://infostart.ru/public/77713/
7. sml 41 17.11.16 09:44 Сейчас в теме
Плюсанул за подробное описание форматов файлов
8. mixperm 67 18.11.16 19:11 Сейчас в теме
Отправка картинки в ВК не удалась. Буду очень благодарен если сообщите кусок кода где на upload_url отправляется эта картинка
9. Anton64 215 19.11.16 17:57 Сейчас в теме
(8) mixperm,
Код
Поручик; chizbo; +2 Ответить
10. Yashazz 4707 21.11.16 17:10 Сейчас в теме
Ай-ай, такие прогрессивные вещи описываете, а всё РазложитьСтрокуВМассивПодстрок вместо СтрРазделить пользуете)))

Если честно, не особенно понимаю прикладную пользу этих новшеств. Где это реально сильно и позарез надо?
TreeDogNight; Сурикат; user636219_dmitriy.gomzin; +3 Ответить
12. DrAku1a 1678 09.01.17 08:28 Сейчас в теме
(10) Разбор кода POST-запроса для HTTP или WEB сервисов.
11. Godman 70 23.11.16 16:29 Сейчас в теме
Ну и зачем это надо? Теперь вирусы писать станет проще - прямо средствами нативного языка. Как щас вижу баннер от Касперского: "Надежная защита от 1С. Доступно и всерьёз!"
Поручик; DrAku1a; TreeDogNight; da.buraev; bow; brr; +6 Ответить
13. fokin 10.01.18 10:40 Сейчас в теме
бьюсь в поисках
а есть способ определить цветное это изображение или оттенки-серого или ч/б ?
14. DenisCh 10.01.18 10:49 Сейчас в теме
Я себе через бинарные файлы вот эту фигню http://ftsc.org/docs/fsc-0048.002 читаю )))
15. fokin 10.01.18 11:22 Сейчас в теме
(14) а можно поподробнее? что это?
18. fokin 10.01.18 13:40 Сейчас в теме
(16) чем фидо мне поможет?
19. DenisCh 10.01.18 15:10 Сейчас в теме
(18) Тебе ничем. я просто привёл пример того, что я делаю с бинарными файлами )))
17. fokin 10.01.18 13:35 Сейчас в теме
21. kild 89 25.06.18 19:34 Сейчас в теме +2 $m
Скачал обработку. JPEG файлы неправильно определяет ширину высоту. Зря потраченные стартмани.
Например, прикрепил картинку 800x600. Твоя обработка показывает 55653х62772. Неужели за 2 года никто не проверил?
Прикрепленные файлы:
22. Anton64 215 27.06.18 09:49 Сейчас в теме +30 $m
(21) Убрал описание JPEG файлов из публикации. Вернул ваши кровно заработанные зря потраченные стартмани в двойном размере.
Sergafan10; Danil.Potapov; juker; pm74; GreenDragon; +5 Ответить
23. pavel_pozdeev 315 16.04.21 11:52 Сейчас в теме
(22) а тип файл jpeg она верно определяет? У меня задача определить тип (jpeg, png, tiff, ...) по Картинке
25. kembrik 10 27.01.22 17:52 Сейчас в теме
(22) Честно говоря, не понял почему вы с шириной-высотой JPEG не довели до конца. Там же сначала ищем первый байт маркера (255) потом смотрим что идёт за ним (нам нужен 192,193 или 194)

Размер секции читаем как .ПрочитатьЦелое16() - 2

И дальше дело техники, сначала точность данных как .ПрочитатьБайт() и дважды .ПрочитатьЦелое16() - высота и ширина
24. zna.miass 30.08.21 05:58 Сейчас в теме
А как используя двоичные данные вырезать область картинки по координатам?
Оставьте свое сообщение