Como escrever um componente de análise de imagem em Delphi
Como uma poderosa ferramenta de desenvolvimento RAD, o Delphi sempre teve suas vantagens exclusivas no desenvolvimento de software aplicativo. Esta vantagem também se reflete no desenvolvimento de software relacionado a imagens. Se você deseja colocar uma imagem na área de trabalho, basta colocar um controle Image na área de trabalho e então carregar arbitrariamente imagens em BMP, WMF, EMF e outros formatos através de sua propriedade Image. Se você também deseja adicionar suporte para JPEG, basta adicionar uma unidade JPEG. Mesmo depois de carregar um JPEG na imagem, o Delphi adicionará automaticamente uma unidade JPEG. Tudo é tão simples de fazer. Os formatos básicos foram encapsulados em VCL, então como o Delphi implementa suporte para formatos de imagem como JPEG?
Na verdade, é fácil perceber o processo de implementação do TPicture, que pode ser entendido como um contêiner para todos os objetos de imagem.
Por exemplo, existem as duas linhas de código a seguir em JPEG.pas:
TPicture.RegisterFileFormat('jpeg',sJPEGImageFile, TJPEGImage);
TPicture.RegisterFileFormat('jpg',sJPEGImageFile, TJPEGImage);
(sJPEGImageFile = 'Arquivo de imagem JPEG', consulte JConsts.pas)
O que isso significa? Pode ser entendida como uma classe que registra TJPEGImage como um arquivo de imagem com dois sufixos: jpeg e jpg.
A essência é salvar o sufixo, a descrição da imagem, a classe específica de análise de imagem e outras informações em FileFormats.
Consulte o código a seguir para obter detalhes:
var FileFormats: TFileFormatsList = nil;
classe PRocedure TPicture.RegisterFileFormat(const AExtension,
ADescrição: string; AGraphicClass: TGraphicClass);
começar
GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
fim;
função GetFileFormats: TFileFormatsList;
começar
se FileFormats = nil então FileFormats := TFileFormatsList.Create;
Resultado:=FileFormats;
fim;
TPicture suporta quatro formatos de imagem por padrão porque eles foram adicionados no construtor de TFileFormatsList.
construtor TFileFormatsList.Create;
começar
herdado Criar;
Add('wmf', SVMetafiles, 0, TMetafile);
Add('emf', SVEnhMetafiles, 0, TMetafile);
Add('ico', SVIcons, 0, TIcon);
Add('bmp', SVBitmaps, 0, TBitmap);
fim;
É através das informações salvas em FileFormats que o controle OpenPictureDialog gera automaticamente uma lista de tipos de arquivos suportados.
Então, como escrever essas classes de análise de imagens?
TGraphic é a classe base dos objetos TBitmap, TIcon e TMetafile. Da mesma forma, a classe de análise de imagem aqui também deve ser derivada de TGraphic. Ela pode economizar muito trabalho usando muito código que foi encapsulado em VCL.
Para implementar funções básicas, geralmente você só precisa sobrecarregar três membros:
TXXXImage = classe(TGráfico)
protegido
procedimento Draw(ACanvas: TCanvas; const Rect: TRect override);//desenha a imagem na tela
público
procedimento LoadFromStream(Stream: TStream override);
procedimento SaveToStream(Stream: TStream override); //Escreve os dados da imagem no stream;
fim;
Como TGraphic.LoadFromFile/TGraphic.SaveToFile já implementou a função de ler dados do nome do arquivo para o fluxo/escrever os dados do fluxo para o arquivo correspondente, não há necessidade de sobrecarregá-los sem necessidades especiais. O membro Draw é naturalmente usado para desenhar imagens no canvas. Devido ao encapsulamento perfeito do GDI pelo TCanvas, não há necessidade de considerar o processo de como desenhar imagens no formulário usando o GDI. Tudo o que resta é escrever o código para a parte de análise da imagem.
Tomemos o formato RAS como exemplo para uma discussão mais aprofundada.
TGraphic não é usado como classe base aqui, mas TBitmap é usado. Isso economiza ainda mais o processo de implementação do Draw e só precisa implementar o processo de conversão para bitmap em LoadFromStream.
tipo
TRASGraphic = classe(TBitmap)
público
procedimento LoadFromStream(Stream: substituição de TStream);
procedimento SaveToStream(Stream: substituição de TStream);
fim;
//Define o tipo de registro que descreve o cabeçalho do arquivo RAS
TRASHheader = registro compactado
Magia, //marca
Largura, //largura
Altura, //alta
Profundidade, //profundidade da cor
Comprimento, //comprimento dos dados da imagem, pode ser igual a 0
RasType, //Tipo de formato
MapType, //Tipo de paleta
MapLength: Cardinal; //Comprimento dos dados da paleta
fim;
//É muito necessário definir um tipo de registro usado para descrever o cabeçalho do arquivo RAS
const
//Define constantes representando todos os tipos de RAS
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;
//Define constantes representando tipos de paleta
RMT_NONE = 0; //Sem dados da paleta
RMT_EQUAL_RGB = 1;
RMT_RAW = 2;
{Se o formato do RAS for RT_OLD, o comprimento dos dados pode ser 0}
função SwapLong (valor const: Cardinal): Cardinal;
asm
BSWAP EAX // Instrução de troca de bytes de chamada
fim;
//Lança uma exceção, o parâmetro é a informação específica da exceção
procedimento RasError(const ErroString: String);
começar
aumentar EInvalidGraphic.Create(ErrorString);
fim;
{A seguir está o código para a parte de implementação. }
procedimento TRASGraphic.LoadFromStream(Stream: TStream);
var
Cabeçalho: LIXO Cabeçalho;
Linha8: PByte;
Linha 24: PRGBTriplo;
Linha32: PRGBQuad;
PMap: PByte;
Y: Inteiro;
Eu: Inteiro;
MapReaded: Booleano;
Amigo: TMaxLogPalette;
R,G,B:array[0..255] de Byte;
ColorByte: Byte;
começar
com Stream faça
começar
ReadBuffer(Header, SizeOf(Header)); //Lê os dados do cabeçalho do arquivo no registro Header
com cabeçalho faça
começar
Largura := SwapLong(Largura);
Altura := SwapLong(Altura);
Profundidade := SwapLong(Profundidade);
Comprimento := SwapLong(Comprimento);
RASType := SwapLong(RASType);
MapType := SwapLong(MapType);
Comprimento do Mapa := SwapLong(Comprimento do Mapa);
fim;
//Devido à ordem de leitura dos dados, você precisa chamar o SwapLong acima para alterar a ordem.
se (Header.Magic = $ 956AA659) e
(Header.Width<>0) e (Header.Height<>0) e
(Header.Depth em [1,8,24,32]) e (Header.RasType em [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) então
começar
Largura := Cabeçalho.Largura;
Altura := Cabeçalho.Altura;
MapReaded := Falso;
cabeçalho do caso. Profundidade de
1:PixelFormat := pf1Bit;
8:
começar
PixelFormat := pf8Bit;
caso Header.MapType de
RMT_NONE:
começar
Pal.palVersão:=$300;
Pal.palNumEntries:=256;
para I := 0 a 255 faça
começar
Pal.palPalEntry[I].peRed:=I;
Pal.palPalEntry[I].peVerde:=I;
Pal.palPalEntry[I].peAzul:=I;
Pal.palPalEntry[I].peFlags:=0;
fim;
Paleta := CreatePalette(PLogPalette(@Pal)^);
//Quando a profundidade de cor da imagem for de 8 bits e não existir nenhuma informação de paleta, crie uma paleta de tons de cinza de 8 bits
fim;
RMT_EQUAL_RGB:
começar
se (Cabeçalho.MapLength = 3*256) então
começar
Pal.palVersão:=$300;
Pal.palNumEntries:=256;
ReadBuffer(R,256);
ReadBuffer(G,256);
ReadBuffer(B,256);
para I := 0 a 255 faça
começar
Pal.palPalEntry[I].peRed:=R[I];
Pal.palPalEntry[I].peVerde:=G[I];
Pal.palPalEntry[I].peAzul:=B[I];
Pal.palPalEntry[I].peFlags:=0;
fim;
Paleta := CreatePalette(PLogPalette(@Pal)^);
//Lê as informações da paleta no arquivo
//Para operações de paleta relacionadas à API, verifique o MSDN
fim
outro
RasError('O comprimento da paleta está errado!');
MapReaded := Verdadeiro;
fim;
RMT_RAW:
começar
RasError('Formato de arquivo não suportado!');
fim;
fim;
fim;
24:PixelFormat := pf24Bit;
32:
começar
PixelFormat := pf32Bit;
//
fim;
fim;
if (não MapReaded) e (Header.MapLength>0) então
começar
Posição := Posição + Header.MapLength;
fim;
//Se o comprimento da paleta não for 0 e as informações relevantes não forem lidas corretamente, pule este dado
cabeçalho do caso. Profundidade de
8:
começar
se Header.RasType = RT_BYTE_ENCODED então
começar
//CODIFICAR
//Por favor, verifique você mesmo as informações sobre a codificação e decodificação da compactação RLE.
RasError('Formato de compactação não suportado!');
fim
outro
começar
para Y := 0 a Altura-1 faça
começar
Linha8:=ScanLine[Y];
ReadBuffer(Linha8^,Largura);
se (largura mod 2)=1 então
começar
Posição := Posição + 1;
fim;
fim;
fim;
fim;{fim de 8 bits}
vinte e quatro:
começar
case Header.RasType de
RT_OLD,
RT_STANDARD:
começar
para Y := 0 a Altura-1 faça
começar
Linha24:=ScanLine[Y];
ReadBuffer(Linha24^,Largura*3);
se (largura mod 2)=1 então
começar
Posição := Posição + 1;
fim;
fim;
fim;
RT_BYTE_ENCODED:
começar
//CODIFICAR
//Por favor, verifique você mesmo as informações sobre a codificação e decodificação da compactação RLE.
RasError('Formato de compactação não suportado!');
fim;
RT_FORMAT_RGB:
começar
para Y := 0 a Altura-1 faça
começar
Linha24:=ScanLine[Y];
ReadBuffer(Linha24^,Largura*3);
para I := 0 para Largura-1 faça
começar
ColorByte := Linha24^.rgbtRed;
Linha24^.rgbtRed := Linha24^.rgbtAzul;
Linha24^.rgbtBlue := ColorByte;
Inc(Linha24);
fim;
//Quando estiver no formato RT_FORMAT_RGB, pegue os dados por RGB, aqui você precisa trocar os valores de R e B
se (largura mod 2)=1 então
começar
Posição := Posição + 1;
fim;
fim;
fim;{fim de RT_FORMAT_RGB}
outro
RasError('Formato de arquivo não suportado!');
fim;
fim;{fim de 24 bits}
32:
começar
case Header.RasType de
RT_OLD,
RT_STANDARD:
começar
para Y := 0 a Altura-1 faça
começar
Linha32:=ScanLine[Y];
ReadBuffer(Linha32^,Largura*4);
para I := 0 para Largura-1 faça
começar
ColorByte := Linha32^.rgbReserved;
Linha32^.rgbReserved := Linha32^.rgbBlue;
Linha32^.rgbAzul := Linha32^.rgbVerde;
Linha32^.rgbVerde := Linha32^.rgbRed;
Linha32^.rgbRed := ColorByte;
Inc(Linha32);
fim;
//Ao usar cores de 32 bits, você precisa ajustar a ordem dos dados após a leitura.
fim;
fim;
RT_BYTE_ENCODED:
começar
//CODIFICAR
//Por favor, verifique você mesmo as informações sobre a codificação e decodificação da compactação RLE.
RasError('Formato de compactação não suportado!');
fim;
RT_FORMAT_RGB:
começar
Para Y := 0 a Height-1 faça
começar
Linha32:=ScanLine[Y];
ReadBuffer(Linha32^,Largura*4);
para I := 0 para Largura-1 faça
começar
ColorByte := Linha32^.rgbBlue;
Linha32^.rgbBlue := Linha32^.rgbReservado;
Linha32^.rgbReserved := ColorByte;
ColorByte := Linha32^.rgbVerde;
Linha32^.rgbVerde := Linha32^.rgbRed;
Linha32^.rgbRed := ColorByte;
Inc(Linha32);
fim;
//Os códigos para ajuste de pedidos e troca de valores R e B são mesclados aqui.
fim;
fim;{fim de RT_FORMAT_RGB}
outro
RasError('Formato de arquivo não suportado!');
fim;{fim de 32 bits}
fim;
outro
começar
Imagem Grátis;
RasError('Formato de arquivo não suportado!');
fim;
fim;
fim
outro
RasError('Formato de arquivo não suportado!');
fim;{terminar com}
fim;
{O código a seguir aparece várias vezes no código acima:
se (largura mod 2)=1 então
começar
Posição := Posição + 1;
fim;
Isso ocorre porque os dados de cada linha devem estar alinhados por palavra, ou seja, os dados de cada linha devem ser registrados com um número par de bytes. Quando a informação de cor de cada pixel é gravada em 1 byte (8 bits) ou 3 bytes (24 bits) e o número de pixels em cada linha é um número ímpar, um byte deve ser preenchido. Portanto, um byte é ignorado aqui.
no código por trás
se (largura mod 2) = 1 então
começar
FillByte:=0;
Stream.Write(FillByte,1);
fim;
Também se baseia no mesmo princípio. }
procedimento TRASGraphic.SaveToStream(Stream: TStream);
var
Cabeçalho: LIXO Cabeçalho;
Linha8: PByte;
Linha 24: PRGBTriplo;
Linha32: PRGBQuad;
FillByte: Byte;
Y: Inteiro;
Eu: Inteiro;
Amigo: TMaxLogPalette;
R,G,B:array[0..255] de Byte;
começar
Cabeçalho.Magic := $956AA659;
Header.Width := SwapLong(Largura);
Header.Height := SwapLong(Altura);
Cabeçalho.RasType := SwapLong(RT_STANDARD);
se (PixelFormat = pf1bit) ou (PixelFormat = pf4bit) então
Formato Pixel:=pf8bit
caso contrário, se (PixelFormat <> pf8bit) e (PixelFormat <> pf24bit) e (PixelFormat <> pf32bit) então
PixelFormat:=pf24bit;
caso PixelFormat de
pf8bit:
começar
Header.Length := SwapLong(Altura*(Largura+(Largura mod 2)));
Cabeçalho.Depth := SwapLong(8);
Header.MapType := SwapLong(RMT_EQUAL_RGB);
Header.MapLength := SwapLong(3*256);
Stream.WriteBuffer(Cabeçalho,SizeOf(Cabeçalho));
GetPaletteEntries(Paleta, 0, 256, Pal.palPalEntry);
para I := 0 a 255 faça
começar
R[I]:=Pal.palPalEntry[I].peRed;
G[I]:=Pal.palPalEntry[I].peVerde;
B[I]:=Pal.palPalEntry[I].peAzul;
fim;
//Para operações de paleta relacionadas à API, verifique o MSDN
Stream.WriteBuffer(R,256);
Stream.WriteBuffer(G,256);
Stream.WriteBuffer(B,256);
para Y := 0 a Altura-1 faça
começar
Linha8 := ScanLine[Y];
Stream.WriteBuffer(Linha8^,Largura);
se (largura mod 2) = 1 então
começar
FillByte:=0;
Stream.Write(FillByte,1);
fim;
fim;
fim;
pf32 bits:
começar
Header.Length := SwapLong(Altura*Largura*4);
Cabeçalho.Depth := SwapLong(32);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Cabeçalho,SizeOf(Cabeçalho));
para Y := 0 a Altura-1 faça
começar
Linha32 := ScanLine[Y];
para I := 0 para Largura-1 faça
começar
Stream.WriteBuffer(Row32.rgbReserved,1);
Stream.WriteBuffer(Row32^,3);
Inc(Linha32);
fim;
fim;
fim;
outro
começar
Header.Length := SwapLong(Altura*Largura*3);
Cabeçalho.Profundidade := SwapLong(24);
Header.MapType := SwapLong(RMT_NONE);
Header.MapLength := 0;
Stream.WriteBuffer(Cabeçalho,SizeOf(Cabeçalho));
para Y := 0 a Altura-1 faça
começar
Linha24 := ScanLine[Y];
Stream.WriteBuffer(Linha24^,Largura*3);
se (largura mod 2) = 1 então
começar
FillByte:=0;
Stream.Write(FillByte,1);
fim;
fim;
fim;
fim;
//SaveToStream é basicamente o processo reverso de LoadFromStream.
fim;
inicialização
TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
finalização
TPicture.UnregisterGraphicClass(TRASGraphic);
Com essas poucas linhas de código, um componente completo de análise de imagem é concluído.