изображение в бинарный код
Декодирование JPEG для чайников
[FF D8]
Вам когда-нибудь хотелось узнать как устроен jpg-файл? Сейчас разберемся! Прогревайте ваш любимый компилятор и hex-редактор, будем декодировать это:
Специально взял рисунок поменьше. Это знакомый, но сильно пережатый favicon Гугла:
Последующее описание упрощено, и приведенная информация не полная, но зато потом будет легко понять спецификацию.
Даже не зная, как происходит кодирование, мы уже можем кое-что извлечь из файла.
[FF D8] — маркер начала. Он всегда находится в начале всех jpg-файлов.
Следом идут байты [FF FE]. Это маркер, означающий начало секции с комментарием. Следующие 2 байта [00 04] — длина секции (включая эти 2 байта). Значит в следующих двух [3A 29] — сам комментарий. Это коды символов «:» и «)», т.е. обычного смайлика. Вы можете увидеть его в первой строке правой части hex-редактора.
Немного теории
Закодированные данные располагаются поочередно, небольшими частями:
Каждый блок Yij, Cbij, Crij — это матрица коэффициентов ДКП (так же 8×8), закодированная кодами Хаффмана. В файле они располагаются в таком порядке: Y00Y10Y01Y11Cb00Cr00Y20.
Чтение файла
Файл поделен на секторы, предваряемые маркерами. Маркеры имеют длину 2 байта, причем первый байт [FF]. Почти все секторы хранят свою длину в следующих 2 байта после маркера. Для удобства подсветим маркеры:
Маркер [FF DB]: DQT — таблица квантования
Оставшимися 64-мя байтами нужно заполнить таблицу 8×8.
Приглядитесь, в каком порядке заполнены значения таблицы. Этот порядок называется zigzag order:
Маркер [FF C0]: SOF0 — Baseline DCT
Этот маркер называется SOF0, и означает, что изображение закодировано базовым методом. Он очень распространен. Но в интернете не менее популярен знакомый вам progressive-метод, когда сначала загружается изображение с низким разрешением, а потом и нормальная картинка. Это позволяет понять что там изображено, не дожидаясь полной загрузки. Спецификация определяет еще несколько, как мне кажется, не очень распространенных методов.
Находим Hmax=2 и Vmax=2. Канал i будет прорежен в Hmax/Hi раз по горизонтали и Vmax/Vi раз по вертикали.
Маркер [FF C4]: DHT (таблица Хаффмана)
Эта секция хранит коды и значения, полученные кодированием Хаффмана.
Следующие 16 значений:
Количество кодов означает количество кодов такой длины. Обратите внимание, что секция хранит только длины кодов, а не сами коды. Мы должны найти коды сами. Итак, у нас есть один код длины 1 и один — длины 2. Итого 2 кода, больше кодов в этой таблице нет.
С каждым кодом сопоставлено значение, в файле они перечислены следом. Значения однобайтовые, поэтому читаем 2 байта:
Далее в файле можно видеть еще 3 маркера [FF C4], я пропущу разбор соответствующих секций, он аналогичен вышеприведенному.
Построение дерева кодов Хаффмана
Мы должны построить бинарное дерево по таблице, которую мы получили в секции DHT. А уже по этому дереву мы узнаем каждый код. Значения добавляем в том порядке, в каком указаны в таблице. Алгоритм прост: в каком бы узле мы ни находились, всегда пытаемся добавить значение в левую ветвь. А если она занята, то в правую. А если и там нет места, то возвращаемся на уровень выше, и пробуем оттуда. Остановиться нужно на уровне равном длине кода. Левым ветвям соответствует значение 0, правым — 1.
Деревья для всех таблиц этого примера:
В кружках — значения кодов, под кружками — сами коды (поясню, что мы получили их, пройдя путь от вершины до каждого узла). Именно такими кодами закодировано само содержимое рисунка.
Маркер [FF DA]: SOS (Start of Scan)
Байт [DA] в маркере означает — «ДА! Наконец-то то мы перешли к финальной секции!». Однако секция символично называется SOS.
[00], [3F], [00] — Start of spectral or predictor selection, End of spectral selection, Successive approximation bit position. Эти значения используются только для прогрессивного режима, что выходит за рамки статьи.
Отсюда и до конца (маркера [FF D9]) закодированные данные.
Закодированные данные
Последующие значения нужно рассматривать как битовый поток. Первых 33 бит будет достаточно, чтобы построить первую таблицу коэффициентов:
Нахождение DC-коэффициента
1) Читаем последовательность битов (если встретим 2 байта [FF 00], то это не маркер, а просто байт [FF]). После каждого бита сдвигаемся по дереву Хаффмана (с соответствующим идентификатором) по ветви 0 или 1, в зависимости от прочитанного бита. Останавливаемся, если оказались в конечном узле.
2) Берем значение узла. Если оно равно 0, то коэффициент равен 0, записываем в таблицу и переходим к чтению других коэффициентов. В нашем случае — 02. Это значение — длина коэффициента в битах. Т. е. читаем следующие 2 бита, это и будет коэффициент:
Нахождение AC-коэффициентов
1) Аналогичен п. 1, нахождения DC коэффициента. Продолжаем читать последовательность:
2) Берем значение узла. Если оно равно 0, это означает, что оставшиеся значения матрицы нужно заполнить нулями. Дальше закодирована уже следующая матрица. В нашем случае значение узла: 0x31.
Читать AC-коэффициенты нужно пока не наткнемся на нулевое значение кода, либо пока не заполнится матрица.
В нашем случае мы получим:
Вы заметили, что значения заполнены в том же зигзагообразном порядке? Причина использования такого порядка простая — так как чем больше значения v и u, тем меньшей значимостью обладает коэффициент Svu в дискретно-косинусном преобразовании. Поэтому, при высоких степенях сжатия малозначащие коэффициенты обнуляют, тем самым уменьшая размер файла.
Аналогично получаем еще 3 матрицы Y-канала…
Но! Закодированные DC-коэффициенты — это не сами DC-коэффициенты, а их разности между коэффициентами предыдущей таблицы (того же канала)! Нужно поправить матрицы:
Теперь порядок. Это правило действует до конца файла.
… и по матрице для Cb и Cr:
Вычисления
Квантование
Вы помните, что матрица проходит этап квантования? Элементы матрицы нужно почленно перемножить с элементами матрицы квантования. Осталось выбрать нужную. Сначала мы просканировали первый канал. Он использует матрицу квантования 0 (у нас она первая из двух). Итак, после перемножения получаем 4 матрицы Y-канала:
… и по матрице для Cb и Cr.
Обратное дискретно-косинусное преобразование
Формула не должна доставить сложностей. Svu — наша полученная матрица коэффициентов. u — столбец, v — строка. Cx = 1/√2 для x = 0, а в остальных случаях = 1. syx — непосредственно значения каналов.
Приведу результат вычисления только первой матрицы канала Y (после обязательного округления):
Ко всем полученным значениям нужно прибавить по 128, а затем ограничить их диапазон от 0 до 255:
Например: 138 → 266 → 255, 92 → 220 → 220 и т. д.
YCbCr в RGB
4 матрицы Y, и по одной Cb и Cr, так как мы прореживали каналы и 4 пикселям Y соответствует по одному Cb и Cr. Поэтому вычислять так: YCbCrToRGB(Y[y,x], Cb[y/2, x/2], Cr[y/2, x/2]):
Вот полученные таблицы для каналов R, G, B для левого верхнего квадрата 8×8 нашего примера:
Конец
Вообще я не специалист по JPEG, поэтому вряд ли смогу ответить на все вопросы. Просто когда я писал свой декодер, мне часто приходилось сталкиваться с различными непонятными проблемами. И когда изображение выводилось некорректно, я не знал где допустил ошибку. Может неправильно проинтерпретировал биты, а может неправильно использовал ДКП. Очень не хватало пошагового примера, поэтому, надеюсь, эта статья поможет при написании декодера. Думаю, она покрывает описание базового метода, но все-равно нельзя обойтись только ей. Предлагаю вам ссылки, которые помогли мне:
Картинка, которая одновременно является кодом на Javascript
Изображения обычно хранятся как двоичные файлы, а файл Javascript по сути является обычным текстом. Оба типа файлов должны следовать собственным правилам: изображения имеют конкретный формат файла, определённым образом кодирующий данные. Для того, чтобы файлы Javascript можно было исполнять, они должны следовать определённому синтаксису. Я задался вопросом: можно ли создать файл изображения, одновременно являющийся допустимым синтаксисом Javascript, чтобы его можно было исполнять?
Прежде чем вы продолжите чтение, крайне рекомендую изучить эту песочницу кода с результатами моих экспериментов:
Если вы хотите посмотреть изображение и изучить его самостоятельно, то скачать его можно отсюда:
Выбор подходящего типа изображения
К сожалению, изображения содержат множество двоичных данных, которые при интерпретации в качестве Javascript будут выдавать ошибку. Поэтому моя первая мысль заключалась в следующем: что если просто поместить все данные изображения в большой комментарий, примерно так:
Этот заголовок файла привёл меня к следующей идее: что если использовать эту последовательность байтов как имя переменной и присвоить ей значение длинной строки:
К сожалению, большинство последовательностей байтов в заголовках файлов изображений содержат непечатаемые символы, которые нельзя использовать в именах переменных. Но есть один формат, который мы можем использовать: GIF. Блок заголовка GIF имеет вид 47 49 46 38 39 61, что удобно преобразуется в ASCII в строку GIF89a — абсолютно допустимое имя переменной!
Выбор подходящих размеров изображения
Теперь, когда мы нашли формат изображения, начинающийся с допустимого имени переменной, нам нужно добавить символы знака равенства и обратного апострофа (backtick). Следовательно следующими четырьмя байтами файла будут: 3D 09 60 04
Первые байты изображения
В формате GIF четыре байта после заголовка определяют размеры изображения. Нам нужно уместить в них 3D (знак равенства) и 60 (обратный апостроф, открывающий строку). В GIF используется порядок little endian, поэтому второй и четвёртый символы имеют огромное влияние на размеры изображения. Они должны быть как можно меньше, чтобы изображение не получилось шириной и высотой в десятки тысяч пикселей. Следовательно, нам нужно хранить большие байты 3D и 60 в наименее значимых байтах.
Наименьший пробельный символ — это 09 (символ горизонтальной табуляции). Он даёт нам ширину изображения 3D 09, что в little endian равно 2365; немного шире, чем бы мне хотелось, но всё равно вполне приемлемо.
Для второго байта высоты можно выбрать значение, дающее хорошее соотношение сторон. Я выбрал 04, что даёт нам высоту 60 04, или 1120 пикселей.
Засовываем в файл скрипт
Пока наш исполняемый GIF почти ничего не делает. Он просто присваивает глобальной переменной GIF89a длинную строку. Мы хотим, чтобы происходило что-нибудь интересное! Основная часть данных внутри GIF используется для кодирования изображения, поэтому если мы попробуем вставить туда Javascript, то изображение, вероятно, будет сильно искажённым. Но по какой-то причине формат GIF содержит нечто под названием Comment Extension. Это место для хранения метаданных, которые не интерпретируются декодером GIF — идеальное место для нашей Javascript-логики.
Это расширение для комментариев находится сразу после таблицы цветов GIF. Поскольку мы можем поместить туда любое содержимое, можно запросто закрыть строку GIF89a, добавить весь Javascript, а затем начать многострочный блок комментария, чтобы остальная часть изображения не влияла на парсер Javascript.
В конечном итоге наш файл может выглядеть следующим образом:
Однако существует небольшое ограничение: хотя сам блок комментария может иметь любой размер, он состоит из нескольких подблоков, и максимальный размер каждого из них составляет 255. Между подблоками есть один байт, определяющий длину следующего подблока. Поэтому чтобы уместить туда большой скрипт, его нужно разделить на мелкие фрагменты, примерно вот так:
Шестнадцатеричные коды в комментариях — это байты, определяющие размер следующего подблока. Они не относятся к Javascript, но обязательны для формата файла GIF. Чтобы они не мешали остальной части кода, их нужно поместить в комментарии. Я написал небольшой скрипт, обрабатывающий фрагменты скрипта и добавляющий их в файл изображения:
Подчищаем двоичные данные
Теперь, когда у нас есть базовая структура, нам нужно сделать так, чтобы двоичные данные изображения не испортили синтаксис кода. Как говорилось в предыдущем разделе, файл состоит из трёх разделов: в первом выполняется присваивание значения переменной GIF89a, второй — это код на Javascript, а третий — комментарий из нескольких строк.
Давайте взглянем на первую часть с присвоением значения переменной:
Боремся с искажениями
В конце Javascript-кода мы открыли многострочный комментарий, чтобы двоичные данные изображения не влияли на парсинг Javascript:
Завершаем файл
Нам осталась последняя операция — завершение файла. Файл должен завершаться байтами 00 3B, поэтому нам нужно завершить комментарий раньше. Поскольку это конец файла и любые потенциальные повреждения будут не особо заметны, я просто завершил комментарий из блоков и добавил однострочный комментарий, чтобы конец файла не вызывал проблем при парсинге:
Уговариваем браузер исполнить изображение
Refused to execute script from ‘http://localhost:8080/image.gif’ because its MIME type (‘image/gif’) is not executable. [Отказ от исполнения скрипта из ‘http://localhost:8080/image.gif’, потому что его MIME-тип не является исполняемым.]
То есть браузер справедливо говорит: «Это изображение, я не буду его исполнять!». И в большинстве случаев это вполне уместно. Но мы всё равно хотим его исполнить. Решение заключается в том, чтобы просто не говорить браузеру, что это изображение. Для этого я написал небольшой сервер, передающий изображение без информации заголовка.
Без информации о MIME-типе из заголовка браузер не знает, что это изображение и делает именно то, что лучше всего подходит в контексте: отображает его как изображение в теге или исполняет как Javascript в теге
Перевод картинки расширения bmp в двоичный код
Считаю теперь с двоичного
Вот так выглядит картинка в hex редакторе
1 ответ 1
Для начала вам нужно ознакомиться с общим описанием формата, например, в Википедии: BMP.
Если упрощенно, в начале файла идут заголовки, потом уже сами бинарные данные:
Общая структура
Данные в формате BMP состоят из трёх основных блоков различного размера:
По сути, чтобы добраться до описания пиксельных данных, нужно пропустить все заголовки.
Смотрим дальше, описание структуры BITMAPFILEHEADER (которая, собственно, идет с начала файла BMP):
Смотрим на ваш скриншот:
В поле bfOffBits в файле записано число 0x36, и по смещению 0x36 от начала файла подряд идут значения FF (совпадение? не думаю). Тройка байт FF FF FF (8 бит * 3 = 24 бита на пиксель) как раз кодируют один пиксель белого цвета, т.е. это как раз и есть искомые пиксельные данные.
Что еще желательно знать? Во-первых при чтении нужно проверить, что картинка действительно имеет формат RGB с 24 битами на пиксель. Во-вторых нужно знать размер картинки.
Для этого нужно смотреть что идет следом за структурой. А следом идет структура BITMAPINFO, но сложность в том, что она может быть разных версий. Версия задается в первых четырех байтах. Самая древняя версия имела размер 12 байт, более новые от 40 байт и выше. Будем для простоты считать, что поддерживаем только новые форматы BMP, тем более что начало структуры BITMAPINFO у новых форматов совпадает.
Итак, в структурах BITMAPINFO новых форматах по порядку идут:
Снова смотрим на скриншот:
Как с этим работать программно? Два варианта:
Псевдокод для второго случая:
Еще одна тонкость, если в поле biHeight лежит положительное число, то строки пикселей в файле записаны в обратном порядке (от нижней строки к верхней строке). Если отрицательное, то в прямом порядке.
Перевод изображения в двоичный код
Двоичный код ниже:
010000100100110111110110000000010000000000000000000000000000 0000
000000000000000000111110000000000000000000000000001010000000 0000
000000000000000000110111000000000000000000000000001101110000 0000
000000000000000000000001000000000000000100000000000000000000 0000
000000000000000010111000000000010000000000000000110001000000 1110
000000000000000011000100000011100000000000000000000000100000 0000
000000000000000000000010000000000000000000000000111111111111 1111
111111110000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000001111000 0000
000000000000000000000000000000000000000000000000000001111100 0000
000000000000000000000000000000000000000000000000000001111100 0000
000000000000000000000000111100000000000000000000000001111100 0000
000000000000000000000001111100000000000000000000000001111110 0000
000000000000000000000001111100000000000000000000000000111110 0000
000000000000000000000011111100000000000000000000000000111111 0000
000000000000000000000011111100000000000000000000000000011111 0000
000000000000000000000011111000000000000000000000000000011111 1000
000000000000000000000111111000000000000000000000000000001111 1000
000000000000000000000111110000000000000000000000000000001111 1100
000000000000000000001111110000000000000000000000000000000111 1100
000000000000000000001111100000000000000000000000000000000111 1100
000000000000000000011111100000000000000000000000000000000111 1110
000000000000000000011111000000000000000000000000000000000011 1110
000000000000000000111111000000000000000000000000000000000011 1111
111111111111111111111110000000000000000000000000000000000001 1111
111111111111111111111110000000000000000000000000000000000001 1111
111111111111111111111100000000000000000000000000000000000001 1111
111111111111111111111100000000000000000000000000000000000001 1111
111111111111111111111000000000000000000000000000000000000000 1111
111111111111111111111000000000000000000000000000000000000000 0111
111000000000000111110000000000000000000000000000000000000000 0111
111000000000001111110000000000000000000000000000000000000000 0011
111000000000001111100000000000000000000000000000000000000000 0011
111100000000011111100000000000000000000000000000000000000000 0001
111100000000011111000000000000000000000000000000000000000000 0001
111110000000111111000000000000000000000000000000000000000000 0000
111110000000111110000000000000000000000000000000000000000000 0000
111111000001111110000000000000000000000000000000000000000000 0000
011111000001111100000000000000000000000000000000000000000000 0000
011111100011111100000000000000000000000000000000000000000000 0000
001111100011111000000000000000000000000000000000000000000000 0000
001111100111111000000000000000000000000000000000000000000000 0000
001111110111110000000000000000000000000000000000000000000000 0000
000111111111110000000000000000000000000000000000000000000000 0000
000111111111100000000000000000000000000000000000000000000000 0000
000011111111100000000000000000000000000000000000000000000000 0000
000011111111000000000000000000000000000000000000000000000000 0000
000001111111000000000000000000000000000000000000000000000000 0000
000001111111000000000000000000000000000000000000000000000000 0000
000001111110000000000000000000000000000000000000000000000000 0000
000000111110000000000000000000000000000000000000000000000000 0000
000000111110000000000000000000000000000000000000000000000000 0000
000000011110000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000000000000000 0000
000000000000000000000000000000000000000000000000
01000010 01001101 11110110 00000001 00000000 00000000 00000000 00000000
00000000 00000000 00111110 00000000 00000000 00000000 00101000 00000000
00000000 00000000 00110111 00000000 00000000 00000000 00110111 00000000
00000000 00000000 00000001 00000000 00000001 00000000 00000000 00000000
00000000 00000000 10111000 00000001 00000000 00000000 11000100 00001110
00000000 00000000 11000100 00001110 00000000 00000000 00000010 00000000
00000000 00000000 00000010 00000000 00000000 00000000 11111111 11111111
11111111 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000111 10000000
00000000 00000000 00000000 00000000 00000000 00000000 00000111 11000000
00000000 00000000 00000000 00000000 00000000 00000000 00000111 11000000
00000000 00000000 00000000 11110000 00000000 00000000 00000111 11000000
00000000 00000000 00000001 11110000 00000000 00000000 00000111 11100000
00000000 00000000 00000001 11110000 00000000 00000000 00000011 11100000
00000000 00000000 00000011 11110000 00000000 00000000 00000011 11110000
00000000 00000000 00000011 11110000 00000000 00000000 00000001 11110000
00000000 00000000 00000011 11100000 00000000 00000000 00000001 11111000
00000000 00000000 00000111 11100000 00000000 00000000 00000000 11111000
00000000 00000000 00000111 11000000 00000000 00000000 00000000 11111100
00000000 00000000 00001111 11000000 00000000 00000000 00000000 01111100
00000000 00000000 00001111 10000000 00000000 00000000 00000000 01111100
00000000 00000000 00011111 10000000 00000000 00000000 00000000 01111110
00000000 00000000 00011111 00000000 00000000 00000000 00000000 00111110
00000000 00000000 00111111 00000000 00000000 00000000 00000000 00111111
11111111 11111111 11111110 00000000 00000000 00000000 00000000 00011111
11111111 11111111 11111110 00000000 00000000 00000000 00000000 00011111
11111111 11111111 11111100 00000000 00000000 00000000 00000000 00011111
11111111 11111111 11111100 00000000 00000000 00000000 00000000 00011111
11111111 11111111 11111000 00000000 00000000 00000000 00000000 00001111
11111111 11111111 11111000 00000000 00000000 00000000 00000000 00000111
11100000 00000001 11110000 00000000 00000000 00000000 00000000 00000111
11100000 00000011 11110000 00000000 00000000 00000000 00000000 00000011
11100000 00000011 11100000 00000000 00000000 00000000 00000000 00000011
11110000 00000111 11100000 00000000 00000000 00000000 00000000 00000001
11110000 00000111 11000000 00000000 00000000 00000000 00000000 00000001
11111000 00001111 11000000 00000000 00000000 00000000 00000000 00000000
11111000 00001111 10000000 00000000 00000000 00000000 00000000 00000000
11111100 00011111 10000000 00000000 00000000 00000000 00000000 00000000
01111100 00011111 00000000 00000000 00000000 00000000 00000000 00000000
01111110 00111111 00000000 00000000 00000000 00000000 00000000 00000000
00111110 00111110 00000000 00000000 00000000 00000000 00000000 00000000
00111110 01111110 00000000 00000000 00000000 00000000 00000000 00000000
00111111 01111100 00000000 00000000 00000000 00000000 00000000 00000000
00011111 11111100 00000000 00000000 00000000 00000000 00000000 00000000
00011111 11111000 00000000 00000000 00000000 00000000 00000000 00000000
00001111 11111000 00000000 00000000 00000000 00000000 00000000 00000000
00001111 11110000 00000000 00000000 00000000 00000000 00000000 00000000
00000111 11110000 00000000 00000000 00000000 00000000 00000000 00000000
00000111 11110000 00000000 00000000 00000000 00000000 00000000 00000000
00000111 11100000 00000000 00000000 00000000 00000000 00000000 00000000
00000011 11100000 00000000 00000000 00000000 00000000 00000000 00000000
00000011 11100000 00000000 00000000 00000000 00000000 00000000 00000000
00000001 11100000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 00000000 00000000
PS Автор статьи переводил свою картинку вручную т.к. её разрешение 3×5 пикселей; у меня в python shell двоичный код без брешей, не знаю почему здесь так; Мне 15 лет могу что-то не понимать, поэтому, пожалуйста, объясняйте подробнее и понятнее. Спасибо!
Помощь в написании контрольных, курсовых и дипломных работ здесь.