vaspvort
Ночной дозор
Команда форума
Модератор
ПРОВЕРЕННЫЙ ПРОДАВЕЦ
Private Club
Старожил
Migalki Club
Меценат💰️
В этот раз поговорим о JPEG...
Что нам нужно знать про JPEG и почему этот парень заслуживает отдельной статьи?
А знать нам для начала нужно то, как JPEG устроен. Начнем с того, что в JPEG не используется RGB, вместо него мы имеем дело с YCbCr.
Из чего состоит YCbCr?
Продолжим. Второй факт о JPEG — он не хранит цвет каждого пикселя. Вместо этого JPEG использует DCT-сжатие:
Это способ представить изображение (а в нашем случае каждый блок) в виде матрицы коэффициентов (или же по-другому частот), описывающих его
DCT для всех трех каналов, для примера
Оно же в виде матрицы
Что мы будем делать?
Ясенч... красен, мы будем менять последний бит в каждом элементе матрицы (на бит, нужный нам)! Подобную механику я описал в
Как это может выглядеть
Но сперва посмотрим, как изменения этой матрицы скажутся на изображении.
Затрагиваем только высокие частоты
Затрагиваем только средние частоты
Затрагиваем только низкие частоты
Затрагиваем все частоты
Примеры приведены с изменением частоты на 70% для пущей наглядности.
Но для нашей задачи требуется изменение всего на 1 bit. А следовательно, изменения будут совершенно незаметны человеческому глазу.
Итак! Чтобы спрятать наше сообщение внутри JPEG, нам остается лишь разбить его на биты и пройтись по матрице, меняя последний бит каждого элемента на нужный нам.
Техническую реализацию по получению 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[j] = (lumaCoefficients[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[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));
});
Ну и результат
Код более развернутого решения можно найти на
Кому интересно - можно поиграться с тем, какие частоты мы используем под хранение информации (от этого зависит визуальное изменение изображения).
X от 0 до 7, Y от 0 до 7
Что нам нужно знать про 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 не прикладываю, так как она довольно объемная. Прекрасный пример -
Для просмотра ссылки необходимо нажать
Вход или Регистрация
. Используя его, можно работать с матрицами 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[j] = (lumaCoefficients[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[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));
});
Ну и результат
Код более развернутого решения можно найти на
Для просмотра ссылки необходимо нажать
Вход или Регистрация
(использование AES, сокрытие файлов в картинке).Кому интересно - можно поиграться с тем, какие частоты мы используем под хранение информации (от этого зависит визуальное изменение изображения).
X от 0 до 7, Y от 0 до 7
Для просмотра ссылки необходимо нажать
Вход или Регистрация