Cómo escribir un componente de análisis de imágenes en Delphi
Como poderosa herramienta de desarrollo RAD, Delphi siempre ha tenido sus ventajas únicas en el desarrollo de software de aplicaciones. Esta ventaja también se refleja en el desarrollo de software relacionado con la imagen. Si desea colocar una imagen en el escritorio, solo necesita colocar un control Imagen en el escritorio y luego podrá cargar imágenes arbitrariamente en BMP, WMF, EMF y otros formatos a través de su propiedad Imagen. Si también desea agregar soporte para JPEG, solo necesita agregar una unidad JPEG. Incluso después de cargar un JPEG en Imagen, Delphi agregará automáticamente una unidad JPEG. Todo es tan sencillo de hacer. Los formatos básicos se han encapsulado en VCL, entonces, ¿cómo implementa Delphi el soporte para formatos de imagen como JPEG?
De hecho, es fácil ver el proceso de implementación desde TPicture, que puede entenderse como un contenedor para todos los objetos de imagen.
Por ejemplo, existen las siguientes dos líneas de código en JPEG.pas:
TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'Archivo de imagen JPEG', consulte JConsts.pas)
¿Qué significa? Puede entenderse como una clase que registra TJPEGImage como un archivo de imagen con dos sufijos: jpeg y jpg.
La esencia es guardar el sufijo, la descripción de la imagen, la clase de análisis de imagen específica y otra información en FileFormats.
Consulte el siguiente código para obtener más detalles:
var FileFormats: TFileFormatsList = nil;
clase PROcedimiento TPicture.RegisterFileFormat(const AExtension,
ADescripción: cadena; AGraphicClass: TGraphicClass);
comenzar
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
fin;
función GetFileFormats: TFileFormatsList;
comenzar
si FileFormats = nil entonces FileFormats := TFileFormatsList.Create;
Resultado := Formatos de archivo;
fin;
TPicture admite cuatro formatos de imagen de forma predeterminada porque se agregaron en el constructor de TFileFormatsList.
constructor TFileFormatsList.Create;
comenzar
heredado Crear;
Agregar('wmf', SVMetafiles, 0, TMetafile);
Agregar('emf', SVEnhMetafiles, 0, TMetafile);
Agregar('ico', SVIcons, 0, TIcon);
Agregar('bmp', SVBitmaps, 0, TBitmap);
fin;
Es a través de la información guardada en FileFormats que el control OpenPictureDialog genera automáticamente una lista de tipos de archivos admitidos.
Entonces, ¿cómo escribir estas clases de análisis de imágenes?
TGraphic es la clase base de los objetos TBitmap, TIcon y TMetafile. De manera similar, la clase de análisis de imágenes aquí también debe derivarse de TGraphic. Puede ahorrar mucho trabajo utilizando una gran cantidad de código encapsulado en VCL.
Para implementar funciones básicas, generalmente solo necesita sobrecargar tres miembros:
TXXXImagen = clase(TGraphic)
protegido
procedimiento Draw(ACanvas: TCanvas; const Rect: TRect override;//Dibuja la imagen en el lienzo
público
procedimiento LoadFromStream(Stream: TStream override; //Obtener datos de imagen de la secuencia);
procedimiento SaveToStream(Stream: TStream override; //Escribe datos de imagen en la secuencia);
fin;
Debido a que TGraphic.LoadFromFile/TGraphic.SaveToFile ya ha implementado la función de leer datos del nombre del archivo en la secuencia/escribir los datos de la secuencia en el archivo correspondiente, no es necesario sobrecargarlo sin necesidades especiales. El miembro Draw se usa naturalmente para dibujar imágenes en el lienzo. Debido a la perfecta encapsulación de GDI de TCanvas, no es necesario considerar el proceso de cómo dibujar imágenes en el formulario usando GDI. Todo lo que queda es escribir el código para la parte de análisis de imágenes.
Tomemos el formato RAS como ejemplo para una mayor discusión.
Aquí no se usa TGraphic como clase base, pero se usa TBitmap. Esto ahorra aún más el proceso de implementación de Draw y solo necesita implementar el proceso de conversión a mapa de bits en LoadFromStream.
tipo
TRASGraphic = clase(TBitmap)
público
procedimiento LoadFromStream(Stream: TStream anulación);
procedimiento SaveToStream(Stream: TStream anulación);
fin;
//Definir el tipo de registro que describe el encabezado del archivo RAS
TRASHheader = registro empaquetado
Magia, //marca
Ancho, //ancho
altura, //alto
Profundidad, //profundidad de color
Longitud, //longitud de los datos de la imagen, puede ser igual a 0
RasType, //Tipo de formato
MapType, //tipo de paleta
MapLength: Cardinal; //Longitud de los datos de la paleta
fin;
//Es muy necesario definir un tipo de registro utilizado para describir el encabezado del archivo RAS
constante
//Definir constantes que representen todos los tipos de RAS
RT_OLD = 0;
RT_ESTÁNDAR = 1;
RT_BYTE_ENCODED = 2;
RT_FORMAT_RGB = 3;
RT_FORMAT_TIFF = 4;
RT_FORMAT_IFF = 5;
RT_EXPERIMENTAL = $FFFF;
//Definir constantes que representan tipos de paleta
RMT_NONE = 0; //Sin datos de paleta
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{Si el formato de RAS es RT_OLD, la longitud de los datos puede ser 0}
función SwapLong (valor constante: cardinal): cardinal;
ENSAMBLE
BSWAP EAX//Instrucción de intercambio de bytes de llamada
fin;
// Lanza una excepción, el parámetro es la información de excepción específica
procedimiento RasError(const ErrorString: String);
comenzar
elevar EInvalidGraphic.Create(ErrorString);
fin;
{El siguiente es el código para la parte de implementación. }
procedimiento TRASGraphic.LoadFromStream(Stream: TStream);
var
Encabezado: TRASHheader;
Fila 8: PByte;
Fila 24: PRGBTriple;
Fila 32: PRGBQuad;
PMap: PByte;
Y: Entero;
I: Entero;
MapReaded: booleano;
Pal: TMaxLogPalette;
R,G,B:matriz[0..255] de bytes;
ColorByte: Byte;
comenzar
con Stream hacer
comenzar
ReadBuffer(Header, SizeOf(Header)); //Lee los datos del encabezado del archivo en el registro Header
con encabezado hacer
comenzar
Ancho := IntercambiarLong(Ancho);
Altura := IntercambiarLong(Altura);
Profundidad: = SwapLong (Profundidad);
Longitud := IntercambiarLong(Longitud);
RASType := IntercambioLong(RASType);
Tipo de mapa := SwapLong(Tipo de mapa);
Longitud del mapa := IntercambiarLong(Longitud del mapa);
fin;
// Debido al orden de lectura de los datos, debe llamar al SwapLong anterior para cambiar el orden.
si (Header.Magic = $956AA659) y
(Encabezado.Ancho<>0) y (Encabezado.Alto<>0) y
(Header.Depth en [1,8,24,32]) y (Header.RasType en [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) luego
comenzar
Ancho: = Encabezado.Ancho;
Altura: = Encabezado.Altura;
Mapa leído := Falso;
encabezado del caso. Profundidad de
1: Formato de píxel: = pf1Bit;
8:
comenzar
Formato de píxel := pf8Bit;
caso Encabezado.MapTipo de
RMT_NINGUNO:
comenzar
Pal.palVersión:=$300;
Pal.palNumEntries:=256;
para I: = 0 a 255 hacer
comenzar
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peGreen:=I;
Pal.palPalEntry[I].peBlue:=I;
Pal.palPalEntry[I].peFlags:=0;
fin;
Paleta := CreatePalette(PLogPalette(@Pal)^);
//Cuando la profundidad de color de la imagen es de 8 bits y no existe información de paleta, crea una paleta en escala de grises de 8 bits
fin;
RMT_EQUAL_RGB:
comenzar
si (Header.MapLength = 3*256) entonces
comenzar
Pal.palVersión:=$300;
Pal.palNumEntries:=256;
LeerBuffer(R,256);
LeerBuffer(G,256);
LeerBuffer(B,256);
para I: = 0 a 255 hacer
comenzar
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peGreen:=G[I];
Pal.palPalEntry[I].peBlue:=B[I];
Pal.palPalEntry[I].peFlags:=0;
fin;
Paleta := CreatePalette(PLogPalette(@Pal)^);
//Leer la información de la paleta en el archivo
//Para operaciones de paleta relacionadas con API, consulte MSDN
fin
demás
RasError('¡La longitud de la paleta es incorrecta!');
Mapa leído: = Verdadero;
fin;
RMT_RAW:
comenzar
RasError('¡Formato de archivo no compatible!');
fin;
fin;
fin;
24: Formato de píxeles: = pf24Bit;
32:
comenzar
Formato de píxel: = pf32Bit;
//
fin;
fin;
si (no MapReaded) y (Header.MapLength>0) entonces
comenzar
Posición := Posición + Header.MapLength;
fin;
// Si la longitud de la paleta no es 0 y la información relevante no se lee correctamente, omita este dato
encabezado del caso. Profundidad de
8:
comenzar
si Header.RasType = RT_BYTE_ENCODED entonces
comenzar
//CODIFICAR
// Verifique usted mismo la información sobre la codificación y decodificación de la compresión RLE.
RasError('¡El formato de compresión no es compatible!');
fin
demás
comenzar
para Y := 0 a Altura-1 hacer
comenzar
Fila8:=Línea de exploración[Y];
ReadBuffer(Fila8^,Ancho);
si (Ancho mod 2) = 1 entonces
comenzar
Posición := Posición + 1;
fin;
fin;
fin;
fin;{fin de 8 bits}
veinticuatro:
comenzar
caso Header.RasTipo de
RT_OLD,
RT_ESTÁNDAR:
comenzar
para Y := 0 a Altura-1 hacer
comenzar
Fila24:=Línea de exploración[Y];
ReadBuffer(Fila24^,Ancho*3);
si (Ancho mod 2) = 1 entonces
comenzar
Posición := Posición + 1;
fin;
fin;
fin;
RT_BYTE_ENCODED:
comenzar
//CODIFICAR
// Verifique usted mismo la información sobre la codificación y decodificación de la compresión RLE.
RasError('¡El formato de compresión no es compatible!');
fin;
RT_FORMAT_RGB:
comenzar
para Y := 0 a Altura-1 hacer
comenzar
Fila24:=Línea de exploración[Y];
ReadBuffer(Fila24^,Ancho*3);
para I: = 0 a Ancho-1 hacer
comenzar
ColorByte: = Fila24^.rgbtRed;
Fila24^.rgbtRojo:= Fila24^.rgbtAzul;
Fila24^.rgbtBlue:= ColorByte;
Inc(Fila24);
fin;
//Cuando está en formato RT_FORMAT_RGB, obtienes los datos por RGB, aquí necesitas intercambiar los valores de R y B
si (Ancho mod 2) = 1 entonces
comenzar
Posición := Posición + 1;
fin;
fin;
fin;{fin de RT_FORMAT_RGB}
demás
RasError('¡Formato de archivo no compatible!');
fin;
fin;{fin de 24 bits}
32:
comenzar
caso Header.RasTipo de
RT_OLD,
RT_ESTÁNDAR:
comenzar
para Y := 0 a Altura-1 hacer
comenzar
Fila32:=Línea de exploración[Y];
ReadBuffer(Fila32^,Ancho*4);
para I: = 0 a Ancho-1 hacer
comenzar
ColorByte: = Fila32^.rgbReserved;
Fila32^.rgbReservado:= Fila32^.rgbBlue;
Fila32^.rgbAzul := Fila32^.rgbVerde;
Fila32^.rgbVerde:= Fila32^.rgbRojo;
Fila32^.rgbRed:= ColorByte;
Inc(Fila32);
fin;
// Cuando se utiliza color de 32 bits, es necesario ajustar el orden de los datos después de leerlos.
fin;
fin;
RT_BYTE_ENCODED:
comenzar
//CODIFICAR
// Verifique usted mismo la información sobre la codificación y decodificación de la compresión RLE.
RasError('¡El formato de compresión no es compatible!');
fin;
RT_FORMAT_RGB:
comenzar
Para Y: = 0 a Altura-1, haga
comenzar
Fila32:=Línea de exploración[Y];
ReadBuffer(Fila32^,Ancho*4);
para I: = 0 a Ancho-1 hacer
comenzar
ColorByte: = Fila32^.rgbBlue;
Fila32^.rgbBlue := Fila32^.rgbReservado;
Fila32^.rgbReservado:= ColorByte;
ColorByte: = Fila32^.rgbVerde;
Fila32^.rgbVerde:= Fila32^.rgbRojo;
Fila32^.rgbRed:= ColorByte;
Inc(Fila32);
fin;
//Aquí se fusionan los códigos para el ajuste de pedidos y el intercambio de valores R y B.
fin;
fin;{fin de RT_FORMAT_RGB}
demás
RasError('¡Formato de archivo no compatible!');
fin;{fin de 32 bits}
fin;
demás
comenzar
Imagen libre;
RasError('¡Formato de archivo no compatible!');
fin;
fin;
fin
demás
RasError('¡Formato de archivo no compatible!');
fin;{terminar con}
fin;
{El siguiente código aparece varias veces en el código anterior:
si (Ancho mod 2) = 1 entonces
comenzar
Posición := Posición + 1;
fin;
Esto se debe a que los datos de cada fila deben estar alineados por palabras, es decir, los datos de cada fila deben registrarse con un número par de bytes. Cuando la información de color de cada píxel se registra en 1 byte (8 bits) o 3 bytes (24 bits) y el número de píxeles en cada fila es un número impar, se debe rellenar un byte. Por lo tanto, aquí se omite un byte.
en el código detrás
si (Ancho mod 2) = 1 entonces
comenzar
FillByte:=0;
Stream.Write(FillByte,1);
fin;
También se basa en el mismo principio. }
procedimiento TRASGraphic.SaveToStream(Stream: TStream);
var
Encabezado: TRASHheader;
Fila 8: PByte;
Fila 24: PRGBTriple;
Fila 32: PRGBQuad;
FillByte: Byte;
Y: Entero;
I: Entero;
Pal: TMaxLogPalette;
R,G,B:matriz[0..255] de bytes;
comenzar
Encabezado.Magic := $956AA659;
Encabezado.Ancho: = SwapLong (Ancho);
Encabezado.Altura: = SwapLong (Altura);
Encabezado.RasType := SwapLong(RT_STANDARD);
si (PixelFormat = pf1bit) o (PixelFormat = pf4bit) entonces
Formato de píxel:=pf8bit
de lo contrario, si (PixelFormat <> pf8bit) y (PixelFormat <> pf24bit) y (PixelFormat <> pf32bit) entonces
Formato de píxel:=pf24bit;
caso PixelFormato de
pf8bit:
comenzar
Header.Length := SwapLong(Alto*(Ancho+(Ancho mod 2)));
Encabezado.Profundidad:= SwapLong(8);
Encabezado.MapType: = SwapLong (RMT_EQUAL_RGB);
Encabezado.MapLength := SwapLong(3*256);
Stream.WriteBuffer(Encabezado,TamañoDe(Encabezado));
GetPaletteEntries(Paleta, 0, 256, Pal.palPalEntry);
para I: = 0 a 255 hacer
comenzar
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peGreen;
B[I]:=Pal.palPalEntry[I].peBlue;
fin;
//Para operaciones de paleta relacionadas con API, consulte MSDN
Corriente.WriteBuffer(R,256);
Corriente.WriteBuffer(G,256);
Corriente.WriteBuffer(B,256);
para Y := 0 a Altura-1 hacer
comenzar
Fila8:= Línea de exploración[Y];
Stream.WriteBuffer(Fila8^,Ancho);
si (Ancho mod 2) = 1 entonces
comenzar
FillByte:=0;
Stream.Write(FillByte,1);
fin;
fin;
fin;
pf32bit:
comenzar
Encabezado.Longitud := SwapLong(Alto*Ancho*4);
Encabezado.Profundidad:= SwapLong(32);
Header.MapType: = SwapLong (RMT_NONE);
Encabezado.MapLength:= 0;
Stream.WriteBuffer(Encabezado,TamañoDe(Encabezado));
para Y := 0 a Altura-1 hacer
comenzar
Fila32: = Línea de exploración [Y];
para I: = 0 a Ancho-1 hacer
comenzar
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Fila32^,3);
Inc(Fila32);
fin;
fin;
fin;
demás
comenzar
Encabezado.Longitud := SwapLong(Alto*Ancho*3);
Encabezado.Profundidad:= SwapLong(24);
Header.MapType: = SwapLong (RMT_NONE);
Encabezado.MapLength:= 0;
Stream.WriteBuffer(Encabezado,TamañoDe(Encabezado));
para Y := 0 a Altura-1 hacer
comenzar
Fila24:= Línea de exploración[Y];
Stream.WriteBuffer(Fila24^,Ancho*3);
si (Ancho mod 2) = 1 entonces
comenzar
FillByte:=0;
Stream.Write(FillByte,1);
fin;
fin;
fin;
fin;
//SaveToStream es básicamente el proceso inverso de LoadFromStream.
fin;
inicialización
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
finalización
TPicture.UnregisterGraphicClass(TRASGraphic);
Con estas pocas líneas de código, se completa un componente completo de análisis de imágenes.