Как написать компонент анализа изображений в Delphi
Будучи мощным инструментом разработки RAD, Delphi всегда имел свои уникальные преимущества при разработке прикладного программного обеспечения. Это преимущество также находит свое отражение в разработке программного обеспечения для работы с изображениями. Если вы хотите разместить изображение на рабочем столе, вам нужно всего лишь разместить на рабочем столе элемент управления Image, а затем вы сможете произвольно загружать изображения в форматах BMP, WMF, EMF и других через его свойство Image. Если вы также хотите добавить поддержку JPEG, вам нужно добавить только модуль JPEG. Даже после загрузки JPEG в Image Delphi автоматически добавит модуль JPEG. Все так просто сделать. Основные форматы инкапсулированы в VCL, так как же Delphi реализует поддержку таких форматов изображений, как JPEG?
На самом деле процесс реализации легко увидеть на примере TPicture, который можно понимать как контейнер для всех объектов изображения.
Например, в JPEG.pas есть следующие две строки кода:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = «Файл изображения JPEG», см. JConsts.pas)
Что это значит? Его можно понимать как класс, который регистрирует TJPEGImage как файл изображения с двумя суффиксами: jpeg и jpg.
Суть заключается в сохранении суффикса, описания изображения, конкретного класса анализа изображения и другой информации в FileFormats.
Подробности смотрите в следующем коде:
вар FileFormats: TFileFormatsList = ноль;
класс PROcedure TPicture.RegisterFileFormat(const AExtension,
ADescription: строка; AGraphicClass: TGraphicClass);
начинать
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
конец;
функция GetFileFormats: TFileFormatsList;
начинать
если FileFormats = ноль, то FileFormats := TFileFormatsList.Create;
Результат:= FileFormats;
конец;
TPicture по умолчанию поддерживает четыре формата изображений, поскольку они были добавлены в конструктор TFileFormatsList.
конструктор TFileFormatsList.Create;
начинать
унаследовано Создать;
Добавить('wmf', SVMetafiles, 0, TMetafile);
Добавить('emf', SVEnhMetafiles, 0, TMetafile);
Добавить('ico', SVIcons, 0, TIcon);
Добавить('bmp', SVBitmaps, 0, TBitmap);
конец;
Именно на основе информации, сохраненной в FileFormats, элемент управления OpenPictureDialog автоматически генерирует список поддерживаемых типов файлов.
Итак, как написать эти классы анализа изображений?
TGraphic — это базовый класс объектов TBitmap, TIcon и TMetafile. Аналогично, класс анализа изображений здесь также должен быть производным от TGraphic. Он может сэкономить много работы, используя большой объем кода, инкапсулированного в VCL.
Для реализации базовых функций обычно нужно перегрузить только три члена:
TXXXImage = класс (TGraphic)
защищенный
процедура Draw(ACanvas: TCanvas; const Rect: TRect); //Рисуем изображение на холсте;
общественный
процедура LoadFromStream(Stream: TStream); //Получаем данные изображения из потока;
процедура SaveToStream(Stream: TStream); //Запись данных изображения в поток;
конец;
Поскольку в TGraphic.LoadFromFile/TGraphic.SaveToFile уже реализована функция чтения данных из имени файла в поток/записи данных в потоке в соответствующий файл, нет необходимости перегружать его без особой необходимости. Член Draw естественным образом используется для рисования изображений на холсте. Благодаря идеальной инкапсуляции GDI в TCanvas нет необходимости рассматривать процесс рисования изображений в форме с помощью GDI. Осталось только написать код для части анализа изображений.
Давайте возьмем формат RAS в качестве примера для дальнейшего обсуждения.
Здесь в качестве базового класса не используется TGraphic, а используется TBitmap. Это дополнительно экономит процесс реализации Draw и нужно лишь реализовать процесс преобразования в растровое изображение в LoadFromStream.
тип
TRASGraphic = класс (TBitmap)
общественный
процедура LoadFromStream (поток: переопределение TStream);
процедура SaveToStream (поток: TStream переопределить);
конец;
//Определяем тип записи, описывающий заголовок файла RAS
TRASHeader = упакованная запись
Магия, //метка
Ширина, //ширина
Высота, //высокая
Глубина, //глубина цвета
Длина, //длина данных изображения, может быть равна 0
RasType, //Тип формата
MapType, //Тип палитры
MapLength: Cardinal // Длина данных палитры;
конец;
//Очень необходимо определить тип записи, используемый для описания заголовка файла RAS
константа
//Определяем константы, представляющие все типы RAS
РТ_ОЛД = 0;
РТ_СТАНДАРТ = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_EXPERIMENTAL = $FFFF;
//Определяем константы, представляющие типы палитр
RMT_NONE = 0 //Нет данных палитры
RMT_EQUAL_RGB = 1;
РМТ_РАВ = 2;
{Если формат RAS — RT_OLD, длина данных может быть равна 0}
функция SwapLong(const Value: Cardinal): Cardinal;
асм
BSWAP EAX//Вызов команды обмена байтами
конец;
//Вызываем исключение, параметр представляет собой конкретную информацию об исключении
процедура RasError (const ErrorString: String);
начинать
поднять EInvalidGraphic.Create(ErrorString);
конец;
{Ниже приведен код реализации. }
процедура TRASGraphic.LoadFromStream(Stream: TStream);
вар
Заголовок: TRASHeader;
Строка8: Пбайт;
Ряд 24: PRGBТройной;
Строка 32: PRGBQuad;
PMap: Pбайт;
Д: целое число;
Я: целое число;
MapReaded: логическое значение;
приятель: TMaxLogPalette;
R,G,B:массив[0..255] байтов;
ЦветБайт: Байт;
начинать
со Стримом сделать
начинать
ReadBuffer(Header, SizeOf(Header)); //Читаем данные заголовка файла в заголовок записи
с заголовком сделать
начинать
Ширина: = SwapLong(Ширина);
Высота: = SwapLong(Высота);
Глубина: = SwapLong(Глубина);
Длина: = SwapLong(Длина);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
конец;
//Из-за порядка чтения данных вам необходимо вызвать указанный выше SwapLong, чтобы изменить порядок.
if (Header.Magic = $956AA659) и
(Header.Width<>0) и (Header.Height<>0) и
(Header.Depth в [1,8,24,32]) и (Header.RasType в [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]), затем
начинать
Ширина := Заголовок.Ширина;
Высота: = Заголовок.Высота;
MapReaded := Ложь;
Заголовок дела. Глубина
1:PixelFormat:= pf1Bit;
8:
начинать
Пиксельный формат: = pf8Bit;
случай Header.MapType of
РМТ_НЕТ:
начинать
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
для I := от 0 до 255 делаю
начинать
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
конец;
Палитра := CreatePalette(PLogPalette(@Pal)^);
//Когда глубина цвета изображения составляет 8 бит и информация о палитре отсутствует, создаем 8-битную палитру в оттенках серого
конец;
RMT_EQUAL_RGB:
начинать
если (Header.MapLength = 3*256), то
начинать
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
ЧитатьБуфер(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
для I := от 0 до 255 делаю
начинать
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
конец;
Палитра := CreatePalette(PLogPalette(@Pal)^);
//Читаем информацию о палитре в файле
//Для операций с палитрой, связанных с API, проверьте MSDN
конец
еще
RasError('Неправильная длина палитры!');
MapReaded := Истина;
конец;
РМТ_РАВ:
начинать
RasError('Неподдерживаемый формат файла!');
конец;
конец;
конец;
24:PixelFormat:= pf24Bit;
32:
начинать
Пиксельный формат: = pf32Bit;
//
конец;
конец;
если (не MapReaded) и (Header.MapLength>0), то
начинать
Позиция := Позиция + Header.MapLength;
конец;
//Если длина палитры не равна 0 и соответствующая информация считывается неправильно, пропустите этот фрагмент данных
Заголовок дела. Глубина
8:
начинать
если Header.RasType = RT_BYTE_ENCODED, то
начинать
//КОДИРОВАТЬ
//Пожалуйста, проверьте информацию самостоятельно о кодировании и декодировании сжатия RLE.
RasError('Формат сжатия не поддерживается!');
конец
еще
начинать
для Y := 0 до высоты-1 сделать
начинать
Строка8:=СканироватьЛинию[Y];
ReadBuffer(Row8^,Width);
если (Ширина mod 2)=1, то
начинать
Позиция := Позиция + 1;
конец;
конец;
конец;
конец;{конец 8-битного}
двадцать четыре:
начинать
Заголовок дела.RasType of
RT_OLD,
РТ_СТАНДАРТ:
начинать
для Y := 0 до высоты-1 сделать
начинать
Строка24:=СканироватьЛинию[Y];
ReadBuffer(Row24^,Width*3);
если (Ширина mod 2)=1, то
начинать
Позиция := Позиция + 1;
конец;
конец;
конец;
RT_BYTE_ENCODED:
начинать
//КОДИРОВАТЬ
//Пожалуйста, проверьте информацию самостоятельно о кодировании и декодировании сжатия RLE.
RasError('Формат сжатия не поддерживается!');
конец;
RT_FORMAT_RGB:
начинать
для Y := 0 до высоты-1 сделать
начинать
Строка24:=СканироватьЛинию[Y];
ReadBuffer(Row24^,Width*3);
для I := 0 до Width-1 сделать
начинать
ColorByte := Row24^.rgbtRed;
Row24^.rgbtRed := Row24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Инк(строка24);
конец;
//Когда он в формате RT_FORMAT_RGB, получаем данные по RGB, здесь нужно поменять местами значения R и B
если (Ширина mod 2)=1, то
начинать
Позиция := Позиция + 1;
конец;
конец;
конец;{конец RT_FORMAT_RGB}
еще
RasError('Неподдерживаемый формат файла!');
конец;
конец;{конец 24 бит}
32:
начинать
Заголовок дела.RasType of
RT_OLD,
РТ_СТАНДАРТ:
начинать
для Y := 0 до высоты-1 сделать
начинать
Строка32:=СканироватьЛинию[Y];
ReadBuffer(Row32^,Width*4);
для I := 0 до Width-1 сделать
начинать
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Строка32^.rgbBlue := Строка32^.rgbGreen;
Строка32^.rgbGreen := Строка32^.rgbRed;
Row32^.rgbRed := ColorByte;
Инк(строка32);
конец;
//При использовании 32-битного цвета необходимо настроить порядок данных после чтения.
конец;
конец;
RT_BYTE_ENCODED:
начинать
//КОДИРОВАТЬ
//Пожалуйста, проверьте информацию самостоятельно о кодировании и декодировании сжатия RLE.
RasError('Формат сжатия не поддерживается!');
конец;
RT_FORMAT_RGB:
начинать
Для Y := от 0 до высоты-1 выполните
начинать
Строка32:=СканироватьЛинию[Y];
ReadBuffer(Row32^,Width*4);
для I := 0 до Width-1 сделать
начинать
ColorByte := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Строка32^.rgbGreen := Строка32^.rgbRed;
Row32^.rgbRed := ColorByte;
Инк(строка32);
конец;
//Здесь объединены коды корректировки заказа и обмена значениями R и B.
конец;
конец;{конец RT_FORMAT_RGB}
еще
RasError('Неподдерживаемый формат файла!');
конец;{конец 32-битного}
конец;
еще
начинать
Бесплатное изображение;
RasError('Неподдерживаемый формат файла!');
конец;
конец;
конец
еще
RasError('Неподдерживаемый формат файла!');
конец;{конец на}
конец;
{Следующий код встречается несколько раз в приведенном выше коде:
если (Ширина mod 2)=1, то
начинать
Позиция := Позиция + 1;
конец;
Это связано с тем, что данные в каждой строке должны быть выровнены по словам, то есть данные в каждой строке должны быть записаны с четным числом байтов. Когда информация о цвете каждого пикселя записана в 1 байте (8 бит) или 3 байтах (24 бита), а количество пикселей в каждой строке является нечетным числом, один байт необходимо дополнить. Таким образом, здесь пропускается один байт.
в коде позади
если (Ширина mod 2) = 1, то
начинать
ЗаполнитьБайт:=0;
Stream.Write(FillByte,1);
конец;
Оно также основано на том же принципе. }
процедура TRASGraphic.SaveToStream(Stream: TStream);
вар
Заголовок: TRASHeader;
Строка8: Пбайт;
Ряд 24: PRGBТройной;
Строка 32: PRGBQuad;
ЗаполнитьБайт: Байт;
Д: целое число;
Я: целое число;
приятель: TMaxLogPalette;
R,G,B:массив[0..255] байтов;
начинать
Header.Magic:= $956AA659;
Header.Width := SwapLong(Width);
Header.Height := SwapLong(Height);
Header.RasType := SwapLong(RT_STANDARD);
если (PixelFormat = pf1bit) или (PixelFormat = pf4bit), то
ПиксельФормат:=pf8bit
иначе, если (PixelFormat <> pf8bit) и (PixelFormat <> pf24bit) и (PixelFormat <> pf32bit), то
PixelFormat:=pf24bit;
случай PixelFormat of
пф8бит:
начинать
Header.Length := SwapLong(Height*(Width+(Width mod 2)));
Header.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(Заголовок,SizeOf(Заголовок));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
для I := от 0 до 255 делаю
начинать
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
конец;
//Для операций с палитрой, связанных с API, проверьте MSDN
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
для Y := 0 до высоты-1 сделать
начинать
Строка8 := ScanLine[Y];
Stream.WriteBuffer(Row8^,Width);
если (Ширина mod 2) = 1, то
начинать
ЗаполнитьБайт:=0;
Stream.Write(FillByte,1);
конец;
конец;
конец;
пф32бит:
начинать
Header.Length := SwapLong(Высота*Ширина*4);
Header.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Заголовок,SizeOf(Заголовок));
для Y := 0 до высоты-1 сделать
начинать
Строка32 := ScanLine[Y];
для I := 0 до Width-1 сделать
начинать
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Инк(строка32);
конец;
конец;
конец;
еще
начинать
Header.Length := SwapLong(Высота*Ширина*3);
Header.Depth := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Заголовок,SizeOf(Заголовок));
для Y := 0 до высоты-1 сделать
начинать
Строка24 := ScanLine[Y];
Stream.WriteBuffer(Row24^,Width*3);
если (Ширина mod 2) = 1, то
начинать
ЗаполнитьБайт:=0;
Stream.Write(FillByte,1);
конец;
конец;
конец;
конец;
//SaveToStream — это, по сути, процесс, обратный LoadFromStream.
конец;
инициализация
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
завершение
TPicture.UnregisterGraphicClass(TRASGraphic);
С помощью этих нескольких строк кода завершается полный компонент анализа изображений.