Как поменять несколько бит в картинке bmp в с++
уважаемые знатоки. Возникла задача - записать в файл (картинку .bmp) некий текст. Текст нужно взять из файла и записать в существующую картинку. Я погуглил, и решил, что нужно открывать файл-картинку в двоичном виде, но проблема в том, что я не могу понять, как считывать эти самые биты. У меня получается всякая абра кадабра. Так вот вопрос, что нужно сделать, чтобы записать в конец каждого байта некоторый символ в виде его числового представления пока сообщение не кончится? Я написал такой код:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
string data = "I:\\NumMet\\bmp\\data.txt";
string image = "I:\\NumMet\\bmp\\bmp.bmp";
string message = "";
ifstream fin(data);
ifstream fin2(image, ios::binary);
if (!fin.is_open() && !fin2.is_open()) {
cout << "File not open";
return 0;
}
// считываю текст для записи в картинку
char ch;
int count = 0;
while (fin.get(ch)) {
message += ch;
count++;
}
cout << count << ' ';
cout << message;
fin.close();
// скипаю байты на всякий случай
int i = 0;
fin2.seekg(256);
// проверяю, что достаточно пикселей для записи
i = 0;
while (fin2.get(ch) && i < count) {
i++;
}
if (i < count) {
cout << "picture is small";
}
else {
cout << "Good";
// замена битов картинки на биты символов.
}
fin2.close();
}
В нем я считываю сообщения для записи, пропускаю несколько байт, чтобы не задеть системные данные (но я не нашел, сколько именно надо пропускать, потому взял с запасом) и после проверки, что пикселей для записи хватит - должен записывать те самые данные.
Добавлено: Загрузил картинку с результатом работы программы
Ответы (1 шт):
Я рассмотрю вариант, когда алфавит кодируется пятью битами и в каждом байте пиксельных данных мы меняем 1 младший бит. Таким образом, для одного символа потребуется пять байт. Это вариант с наименьшим воздействием на изображение.
Имеем 26 букв латинского алфавита + пробел. Еще в запасе 5 кодов.
Если внимательно посмотреть на структуру, то мы увидим, что по адресу 0x0a можно прочитать смещение пиксельных данных от начала файла.
Записать данные не проблема, а вот как при чтении понять, какое количество символов нужно прочитать и есть ли этот текст вообще? Можно использовать пару из неиспользуемых кодов в алфавите в качестве терминальных символов (начало и конец строки). Но вероятность ложного захвата строки все же остается, хоть и сводится к минимуму при желании.
Я же обратил внимание на то, что есть неиспользуемые 4 байта по адресу 0x06, куда можно записать размер строки.
Дальнейшие комментарии в коде. Лишних проверок не делаю.
Запись данных
// Открываем файлы. BMP на чтение и запись
fstream fImg(image, ios::binary | ios::in | ios::out);
ifstream fTxt(text);
char ch;
unsigned char byte;
long offset;
long count = 0;
// Считываем смещение пиксельных данных
fImg.seekg(0x0a);
fImg.read((char*)&offset, 4);
while (fTxt.get(ch)) {
// алфавит начинается с ASCII кода 97, нам нужно с 0
ch -= 97;
// все что не входит в 5 бит, будет под кодом 31 (в дальнейшем пробел)
if (ch < 0 || ch > 31) ch = 31;
// итерируем 5 бит символа
for (int i = 0; i < 5; i++, offset++)
{
// значение младшего бита
int firstBit = ch & 1;
// читаем байт пиксельных данных
fImg.seekg(offset);
byte = fImg.peek();
// Заменяем младший бит на firstBit символа
byte ^= (-firstBit ^ byte) & 1U;
// записываем байт обратно
fImg.seekp(offset);
fImg.put(byte);
// побитовый сдвиг вправо, чтобы получить следующий младший бит
ch >>= 1;
}
// кол-во записанных символов
count++;
}
// Записываем в неиспользуемое поле заголовка длину строки
fImg.seekp(0x06);
fImg.write((const char*)&count, sizeof(count));
fTxt.close();
fImg.close();
Чтение
ifstream fImg(image);
char ch;
unsigned char byte;
long count;
long offset;
// Считываем длину строки
fImg.seekg(0x06);
fImg.read((char*)&count, 4);
// Считываем смещение пиксельных данных
fImg.seekg(0x0a);
fImg.read((char*)&offset, 4);
while (count--) {
ch = 0;
// нужно 5 байт, чтобы восстановить символ
for (int i = 0; i < 5; i++, offset++)
{
// читаем байт
fImg.seekg(offset);
byte = fImg.peek();
// получаем младший бит
int firstBit = byte & 1;
// заменяем i-тый бит на firstBit
ch ^= (-firstBit ^ ch) & (1U << i);
}
// Все, что не буква, будет пробел
if (ch > 25) ch = -65;
ch += 97;
cout << ch;
}
fImg.close();
Дополнение
Код работает для 24-битных изображений и 8-битных с равномерной grayscale-палитрой.