Comment écrire un composant d'analyse d'image dans Delphi
En tant que puissant outil de développement RAD, Delphi a toujours eu ses avantages uniques dans le développement de logiciels d'application. Cet avantage se reflète également dans le développement de logiciels liés à l'image. Si vous souhaitez placer une image sur le bureau, il vous suffit de placer un contrôle Image sur le bureau, puis vous pouvez charger arbitrairement des images aux formats BMP, WMF, EMF et autres via sa propriété Image. Si vous souhaitez ajouter la prise en charge du JPEG, il vous suffit d'ajouter une unité JPEG. Même après avoir chargé un JPEG dans Image, Delphi ajoutera automatiquement une unité JPEG. Tout est si simple à faire. Les formats de base ont été encapsulés dans VCL, alors comment Delphi implémente-t-il la prise en charge des formats d'image comme JPEG ?
En fait, il est facile de voir le processus d'implémentation à partir de TPicture, qui peut être compris comme un conteneur pour tous les objets image.
Par exemple, il y a les deux lignes de code suivantes dans JPEG.pas :
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'JPEG Image File', voir JConsts.pas)
Qu'est-ce que ça veut dire? Il peut être compris comme une classe qui enregistre TJPEGImage en tant que fichier image avec deux suffixes : jpeg et jpg.
L'essentiel est de sauvegarder le suffixe, la description de l'image, la classe d'analyse d'image spécifique et d'autres informations dans FileFormats.
Voir le code suivant pour plus de détails :
var FileFormats : TFileFormatsList = néant ;
classe PRocédure TPicture.RegisterFileFormat(const AExtension,
ADescription : chaîne ; AGraphicClass : TGraphicClass);
commencer
GetFileFormats.Add(AEExtension, ADescription, 0, AGraphicClass);
fin;
fonction GetFileFormats : TFileFormatsList ;
commencer
si FileFormats = nil alors FileFormats := TFileFormatsList.Create;
Résultat := FileFormats ;
fin;
TPicture prend en charge quatre formats d'image par défaut car ils ont été ajoutés dans le constructeur de TFileFormatsList.
constructeur TFileFormatsList.Create ;
commencer
hérité Créer;
Ajouter('wmf', SVMetafiles, 0, TMetafile);
Add('emf', SVEnhMetafiles, 0, TMetafile);
Ajouter('ico', SVIcons, 0, TIcon);
Ajouter('bmp', SVBitmaps, 0, TBitmap);
fin;
C'est grâce aux informations enregistrées dans FileFormats que le contrôle OpenPictureDialog génère automatiquement une liste des types de fichiers pris en charge.
Alors, comment écrire ces cours d’analyse d’images ?
TGraphic est la classe de base des objets TBitmap, TIcon et TMetafile. De même, la classe d'analyse d'images ici doit également être dérivée de TGraphic. Elle peut économiser beaucoup de travail en utilisant beaucoup de code encapsulé dans VCL.
Pour implémenter les fonctions de base, il suffit généralement de surcharger trois membres :
TXXXImage = classe (TGraphic)
protégé
procédure Draw(ACanvas: TCanvas; const Rect: TRect);//Dessine l'image sur le canevas
publique
procédure LoadFromStream(Stream: TStream); //Obtenir les données d'image du flux
procédure SaveToStream(Stream: TStream); //Écrire les données d'image dans le flux
fin;
Étant donné que TGraphic.LoadFromFile/TGraphic.SaveToFile a déjà implémenté la fonction de lecture des données du nom de fichier dans le flux/d'écriture des données du flux dans le fichier correspondant, il n'est pas nécessaire de le surcharger sans besoins particuliers. Le membre Draw est naturellement utilisé pour dessiner des images sur le canevas. En raison de l'encapsulation parfaite de GDI par TCanvas, il n'est pas nécessaire de considérer le processus de dessin d'images sur le formulaire à l'aide de GDI. Il ne reste plus qu'à écrire le code pour la partie analyse d'image.
Prenons le format RAS comme exemple pour une discussion plus approfondie.
TGraphic n'est pas utilisé comme classe de base ici, mais TBitmap est utilisé. Cela permet d'économiser davantage le processus d'implémentation de Draw et n'a besoin que d'implémenter le processus de conversion en bitmap dans LoadFromStream.
taper
TRASGraphic = classe (TBitmap)
publique
procédure LoadFromStream(Stream : TStream);
procédure SaveToStream(Stream : TStream);
fin;
//Définit le type d'enregistrement décrivant l'en-tête du fichier RAS
TRASHheader = enregistrement compressé
Magie, //marque
Largeur, //largeur
Hauteur, //haut
Profondeur, //profondeur de couleur
Longueur, //longueur des données d'image, peut être égale à 0
RasType, //Type de format
MapType, //type de palette
MapLength : Cardinal ; //Longueur des données de la palette
fin;
//Il est très nécessaire de définir un type d'enregistrement utilisé pour décrire l'en-tête du fichier RAS
const
//Définir des constantes représentant tous les types de RAS
RT_ANCIEN = 0 ;
RT_STANDARD = 1 ;
RT_BYTE_ENCODED = 2 ;
RT_FORMAT_RVB = 3 ;
RT_FORMAT_TIFF = 4 ;
RT_FORMAT_IFF = 5 ;
RT_EXPERIMENTAL = $FFFF ;
//Définit des constantes représentant les types de palette
RMT_NONE = 0 ; //Aucune donnée de palette
RMT_EQUAL_RVB = 1 ;
RMT_RAW = 2 ;
{Si le format de RAS est RT_OLD, la longueur des données peut être 0}
fonction SwapLong (valeur const : cardinal) : cardinal ;
asme
BSWAP EAX//Instruction d'échange d'octets d'appel
fin;
//Lance une exception, le paramètre est les informations d'exception spécifiques
procédure RasError(const ErrorString: String);
commencer
augmenter EInvalidGraphic.Create(ErrorString);
fin;
{Ce qui suit est le code de la partie implémentation. }
procédure TRASGraphic.LoadFromStream(Stream : TStream);
var
En-tête : TRASHHeader ;
Ligne 8 : Poctet ;
Rangée 24 : PRGBTriple ;
Rangée 32 : PRGBQuad ;
PMap : Poctet ;
Y : entier ;
I : Entier ;
MapReaded : booléen ;
Copain : TMaxLogPalette ;
R, V, B : tableau [0..255] d'octets ;
ColorByte : octet ;
commencer
avec Stream faire
commencer
ReadBuffer(Header, SizeOf(Header)); //Lire les données d'en-tête du fichier dans l'enregistrement Header
avec l'en-tête faire
commencer
Largeur := SwapLong(Largeur);
Hauteur := SwapLong(Hauteur);
Profondeur := SwapLong(Profondeur);
Longueur := SwapLong(Longueur);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
MapLength := SwapLong(MapLength);
fin;
//En raison de l'ordre de lecture des données, vous devez appeler le SwapLong ci-dessus pour modifier l'ordre.
si (Header.Magic = 956AA659 $) et
(Header.Width<>0) et (Header.Height<>0) et
(Header.Depth dans [1,8,24,32]) et (Header.RasType dans [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) puis
commencer
Largeur := En-tête.Largeur;
Hauteur := Header.Height;
MapReaded := Faux;
case En-tête.Profondeur de
1:PixelFormat := pf1Bit;
8 :
commencer
FormatPixel := pf8Bit ;
case Header.MapType de
RMT_NONE :
commencer
Pal.palVersion : = 300 $ ;
Pal.palNumEntries :=256 ;
pour I := 0 à 255 do
commencer
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
fin;
Palette := CreatePalette(PLogPalette (@Pal)^);
//Lorsque la profondeur de couleur de l'image est de 8 bits et qu'aucune information de palette n'existe, créez une palette de niveaux de gris de 8 bits
fin;
RMT_EQUAL_RVB :
commencer
si (Header.MapLength = 3*256) alors
commencer
Pal.palVersion : = 300 $ ;
Pal.palNumEntries :=256 ;
ReadBuffer(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
pour I := 0 à 255 do
commencer
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
fin;
Palette := CreatePalette(PLogPalette (@Pal)^);
//Lire les informations de la palette dans le fichier
//Pour les opérations de palette liées à l'API, veuillez consulter MSDN
fin
autre
RasError('La longueur de la palette est incorrecte !');
MapReaded := Vrai ;
fin;
RMT_RAW :
commencer
RasError('Format de fichier non pris en charge !');
fin;
fin;
fin;
24:PixelFormat := pf24Bit;
32 :
commencer
FormatPixel := pf32Bit ;
//
fin;
fin;
si (pas MapReaded) et (Header.MapLength>0) alors
commencer
Position := Position + Header.MapLength;
fin;
//Si la longueur de la palette n'est pas 0 et que les informations pertinentes ne sont pas lues correctement, ignorez cette donnée
case En-tête.Profondeur de
8 :
commencer
si Header.RasType = RT_BYTE_ENCODED alors
commencer
//ENCODER
//Veuillez vérifier vous-même les informations sur l'encodage et le décodage de la compression RLE.
RasError('Format de compression non pris en charge !');
fin
autre
commencer
pour Y := 0 à Hauteur-1 faire
commencer
Ligne8 : = ScanLine[Y] ;
ReadBuffer(Rangée8^,Largeur);
si (Largeur mod 2)=1 alors
commencer
Position := Position + 1 ;
fin;
fin;
fin;
fin ;{fin de 8 bits}
vingt-quatre:
commencer
case Header.RasType de
RT_OLD,
RT_STANDARD :
commencer
pour Y := 0 à Hauteur-1 faire
commencer
Ligne24 :=ScanLine[Y] ;
ReadBuffer(Rangée24^,Largeur*3);
si (Largeur mod 2)=1 alors
commencer
Position := Position + 1 ;
fin;
fin;
fin;
RT_BYTE_ENCODED :
commencer
//ENCODER
//Veuillez vérifier vous-même les informations sur l'encodage et le décodage de la compression RLE.
RasError('Format de compression non pris en charge !');
fin;
RT_FORMAT_RVB :
commencer
pour Y := 0 à Hauteur-1 faire
commencer
Ligne24 :=ScanLine[Y] ;
ReadBuffer(Rangée24^,Largeur*3);
pour I := 0 à Largeur-1 faire
commencer
ColorByte := Row24^.rgbtRed ;
Rangée24^.rgbtRed := Rangée24^.rgbtBlue;
Row24^.rgbtBlue := ColorByte;
Inc (ligne 24) ;
fin;
//Quand il est au format RT_FORMAT_RGB, récupérez les données par RVB, ici vous devez échanger les valeurs de R et B
si (Largeur mod 2)=1 alors
commencer
Position := Position + 1 ;
fin;
fin;
fin ;{fin de RT_FORMAT_RGB}
autre
RasError('Format de fichier non pris en charge !');
fin;
fin ;{fin de 24 bits}
32 :
commencer
case Header.RasType de
RT_OLD,
RT_STANDARD :
commencer
pour Y := 0 à Hauteur-1 faire
commencer
Ligne32 :=ScanLine[Y] ;
ReadBuffer(Rangée32^,Largeur*4);
pour I := 0 à Largeur-1 faire
commencer
ColorByte := Row32^.rgbReserved;
Row32^.rgbReserved := Row32^.rgbBlue;
Row32^.rgbBlue := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc (ligne 32) ;
fin;
//Lorsque vous utilisez des couleurs 32 bits, vous devez ajuster l'ordre des données après la lecture.
fin;
fin;
RT_BYTE_ENCODED :
commencer
//ENCODER
//Veuillez vérifier vous-même les informations sur l'encodage et le décodage de la compression RLE.
RasError('Format de compression non pris en charge !');
fin;
RT_FORMAT_RVB :
commencer
Pour Y := 0 à Hauteur-1 faire
commencer
Ligne32 :=ScanLine[Y] ;
ReadBuffer(Rangée32^,Largeur*4);
pour I := 0 à Largeur-1 faire
commencer
ColorByte := Row32^.rgbBlue ;
Row32^.rgbBlue := Row32^.rgbReserved;
Row32^.rgbReserved := ColorByte;
ColorByte := Row32^.rgbGreen;
Row32^.rgbGreen := Row32^.rgbRed;
Row32^.rgbRed := ColorByte;
Inc (ligne 32) ;
fin;
//Les codes d'ajustement des commandes et d'échange des valeurs R et B sont fusionnés ici.
fin;
fin ;{fin de RT_FORMAT_RGB}
autre
RasError('Format de fichier non pris en charge !');
fin ;{fin de 32 bits}
fin;
autre
commencer
Image gratuite ;
RasError('Format de fichier non pris en charge !');
fin;
fin;
fin
autre
RasError('Format de fichier non pris en charge !');
fin ;{fin par}
fin;
{Le code suivant apparaît plusieurs fois dans le code ci-dessus :
si (Largeur mod 2)=1 alors
commencer
Position := Position + 1 ;
fin;
En effet, les données de chaque ligne doivent être alignées sur les mots, c'est-à-dire que les données de chaque ligne doivent être enregistrées avec un nombre pair d'octets. Lorsque les informations de couleur de chaque pixel sont enregistrées sur 1 octet (8 bits) ou 3 octets (24 bits) et que le nombre de pixels dans chaque ligne est un nombre impair, un octet doit être complété. Un octet est donc ignoré ici.
dans le code derrière
si (Largeur mod 2) = 1 alors
commencer
Octet de remplissage :=0 ;
Stream.Write(FillByte,1);
fin;
Il repose également sur le même principe. }
procédure TRASGraphic.SaveToStream(Stream : TStream);
var
En-tête : TRASHheader ;
Ligne 8 : Poctet ;
Ligne 24 : PRGBTriple ;
Rangée 32 : PRGBQuad ;
FillByte : octet ;
Y : entier ;
I : Entier ;
Copain : TMaxLogPalette ;
R, V, B : tableau [0..255] d'octets ;
commencer
En-tête.Magic := 956 AA659 $ ;
Header.Width := SwapLong(Largeur);
Header.Height := SwapLong(Hauteur);
Header.RasType := SwapLong(RT_STANDARD);
si (PixelFormat = pf1bit) ou (PixelFormat = pf4bit) alors
FormatPixel :=pf8bit
sinon si (PixelFormat <> pf8bit) et (PixelFormat <> pf24bit) et (PixelFormat <> pf32bit) alors
FormatPixel :=pf24bit ;
cas PixelFormat de
pf8bit :
commencer
Header.Length := SwapLong(Hauteur*(Largeur+(Largeur mod 2)));
Header.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(En-tête,SizeOf(En-tête));
GetPaletteEntries(Palette, 0, 256, Pal.palPalEntry);
pour I := 0 à 255 do
commencer
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
fin;
//Pour les opérations de palette liées à l'API, veuillez consulter MSDN
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
pour Y := 0 à Hauteur-1 faire
commencer
Ligne8 := ScanLine[Y];
Stream.WriteBuffer(Row8^,Width);
si (Largeur mod 2) = 1 alors
commencer
Octet de remplissage :=0 ;
Stream.Write(FillByte,1);
fin;
fin;
fin;
pf32bit :
commencer
Header.Length := SwapLong(Hauteur*Largeur*4);
En-tête.Profondeur := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(En-tête,SizeOf(En-tête));
pour Y := 0 à Hauteur-1 faire
commencer
Ligne32 := ScanLine[Y] ;
pour I := 0 à Largeur-1 faire
commencer
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc (ligne 32) ;
fin;
fin;
fin;
autre
commencer
Header.Length := SwapLong(Hauteur*Largeur*3);
En-tête.Profondeur := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(En-tête,SizeOf(En-tête));
pour Y := 0 à Hauteur-1 faire
commencer
Ligne24 := ScanLine[Y] ;
Stream.WriteBuffer(Row24^,Width*3);
si (Largeur mod 2) = 1 alors
commencer
Octet de remplissage :=0 ;
Stream.Write(FillByte,1);
fin;
fin;
fin;
fin;
//SaveToStream est essentiellement le processus inverse de LoadFromStream.
fin;
initialisation
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
finalisation
TPicture.UnregisterGraphicClass(TRASGraphic);
Avec ces quelques lignes de code, un composant complet d’analyse d’image est complété.