So schreiben Sie eine Bildanalysekomponente in Delphi
Als leistungsstarkes RAD-Entwicklungstool hatte Delphi schon immer seine einzigartigen Vorteile bei der Entwicklung von Anwendungssoftware. Dieser Vorteil spiegelt sich auch in der Entwicklung bildbezogener Software wider. Wenn Sie ein Bild auf dem Desktop platzieren möchten, müssen Sie lediglich ein Bildsteuerelement auf dem Desktop platzieren und können dann über die Image-Eigenschaft beliebige Bilder in BMP, WMF, EMF und anderen Formaten laden. Wenn Sie auch Unterstützung für JPEG hinzufügen möchten, müssen Sie nur eine JPEG-Einheit hinzufügen. Auch nach dem Laden eines JPEG in Image fügt Delphi automatisch eine JPEG-Einheit hinzu. Alles ist so einfach zu machen. Die Grundformate wurden in VCL gekapselt. Wie implementiert Delphi also die Unterstützung für Bildformate wie JPEG?
Tatsächlich ist der Implementierungsprozess von TPicture leicht zu erkennen, das als Container für alle Bildobjekte verstanden werden kann.
Beispielsweise gibt es in JPEG.pas die folgenden zwei Codezeilen:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'JPEG Image File', siehe JConsts.pas)
Was bedeutet es? Es kann als eine Klasse verstanden werden, die TJPEGImage als Bilddatei mit zwei Suffixen registriert: jpeg und jpg.
Der Kern besteht darin, das Suffix, die Bildbeschreibung, die spezifische Bildanalyseklasse und andere Informationen in FileFormats zu speichern.
Weitere Informationen finden Sie im folgenden Code:
var FileFormats: TFileFormatsList = nil;
Klasse PROcedure TPicture.RegisterFileFormat(const AExtension,
ADescription: string; AGraphicClass: TGraphicClass);
beginnen
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
Ende;
Funktion GetFileFormats: TFileFormatsList;
beginnen
if FileFormats = nil then FileFormats := TFileFormatsList.Create;
Ergebnis := FileFormats;
Ende;
TPicture unterstützt standardmäßig vier Bildformate, da diese im Konstruktor von TFileFormatsList hinzugefügt wurden.
Konstruktor TFileFormatsList.Create;
beginnen
geerbtes Erstellen;
Add('wmf', SVMetafiles, 0, TMetafile);
Add('emf', SVEnhMetafiles, 0, TMetafile);
Add('ico', SVIcons, 0, TIcon);
Add('bmp', SVBitmaps, 0, TBitmap);
Ende;
Anhand der in FileFormats gespeicherten Informationen generiert das Steuerelement OpenPictureDialog automatisch eine Liste der unterstützten Dateitypen.
Wie schreibt man diese Bildanalyseklassen?
TGraphic ist die Basisklasse der TBitmap-, TIcon- und TMetafile-Objekte. Ebenso sollte die Bildanalyseklasse hier ebenfalls von TGraphic abgeleitet sein. Sie kann viel Arbeit sparen, indem viel Code verwendet wird, der in VCL gekapselt wurde.
Um Grundfunktionen zu implementieren, müssen Sie im Allgemeinen nur drei Mitglieder überladen:
TXXXImage = class(TGraphic)
geschützt
procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;//Zeichne das Bild auf die Leinwand
öffentlich
procedure LoadFromStream(Stream: TStream); //Bilddaten aus dem Stream abrufen
procedure SaveToStream(Stream: TStream); //Bilddaten in den Stream schreiben
Ende;
Da TGraphic.LoadFromFile/TGraphic.SaveToFile die Funktion zum Lesen von Daten aus dem Dateinamen in den Stream/Schreiben der Daten im Stream in die entsprechende Datei bereits implementiert hat, besteht keine Notwendigkeit, sie ohne besondere Anforderungen zu überladen. Der Member Draw wird natürlich zum Zeichnen von Bildern auf die Leinwand verwendet. Aufgrund der perfekten Kapselung von GDI durch TCanvas besteht keine Notwendigkeit, den Prozess zum Zeichnen von Bildern in das Formular mithilfe von GDI zu berücksichtigen. Jetzt müssen Sie nur noch den Code für den Bildanalyseteil schreiben.
Nehmen wir zur weiteren Diskussion das RAS-Format als Beispiel.
TGraphic wird hier nicht als Basisklasse verwendet, sondern TBitmap. Dadurch wird der Implementierungsprozess von Draw weiter eingespart und es muss nur der Prozess der Konvertierung in Bitmap in LoadFromStream implementiert werden.
Typ
TRASGraphic = class(TBitmap)
öffentlich
procedure LoadFromStream(Stream: TStream override);
procedure SaveToStream(Stream: TStream override);
Ende;
//Definieren Sie den Datensatztyp, der den RAS-Dateiheader beschreibt
TRASHeader = gepackter Datensatz
Magie, //mark
Breite, //Breite
Höhe, //hoch
Tiefe, //Farbtiefe
Länge, //Bilddatenlänge, kann gleich 0 sein
RasType, //Formattyp
MapType, //Palettentyp
MapLength: Cardinal; //Palettendatenlänge
Ende;
//Es ist unbedingt erforderlich, einen Datensatztyp zu definieren, der zur Beschreibung des RAS-Dateiheaders verwendet wird
const
//Konstanten definieren, die alle RAS-Typen darstellen
RT_OLD = 0;
RT_STANDARD = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_EXPERIMENTAL = $FFFF;
//Konstanten definieren, die Palettentypen darstellen
RMT_NONE = 0; //Keine Palettendaten
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{Wenn das Format von RAS RT_OLD ist, kann die Datenlänge 0 sein}
function SwapLong(const Value: Cardinal): Cardinal;
asm
BSWAP EAX//Anweisung zum Aufrufen des Byte-Austauschs
Ende;
// Eine Ausnahme auslösen, der Parameter sind die spezifischen Ausnahmeinformationen
procedure RasError(const ErrorString: String);
beginnen
raise EInvalidGraphic.Create(ErrorString);
Ende;
{Das Folgende ist der Code für den Implementierungsteil. }
procedure TRASGraphic.LoadFromStream(Stream: TStream);
var
Header: TRASHeader;
Zeile8: PByte;
Zeile 24: PRGBTriple;
Zeile 32: PRGBQuad;
PMap: PByte;
Y: Ganzzahl;
I: Ganzzahl;
MapReaded: Boolean;
Kumpel: TMaxLogPalette;
R,G,B:array[0..255] of Byte;
Farbbyte: Byte;
beginnen
mit Stream tun
beginnen
ReadBuffer(Header, SizeOf(Header)); //Lesen Sie die Datei-Header-Daten in den Datensatz-Header
mit Header tun
beginnen
Breite := SwapLong(Width);
Höhe := SwapLong(Height);
Tiefe := SwapLong(Depth);
Länge := SwapLong(Länge);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
Ende;
//Aufgrund der Reihenfolge beim Lesen der Daten müssen Sie den obigen SwapLong aufrufen, um die Reihenfolge zu ändern.
if (Header.Magic = $956AA659) und
(Header.Width<>0) und (Header.Height<>0) und
(Header.Depth in [1,8,24,32]) und (Header.RasType in [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) dann
beginnen
Breite := Header.Width;
Höhe := Header.Height;
MapReaded := False;
case Header.Tiefe von
1:PixelFormat := pf1Bit;
8:
beginnen
PixelFormat := pf8Bit;
Fall Header.MapType von
RMT_NONE:
beginnen
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
für I := 0 bis 255 tun
beginnen
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
Ende;
Palette := CreatePalette(PLogPalette(@Pal)^);
//Wenn die Farbtiefe des Bildes 8 Bit beträgt und keine Paletteninformationen vorhanden sind, erstellen Sie eine 8-Bit-Graustufenpalette
Ende;
RMT_EQUAL_RGB:
beginnen
if (Header.MapLength = 3*256) dann
beginnen
Pal.palVersion:=$300;
Pal.palNumEntries:=256;
ReadBuffer(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
für I := 0 bis 255 tun
beginnen
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
Ende;
Palette := CreatePalette(PLogPalette(@Pal)^);
//Paletteninformationen in der Datei lesen
//Informationen zu API-bezogenen Palettenvorgängen finden Sie im MSDN
Ende
anders
RasError('Palettenlänge ist falsch!');
MapReaded := True;
Ende;
RMT_RAW:
beginnen
RasError('Nicht unterstütztes Dateiformat!');
Ende;
Ende;
Ende;
24:PixelFormat := pf24Bit;
32:
beginnen
PixelFormat := pf32Bit;
//
Ende;
Ende;
if (not MapReaded) und (Header.MapLength>0) then
beginnen
Position := Position + Header.MapLength;
Ende;
//Wenn die Palettenlänge nicht 0 ist und die relevanten Informationen nicht korrekt gelesen werden, überspringen Sie dieses Datenelement
case Header.Tiefe von
8:
beginnen
wenn Header.RasType = RT_BYTE_ENCODED dann
beginnen
//KODIEREN
//Bitte überprüfen Sie selbst die Informationen zur Kodierung und Dekodierung der RLE-Komprimierung.
RasError('Komprimierungsformat nicht unterstützt!');
Ende
anders
beginnen
für Y := 0 bis Height-1 tun
beginnen
Row8:=ScanLine[Y];
ReadBuffer(Row8^,Width);
wenn (Breite mod 2)=1 dann
beginnen
Position := Position + 1;
Ende;
Ende;
Ende;
Ende;{Ende von 8Bit}
vierundzwanzig:
beginnen
case Header.RasType von
RT_OLD,
RT_STANDARD:
beginnen
für Y := 0 bis Height-1 tun
beginnen
Row24:=ScanLine[Y];
ReadBuffer(Row24^,Width*3);
wenn (Breite mod 2)=1 dann
beginnen
Position := Position + 1;
Ende;
Ende;
Ende;
RT_BYTE_ENCODED:
beginnen
//KODIEREN
//Bitte überprüfen Sie selbst die Informationen zur Kodierung und Dekodierung der RLE-Komprimierung.
RasError('Komprimierungsformat nicht unterstützt!');
Ende;
RT_FORMAT_RGB:
beginnen
für Y := 0 bis Height-1 tun
beginnen
Row24:=ScanLine[Y];
ReadBuffer(Row24^,Width*3);
für I := 0 bis Breite-1 tun
beginnen
ColorByte := Row24^.rgbtRed;
Row24^.rgbtRed := Row24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Inc(Zeile24);
Ende;
//Wenn es im RT_FORMAT_RGB-Format vorliegt, erhalten Sie die Daten per RGB. Hier müssen Sie die Werte von R und B austauschen
wenn (Breite mod 2)=1 dann
beginnen
Position := Position + 1;
Ende;
Ende;
end;{Ende von RT_FORMAT_RGB}
anders
RasError('Nicht unterstütztes Dateiformat!');
Ende;
end;{Ende von 24Bit}
32:
beginnen
case Header.RasType von
RT_OLD,
RT_STANDARD:
beginnen
für Y := 0 bis Height-1 tun
beginnen
Row32:=ScanLine[Y];
ReadBuffer(Row32^,Width*4);
für I := 0 bis Breite-1 tun
beginnen
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(Zeile32);
Ende;
//Bei Verwendung von 32-Bit-Farben müssen Sie die Reihenfolge der Daten nach dem Lesen anpassen.
Ende;
Ende;
RT_BYTE_ENCODED:
beginnen
//KODIEREN
//Bitte überprüfen Sie selbst die Informationen zur Kodierung und Dekodierung der RLE-Komprimierung.
RasError('Komprimierungsformat nicht unterstützt!');
Ende;
RT_FORMAT_RGB:
beginnen
Für Y := 0 bis Height-1 tun
beginnen
Row32:=ScanLine[Y];
ReadBuffer(Row32^,Width*4);
für I := 0 bis Breite-1 tun
beginnen
ColorByte := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc(Zeile32);
Ende;
//Die Codes zur Auftragsanpassung und zum Austausch von R- und B-Werten werden hier zusammengeführt.
Ende;
end;{Ende von RT_FORMAT_RGB}
anders
RasError('Nicht unterstütztes Dateiformat!');
end;{Ende von 32Bit}
Ende;
anders
beginnen
FreeImage;
RasError('Nicht unterstütztes Dateiformat!');
Ende;
Ende;
Ende
anders
RasError('Nicht unterstütztes Dateiformat!');
end;{end with}
Ende;
{Der folgende Code kommt im obigen Code mehrmals vor:
wenn (Breite mod 2)=1 dann
beginnen
Position := Position + 1;
Ende;
Dies liegt daran, dass die Daten in jeder Zeile wortausgerichtet sein müssen, d. h. die Daten in jeder Zeile müssen mit einer geraden Anzahl von Bytes aufgezeichnet werden. Wenn die Farbinformationen jedes Pixels in 1 Byte (8 Bit) oder 3 Byte (24 Bit) aufgezeichnet werden und die Anzahl der Pixel in jeder Zeile eine ungerade Zahl ist, muss ein Byte aufgefüllt werden. Hier wird also ein Byte übersprungen.
im Code dahinter
wenn (Breite mod 2) = 1 dann
beginnen
FillByte:=0;
Stream.Write(FillByte,1);
Ende;
Es basiert auch auf dem gleichen Prinzip. }
procedure TRASGraphic.SaveToStream(Stream: TStream);
var
Header: TRASHeader;
Zeile8: PByte;
Zeile 24: PRGBTriple;
Zeile 32: PRGBQuad;
FillByte: Byte;
Y: Ganzzahl;
I: Ganzzahl;
Kumpel: TMaxLogPalette;
R,G,B:array[0..255] of Byte;
beginnen
Header.Magic := $956AA659;
Header.Width := SwapLong(Width);
Header.Height := SwapLong(Height);
Header.RasType := SwapLong(RT_STANDARD);
if (PixelFormat = pf1bit) oder (PixelFormat = pf4bit) then
PixelFormat:=pf8bit
sonst wenn (PixelFormat <> pf8bit) und (PixelFormat <> pf24bit) und (PixelFormat <> pf32bit) dann
PixelFormat:=pf24bit;
Fall PixelFormat von
pf8bit:
beginnen
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(Header,SizeOf(Header));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
für I := 0 bis 255 tun
beginnen
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
Ende;
//Informationen zu API-bezogenen Palettenvorgängen finden Sie im MSDN
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
für Y := 0 bis Height-1 tun
beginnen
Row8 := ScanLine[Y];
Stream.WriteBuffer(Row8^,Width);
wenn (Breite mod 2) = 1 dann
beginnen
FillByte:=0;
Stream.Write(FillByte,1);
Ende;
Ende;
Ende;
pf32bit:
beginnen
Header.Length := SwapLong(Height*Width*4);
Header.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
für Y := 0 bis Height-1 tun
beginnen
Row32 := ScanLine[Y];
für I := 0 bis Breite-1 tun
beginnen
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc(Zeile32);
Ende;
Ende;
Ende;
anders
beginnen
Header.Length := SwapLong(Height*Width*3);
Header.Depth := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Header,SizeOf(Header));
für Y := 0 bis Height-1 tun
beginnen
Row24 := ScanLine[Y];
Stream.WriteBuffer(Row24^,Width*3);
wenn (Breite mod 2) = 1 dann
beginnen
FillByte:=0;
Stream.Write(FillByte,1);
Ende;
Ende;
Ende;
Ende;
//SaveToStream ist im Grunde der umgekehrte Prozess von LoadFromStream.
Ende;
Initialisierung
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
Finalisierung
TPicture.UnregisterGraphicClass(TRASGraphic);
Mit diesen wenigen Codezeilen ist eine vollständige Bildanalysekomponente fertiggestellt.