В данной статье демонстрируется программа, позволяющая сохранять
текстовую информацию в растровые рисунки. Суть программы состоит в следующем:
берется текстовый файл и набор файлов рисунков. Далее выбирается подходящий
рисунок и на его основе создается второй рисунок, несколько измененный. Причем
степень изменения зависит от количества "вшиваемых" данных. При небольшом
количестве информации отличить рисунки практически невозможно. А получить
обратно текстовую информацию можно только лишь совместив эти два рисунка, а
также имея библиотеку, реализующую алгоритм "склеивания" этих рисунков.
Так выглядит программа в "рабочем" состоянии. А текст я взял из
какого-то файла, валявшегося у меня под рукой.
Как работать с программой
Для того, чтобы создать "слепок" существующего рисунка, нужно:
Загрузить понравившиеся рисунки (секция Picture, кнопка Open, можно
загружать несколько раз, загруженные ранее картинки не пропадут)
Выделить нужный рисунок в компоненте TGraphGrid
При желании, отмасштабировать его (кнопка Resize, масштабирует рисунок в
соответствии с его пропорциями)
Нажать на кнопку Reflect. Через несколько секунд (это если картинка
большая, а так - через несколько миллисекунд) в компоненте TGraphGrid появится
сгенерированное изображение, созданное на основе выбраной картинки и
текстового файла. Новая картинка будет иметь имя "Data".
Сохранить при необходимости нужный файл изображения, выбрав его в
компоненте TDrawGrid и нажав на кнопку Save.
Для того, чтобы получить
текст из двух одинаковых (на первый взгляд) картинок, нужно:
Загрузить картинку, на основе которой был сделан "слепок"
Нажать на кнопку Assembly. При этом появится диалоговое окно загрузки
изображения и на этот раз Вам надо будет выбрать файл "слепка", т.е.
измененную картинку.
Далее, через некоторое время (в зависимости от количества данных,
содержащихся в картинке и размера самой картинки) в компоненте TRichEdit
появится этот самый закодированный текст.
Некоторые важные моменты
Выше было сказано, что при больших размерах картинки и при небольшем
объеме текстового файла отличить исходную и "слепленную" картинку практически
невозможно. Это правильно, но только отчасти. Если взглянуть на изображения, в
котором "зашит" большой текстовый файл, то сразу же в глаза бросаются чужеродные
пиксели, распределенные по всему изображению (кстати, коэффициент разброса можно
менять) а особенно хорошо эти пиксели видны на рисунке, с однородным фоном.
Сравните следующие два рисунка:
На правом рисунке отчетливо виден шум. Этого отчасти можно было бы
избежать, используя неоднороные рисунки с резкими переходами цвета, а также
рисунки большего формата. Или можно написать такой хитрый алгоритм кодирования,
что второе изображение будет невозможно отличить от первого. В примере вместо
увеличения размера рисунка я просто уменьшил количество информации:
Как работает программа
Программа использует компонент Graph, о котором я рассказывал в прошлой
статье.
Первая функция создает изображение, содержащее информацию; вторая -
получает информацию из двух изображений, как я рассказывал выше. Параметры
функции ReflectData:
BaseBitmap - Базовое изображение, на его основе создается
изображение, содержащее информацию
DataBitmap - Изображение, содержащее информацию
Reflection - Процедура, которая изменяет соответствующий
байт исходного изображения и формирует DataBitmap Эта процедура отвечает за
сохранение информации во второй картинке. Но о ней - чуть позже.
ScatterType - Тип распределения информации Может
принимать два значения: stGiven и stEvenly По умолчанию в программе
установлено значение stEvenly. Это означает, что вся информация будет
равномерно распределена по всей картинке. Если так, то задавать значение
следующего параметра Factor не нужно. Если ScatterType установить в stGiven,
то распределение информации зависит от коэффициента распределения.
Factor - Коэффициент распределения. Если параметр
ScatterType равен stGiven, то фактически Factor означает через сколько байт то
начала картинки (не совсем от начала - первые восемь байт идут на длину
записываемого текста и на этот коэффициент) будет вписан следующий код символа
текста. В программе этот коэффициент задается в компоненте TEdit.
Теперь несколько слов о используемой функции Reflection. Эту функцию я
вынес в библиотеку. Эта библиотека является специфическим ключем, паролем для
соединения двух картинок. В нее могут быть заложены самые разные методы
шифрования, в данный же момент использзуется самый примитивный: при шифровании
код очередного символа просто прибавляется к значению из картинки BaseBitmap,
при расшифровке - наооборот. Эта процедура имеет тип:
В этой
программе каждое значение символа высчитывается путем вычитания DataValue из
BaseValue.
Заключение
На последок хочу дать один совет. Не стоит сохранять полученное
изображение в -jpg файле :) Хотя я один раз попробовал, текст оказался сильно
поврежден, хотя такие слова как Microsoft все-таки сохранились... в этом что-то
есть... :)
И еще одно, практический, такой пример:
Попробуйте получить информацию из этих рисунков. Должна получиться
статья, (в формате HTML) которую Вы сейчас читаете.
Примечание: В тексте статьи использовано
конвертированное изображение, если Вы хотите получить реальный рисунок в
формате BMP, содержащий информацию для вышеописанного конкретного примера,
необходимо скачать Image7.zip (162
K)
Скачать :
Компонент, необходимый для работы программы Crypto_Graph.zip
(12 K)
Похоже, у меня есть некоторые замечания по поводу Вашей статьи "Вшивание информации в растровые рисунки". Они касаются искажения изображений - появления разного рода пикселей других цветов. Просто Вы кое-что не учитываете при кодировании. Если все сделать верно, таких пикселей не будет вообще.
С уважением, Владислав.
:: 2010-11-29 20:56:09 :: re:Вшивание информации в растровые рисунки
Логика очень проста: сначала нужно подготовить изображение таким образом, чтобы в нем не осталось ни одного оттенка, равного 255 - заменяем его на 254 (это никак не скажется на изображении) с тем расчетом, что при добавлении к нему двоичного кода помещаемого текста он не сбросится в единицу (если к 255 прибавим 1).
После этого первые три байта изображения резервируются для хранения информации о длине помещаемого в изображение текста. Далее - добавляем двоичный код текста к оттенкам точек изображения.
Ниже представлен исходный код всего проекта. Необходимые краткие пояснения также даны. Форма проекта содержит компоненты: Memo1 - для ввода текста, который необходимо поместить в изображение; кнопка SpeedButton1 - для помещения текста в изображение; SpeedButton2 - для извлечения текста из изображения.
Для восстановления текста сравниваются два рисунка по оттенкам, и получаемый таким образом двоичный код преобразуется в текст. Также легко кодировать таким образом с ключом, который, например, будет хранить информацию о порядке расположения бит текста в изображении (на основе приведенного здесь кода это не составит никакого труда). Вот и все.
Кстати, можно также реализовать случай, когда для шифрования и дешифрования используется одно и то же изображение, без необходимости сравнения его с исходным (исходное после шифрования лучше вообще сразу же удалить).
type
TForm1 = class(TForm)
OpenDialog1: TOpenDialog;
Memo1: TMemo;
SpeedButton1: TSpeedButton;
SpeedButton2: TSpeedButton;
procedure SpeedButton1Click(Sender: TObject);
procedure SpeedButton2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
Stop_Process: Boolean;
implementation
{Преобразуем текст в бинарный код}
function TextToBinary(S: AnsiString):AnsiString;
Var k,n: LongInt;
A,B: Byte;
St: AnsiString;
Vrem: String;
begin
n:= Length(S);
St:=''; Vrem:='';
// Первые 3 байта в изображении резервируем для хранения
// информации о длине текста - записываем туда длину всего текста
While n>1 do
begin
B:=n mod 2;
n:=n div 2;
If B=1 Then Vrem:='1'+Vrem Else Vrem:='0'+Vrem;
end;
If n=1 Then Vrem:='1'+Vrem;
// Дополняем строку Vrem до 24 символов (3 байта)
While Length(Vrem)1 do // теперь преобразуем его в двоичный код
begin
B:=A mod 2;
A:=A div 2;
If B=1 Then Vrem:='1'+Vrem Else Vrem:='0'+Vrem;
end;
If A=1 Then Vrem:='1'+Vrem;
// Дополняем строку Vrem до 8 символов (1 байт)
While Length(Vrem)
{здесь знак "меньше"} 8 do Vrem:='0'+Vrem;
St:=St+Vrem; // помещаем результат в исходную строку St
END; // *************************
TextToBinary:=St;
end;
function GetKey_Length(S: String): Integer;
Var i,Vrem: Integer;
begin
Vrem:=0;
For i:=23 DownTo 0 do Vrem:=Vrem+StrToInt(S[24-i]) shl i;
GetKey_Length:=Vrem;
end;
function BinaryToText(S: AnsiString):AnsiString;
Var i,j,k,n: LongInt;
St: AnsiString;
begin
St:='';
i:=1;
n:=Length(S);
While i
{здесь знак "меньше"}=n do
begin
k:=0;
For j:=7 DownTo 0 do
begin
k:=k+StrToInt(S[i]) shl j;
i:=i+1;
end;
St:=St+Chr(k);
end;
BinaryToText:=St;
end;
procedure EncodingText; // кодирование текста
Var i,j: Integer;
k,n: LongInt;
S_Line: pByteArray;
File_Key,File_Code: String;
S_Binary: AnsiString;
BitMap: TBitMap;
L,H: Integer;
begin
Form1.OpenDialog1.Title:='Выбор базового файла изображения';
If Form1.OpenDialog1.Execute Then
BEGIN
// Создаем BitMap в оперативной памяти
File_Key:=Form1.OpenDialog1.FileName;
BitMap:= TBitMap.Create;
BitMap.PixelFormat:=pf24bit; // формат изображения
BitMap.LoadFromFile(File_Key);
L:=BitMap.Width; // ширина изображения
H:=BitMap.Height; // высота изображения
// Готовим BitMap к приему текстовой информации:
// заменяем в изображении все оттенки цвета 255 на оттенки 254,
// чтобы можно было прибавить 1 из двоичного кода символов текста
For i:=0 To H-1 do
begin
S_Line:=BitMap.ScanLine[i];
For j:=0 To 3*L-1 do
If S_Line[j]=255 Then S_Line[j]:=254;
end;
// Преобразуем текст в двоичный код
S_Binary:=TextToBinary(Form1.Memo1.Text);
// Кодируем текст - "помещаем" его в изображение
n:=Length(S_Binary);
k:=1;
i:=0;
While i{здесь знак "меньше"}=H-1 do
begin //********************************************
S_Line:=BitMap.ScanLine[i];
j:=0;
While j{здесь знак "меньше"}=3*L-1 do
begin
If S_Binary[k]='1' Then S_Line[j]:=S_Line[j]+1;
j:=j+1;
k:=k+1;
If k>n Then // если весь текст помещен в рисунок
begin //- завершаем все циклы
j:=3*L;
i:=H;
end;
end;
i:=i+1;
end; //*********************************************
// Сохраняем подготовленное закодированное изображение
BitMap.SaveToFile(File_Code);
BitMap.Free;
END;
end;
procedure DecodingText; // декодирование текста
Var i,j: Integer;
k,n: LongInt;
S_Line_Key,S_Line_Code: pByteArray;
File_Key,File_Code: String;
S_Binary: AnsiString;
BitMap_Key,BitMap_Code: TBitMap;
L_Key,H_Key,L_Code,H_Code: Integer;
Length_Key: Integer; //длина кодированного текста
St_Key: String; // двоичное строковое представление длины кодированного текста
begin
Form1.Memo1.Clear;
File_Key:='';
File_Code:='';
Form1.OpenDialog1.Title:='Выбор ключевого файла';
If Form1.OpenDialog1.Execute Then File_Key:=Form1.OpenDialog1.FileName;
Form1.OpenDialog1.Title:='Выбор кодированного файла';
If Form1.OpenDialog1.Execute Then File_Code:=Form1.OpenDialog1.FileName;
If (File_Key{здесь знак "меньше"}>'') and (File_Code{здесь знак "меньше"}>'') Then
BEGIN
// Создаем BitMap_Key в оперативной памяти
BitMap_Key:= TBitMap.Create;
BitMap_Key.PixelFormat:=pf24bit; // формат изображения
BitMap_Key.LoadFromFile(File_Key);
L_Key:=BitMap_Key.Width; // ширина изображения
H_Key:=BitMap_Key.Height; // высота изображения
// Создаем BitMap_Code в оперативной памяти
BitMap_Code:= TBitMap.Create;
BitMap_Code.PixelFormat:=pf24bit; // формат изображения
BitMap_Code.LoadFromFile(File_Code);
L_Code:=BitMap_Code.Width; // ширина изображения
H_Code:=BitMap_Code.Height; // высота изображения
If (L_Key=L_Code) and (H_Key=H_Code) Then
BeGiN
// получаем длину кодированного текста
S_Line_Key:=BitMap_Key.ScanLine[0];
S_Line_Code:=BitMap_Code.ScanLine[0];
St_Key:='';
For i:=0 To 23 do St_Key:=St_Key+IntToStr(S_Line_Code[i]-S_Line_Key[i]);
Length_Key:=GetKey_Length(St_Key);
// получаем бинарную последовательность кодов текста
k:=1;
n:=Length_Key*8;
S_Binary:='';
i:=0;
While i{здесь знак "меньше"}=H_Key-1 do
begin //********************************************
S_Line_Key:=BitMap_Key.ScanLine[i];
S_Line_Code:=BitMap_Code.ScanLine[i];
j:=0;
If i=0 Then j:=24; //пропускаем 3 первых байта, хранящих длину текста
While j{здесь знак "меньше"}=3*L_Key-1 do
begin
S_Binary:=S_Binary+IntToStr(S_Line_Code[j]-S_Line_Key[j]);
j:=j+1;
k:=k+1;
If k>n Then // если весь текст извлечен из рисунок
begin // - завершаем все циклы
j:=3*L_Key;
i:=H_Key;
end;
end;
i:=i+1;
end; //*********************************************
// восстанавливаем текст на основе двоичной последовательности
Form1.Memo1.Text:=BinaryToText(S_Binary);
EnD ElSe
MessageDLG(' Размеры ключевого и кодированного файлов'+
#13+'не совпадают. Очевидно, выбраны не те файлы.'+
#13+' Выберите файлы правильно и повторите операцию.',
mtInformation,[mbOk],0);
BitMap_Key.Free;
BitMap_Code.Free;
END;
end;
{$R *.dfm}
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
EncodingText;
end;
procedure TForm1.SpeedButton2Click(Sender: TObject);
begin
DecodingText;
end;
end.
Еще на форму киньте компонент OpenDialog1.
Строка Stop_Process: Boolean; не нужна - забыл удалить...
Место, где написано {здесь знак "меньше"} замените на знак "меньше". Просто знак "меньше" воспринимается этим сайтом как тег... поэтому и не добавлялся код...
Надеюсь, приведенный код кому-то будет полезен.
:: 2010-12-02 21:32:38 :: замечание по статье автора
Вопрос автору статьи: не понятно, зачем выделять 8 байт для хранения информации о длине текста и коэффициенте: 3 байта уже могут хранить число, большее 16 миллионов - вряд-ли текст будет содержать большее число символов (соответственно, изображение для размещения этого текста потребуется более чем 6600 х 6600 точек)...
:: 2010-12-06 14:14:13 :: re:Вшивание информации в растровые рисунки
Спасибо, за интересную реализацию.
Координаты автора есть в заголовке статьи.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.