Прошлая статья нашла своих читателей, а поэтому продолжаем!
В этот раз поговорим о JPEG…

Что нам нужно знать про JPEG и почему этот парень заслуживает отдельной статьи?
А знать нам для начала нужно то, как JPEG устроен. Начнем с того, что в JPEG не используется RGB, вместо него мы имеем дело с YCbCr.
Из чего состоит YCbCr?
-
Y — Яркость (светлота). Вычисляется по формуле:
-
-
Cb — Разница между яркостью и синим
-
Сr — Разница между яркостью и красным
Продолжим. Второй факт о JPEG — он не хранит цвет каждого пикселя. Вместо этого JPEG использует DCT-сжатие:
-
Изображение разбивается на компоненты YCbCr и делится на блоки 8×8 пикселей.
-
Каждый блок преобразуется в частотное пространство с помощью DCT (дискретное косинусное преобразование).
-
Высокие частоты у каналов Cb и Cr, как правило, уменьшаются (зависит от степени сжатия)
Что такое DCT?
Это способ представить изображение (а в нашем случае каждый блок) в виде матрицы коэффициентов (или же по-другому частот), описывающих его
-
Низкие частоты: описывают плавные изменения (фон, крупные объекты). Изменение низких частот напрямую влияет на яркость изображения.
-
Средние частоты: описывают контуры объектов, крупные текстуры. Их изменения влияют на восприятие объектов и текстур на изображении, делая их более заметными или, наоборот, сглаженными.
-
Высокие частоты: описывают очень мелкие детали. Высокие частоты отвечают за резкость, чёткость и ощущение «детализации» в изображении.
Что мы будем делать?
Ясен ч… красен, мы будем менять последний бит в каждом элементе матрицы (на бит, нужный нам)! Подобную механику я описал в прошлой статье о PNG, только там мы меняли бит у цвета, а в этот раз нам предстоит менять бит у чисел в матрице.
Но сперва посмотрим, как изменения этой матрицы скажутся на изображении.
Примеры приведены с изменением частоты на 70% для пущей наглядности.
Но для нашей задачи требуется изменение всего на 1 bit. А следовательно, изменения будут совершенно незаметны человеческому глазу.
Итак! Чтобы спрятать наше сообщение внутри JPEG, нам остается лишь разбить его на биты и пройтись по матрице, меняя последний бит каждого элемента на нужный нам.

Техническую реализацию по получению DCT не прикладываю, так как она довольно объемная. Прекрасный пример — owencm/js-steg. Используя его, можно работать с матрицами DCT.
Простой пример сохранения и получения сообщения:
/** * Переводит наше сообщение в байты */ function textToBytes(text){ let encoder = new TextEncoder(); return encoder.encode(text); } /** * Производит изменения с матрифами */ function modifyCoefficients(coefficients){ //Наше сообщение let message = "Message"; let data = textToBytes(message); //coefficients[0] -> все блоки Y //coefficients[1] -> все блоки Cb //coefficients[2] -> все блоки Cr //coefficients[0][0] //64 элемента матрицы DCT //Меняем данные в матрице //Для примера, работаем только с Y-каналом let lumaCoefficients = coefficients[0]; for (let i = 0, bitIndex = 0; i < lumaCoefficients.length; i++) { for (let j = 0; j < 64; j++) { if(bitIndex < data.length * 8){ let bit = (data[Math.floor(bitIndex / 8)] >> (7 - (bitIndex % 8))) & 1; //Меняем последний бит lumaCoefficients[i][j] = (lumaCoefficients[i][j] & 0xFE) | bit; bitIndex++; } } } } jsSteg.reEncodeWithModifications(objectURL, modifyCoefficients, function (resultUri) { //resultUri - наш результат (картинка base64) });
/** * Переврдит байты в строку */ function bytesToText(bytes){ let uint8Array = new Uint8Array(bytes); let decoder = new TextDecoder(); return decoder.decode(uint8Array); } function readCoefficients(coefficients) { //coefficients[1] - Y //coefficients[2] - Cb //coefficients[3] - Cr let bytes = []; let dataBitIndex = 0; let currentByte = 0; //Работаем так же только с Y let lumaCoefficients = coefficients[1]; for (let i = 0; i < lumaCoefficients.length; i++) { for (let j = 0; j < 64; j++) { let bit = lumaCoefficients[i][j] & 1; currentByte = (currentByte << 1) | bit; dataBitIndex++; if (dataBitIndex % 8 === 0) { bytes.push(currentByte); currentByte = 0; } } } return bytesToText(bytes); } //Чтение сообщения jsSteg.getCoefficients(objectURL, function(coefficients){ console.log(readCoefficients(coefficients)); });
Код более развернутого решения можно найти на GitHub (использование AES, сокрытие файлов в картинке).
Кому интересно — можно поиграться с тем, какие частоты мы используем под хранение информации (от этого зависит визуальное изменение изображения).
З.ы: увидел ваши комментарии на счет «steganography», позже исправлю.
ссылка на оригинал статьи https://habr.com/ru/articles/862598/
Добавить комментарий