Mesmo que eu usasse uma seção CDATA, teria que aplicar alguma transformação no conteúdo binário para que ele pudesse ser lido corretamente - certos bytes representam caracteres especiais que não podem ser exibidos, tais como o 27 (escape) e o 0 (zero sinaliza fim de uma string em C/C++).
Uma forma de converter o conteúdo binário num texto legível seria tomar cada byte e usar a representação hexadecimal dele. Por exemplo, ao invés do byte com valor 27, eu teria um texto com valor 1B. Um inconveniente salta aos olhos : como cada byte é representado por 2 caracteres, o conteúdo binário dobra de tamanho no processo de conversão !
A melhor solução que encontrei para esse problema é usar a codificação chamada Base64. Nesse algoritmo, uma sequência de bytes é processada para gerar um texto contendo apenas caracteres que podem ser exibidos, selecionados em uma lista fixa com 64 caracteres possíveis - daí o nome de Base64. Não sei se a VCL inclui uma implementação desse algoritmo; a versão que usei está disponível para download no site Koders.com. O texto resultante dessa conversão ainda é maior do que o conteúdo original mas ao menos não dobra de tamanho.
Trabalhar com a Base64 é relativamente simples. Há somente duas funções no fonte indicado: uma para codificar a informação binária (B64Encode) e outra para obter de volta a informação originalmente codificada (B64Decode). Como exemplo, o trecho de código abaixo carrega uma imagem a partir de um arquivo, codifica o conteúdo encontrado e retorna o resultado do processamento como uma string. Por ser um texto, esse resultado pode ser facilmente utilizado em outras partes do programa. No meu caso, eu o incluí como valor de uma tag num XML:
var lConteudo : String;
lSsAux : TStringStream;
lStreamOrg : TFileStream;
begin
{ Stream auxiliar para recuperar o conteúdo binário do arquivo }
lSsAux := TStringStream.Create ('');
lStreamOrg := TFileStream.Create (_ImgFilePath, fmOpenRead);
{ Copia o conteúdo do arquivo para o stream em memória }
lSsAux.CopyFrom (lStreamOrg, 0);
lSsAux.Position = 0;
{ Prepara o conteúdo binário, codificando-o como um texto }
lConteudo := lSsAux.DataString;
lConteudo := B64Encode(lConteudo);
lStreamOrg.Free();
lSsAux.Free();
Result := lConteudo;
end;
lSsAux : TStringStream;
lStreamOrg : TFileStream;
begin
{ Stream auxiliar para recuperar o conteúdo binário do arquivo }
lSsAux := TStringStream.Create ('');
lStreamOrg := TFileStream.Create (_ImgFilePath, fmOpenRead);
{ Copia o conteúdo do arquivo para o stream em memória }
lSsAux.CopyFrom (lStreamOrg, 0);
lSsAux.Position = 0;
{ Prepara o conteúdo binário, codificando-o como um texto }
lConteudo := lSsAux.DataString;
lConteudo := B64Encode(lConteudo);
lStreamOrg.Free();
lSsAux.Free();
Result := lConteudo;
end;
Para obter o arquivo original de volta, basta decodificar o texto preparado pelo código anterior. O exemplo que segue traz um função que recebe esse texto, decodifica-o e grava o resultado num novo arquivo com o conteúdo original intocado:
procedure TWAssitente.DecodeContentToFile (AConteudo: String);
var lStreamDst : TFileStream;
Buffer : PChar;
begin
{ Cria o novo arquivo vazio }
lStreamDst := TFileStream.Create (_ImgFilePath, fmCreate);
{ Decodifica o conteúdo }
AConteudo := B64Decode (AConteudo);
{ Grava o conteúdo decodificado no arquivo }
Buffer := PChar (AConteudo);
lStreamDst.Write (Buffer^, Length(AConteudo));
lStreamDst.Free ();
end;
var lStreamDst : TFileStream;
Buffer : PChar;
begin
{ Cria o novo arquivo vazio }
lStreamDst := TFileStream.Create (_ImgFilePath, fmCreate);
{ Decodifica o conteúdo }
AConteudo := B64Decode (AConteudo);
{ Grava o conteúdo decodificado no arquivo }
Buffer := PChar (AConteudo);
lStreamDst.Write (Buffer^, Length(AConteudo));
lStreamDst.Free ();
end;
Como é possível deduzir da discussão até aqui, a Base64 pode ser usada para preparar qualquer conteúdo binário e não só imagens. Isto inclui tanto arquivos (documentos, planilhas, executáveis, músicas, etc.) quanto dados contidos em arrays e streams na memória de seu programa. Até mesmo o resultado de assinaturas digitais podem passar por esse processo, como mostra o post sobre a assinatura do XML de uma nota fiscal eletrônica.
Os cálculos do algoritmo da Base64 não são complexos. Caso queira analisá-lo (ou simplesmente utilizá-lo), o fonte completo pode ser acessado através desse link.
29 comentários :
Quero lhe dar os parabéns por compartilhar esse conhecimento. É um dos raros blogs que conheço de conteúdo sério. Já divulguei e coloquei nos meus favoritos.
Cordialmente,
Flavio Andrade.
Bom dia!
Gostei muito do seu artigo, como seria possivel converter imagens para base64?
Grande abraço, e parabéns!
Leonardo
Se a sua imagem está num arquivo, é só seguir o próprio exemplo do post. Mas, se o que você tem é uma instância de TImage, você pode salvá-la num stream que substituirá o File Stream que há no exemplo do post :
/* Converte uma imagem no TImage para Base64 */
Image1.Picture.Graphic.SaveToStream(lSsAux);
lSsAux.Position = 0;
lConteudo := lSsAux.DataString;
lConteudo := B64Encode(lConteudo);
Salvando o arquivo externo eu consegui, mas pegando do TImage não funcionou, ele gera uma codificação com simbolos, sabe o que poderia ser?
Grande abraço!
O que você quer dizer com "símbolos" ? O importante é que o resultado da codificação contenha apenas os caracteres alphanuméricos da Base64. Esse resultado, quando decodificado, deve restaurar o conteúdo original.
Essas características estão sendo respeitadas ? Senão, poste o seu código (ou envie para o email do balaio).
Olá Luís, a partir do seu texto resolvi buscar alternativas para conversão de imagem em base64 no ASP. Consegui um resultado satisfatório e compartilho-o abaixo. Contudo, voltando ao seu texto, você escreve: "eu o incluí como valor de uma tag num XML". Poderia me dizer como é montada esta tag? Existe alguma configuração diferente?
Segue abaixo a solução que encontrei para ASP. Obrigado. Wilson Junior, Rio de Janeiro.
Function base64_encode_fromfile( byval sFilename )
Dim objXMLDoc, objDocElem, objStream, sBase64String
Set objXMLDoc = Server.CreateObject("MSXML2.DOMDocument")
objXMLDoc.async = False
objXMLDoc.validateOnParse = False
Set objStream = Server.CreateObject("ADODB.Stream")
objStream.Type = 1
objStream.Open
'objStream.LoadFromFile Server.MapPath("/images/"&sFilename)
if err.Number <> 0 then exit function
Set objDocElem = objXMLDoc.createElement("A")
objDocElem.dataType = "bin.base64"
objDocElem.nodeTypedValue = objStream.Read
sBase64String = objDocElem.text
objStream.Close
Set objStream = Nothing
Set objDocElem = Nothing
Set objXMLDoc = Nothing
base64_encode_fromfile = sBase64String
End Function
Wilson
Não há nenhuma configuração diferente para incluir num XML a codificação em Base64 de imagens (ou qquer outro conteúdo binário).
Como o resultado da codificação é um texto, basta atribuir esse texto como valor para a tag, que ficaria mais ou menos como segue :
<minhaImg>iVBORw0KGgoAAAANSUhEUgAAADIA...</minhaImg>
Muito bom seu artigo. parabens
fiz alguns testes utilizando o delphi 7 e funcionou. Agora utilizando o delphi XE nao funciona. Sera q pode ser alguma coisa relacionada ao tipo String? Estou querendo usar para trafegar imagens no formato json.
Não me lembro a partir de qual versão o Delphi assumiu como padrão para string o tipo UnicodeString e como padrão para char o tipo UnicodeChar. Isso significa que cada caractere nesse ambiente é representado por 2 bytes.
O código para Base64 usado no post não está preparado para lidar com isso : ele espera que um caractere caiba num byte.
A melhor solução é preparar o Base64.pas para o novo ambiente, trocando os tipos de dados usados por ele pelos tipos que ele efetivamente espera. Ou seja, troque nesse fonte o que for string para AnsiString e o que for Char para AnsiChar.
Outra solução seria forçar seu projeto a trabalhar com AnsiString e AnsiChar, tipos onde cada caractere cabe num byte. Mas essa solução pode ter outras implicações para seu projeto, principalmente se ele também for dirigido a usuários chineses, japoneses ou outros cujas línguas exijam o uso de caracteres Unicode.
[]s
Ola luiz.. infelizmente nao deu certo. qudo troco o tipo char por AnsiChar da erro na linha
OutBuf[0]:= B64Table[((InBuf[0] and $FC) shr 2) + 1];
o que eu precisava mesmo era trafegar uma imagem usando json... mas ta dificil
Daniel
A constante B64Table também é um texto. É provável que você não tenha alterado a declaração dela para forçá-la a ser AnsiString. Ela deve estar declarada como segue:
const
B64Table: AnsiString = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
Se não fizer isso, ela será por padrão do tipo UnicodeString, com cada caractere ocupando 2 bytes, tornando o cálculo binário inválido.
Luiz agradeço pela ajuda... mas nao deu certo. Parou de dar erro mas a imagem decodificada nao carrega... fica uma imagem invalida.. de qualquer forma vou aki vai o codigo que estou usando e funcionando para o Delphi XE, mas so serve para imagem JPG... Obrigado e espero contribuir de alguma forma..
//======= Transforma Jpg em String
function Jpeg_em_texto(Local_imagem : string) : string;
var Foto_Memoria : TStringStream;
begin
Result := '';
if FileExists(Local_imagem) = False then
Exit;
//========= salva a foto na memoria
Foto_Memoria := TStringStream.Create;
Foto_Memoria.LoadFromFile(Local_imagem);
//========= Transforma a foto em string
Result := Foto_Memoria.DataString;
Foto_Memoria.Destroy;
end;
//========== transforma a string em Jpeg procedure TfrmPrincipal.CarregaFoto(Foto_String: string);
var NovaImagem : TJPEGImage;
Imagem_na_memoria : TMemoryStream;
Foto : TStringStream;
begin
//============ Decodifica uma string para o formato de imagem
Foto := TStringStream.Create;
Imagem_na_memoria := TMemoryStream.Create;
NovaImagem := TJPEGImage.Create;
//Carrega a foto de uma string
Foto.WriteString(Foto_String);
foto.SaveToStream(Imagem_na_memoria);
Imagem_na_memoria.Position :=0;
NovaImagem.LoadFromStream(Imagem_na_memoria);
//======= salva a imagem na area de transferencia para salvar no Cliente DataSet
clipboard.Assign(NovaImagem);
cdsInner.Edit;
DBImage1.PasteFromClipboard;
cdsInner.Post;
Foto.Destroy;
Imagem_na_memoria.Destroy;
NovaImagem.Destroy;
end;
Pessoal, para quem não está conseguindo, fiz como no post e também não deu certo, mas dando uma olhada no código e com a ajuda de nosso amigo google, consegui da seguinte maneira utilizando o delphi 2010:
procedure Decodificar(AConteudo : AnsiString; SaveAs: String);
var
lStreamDst: TFileStream;
Buffer: PChar;
begin
{ Cria o novo arquivo vazio }
lStreamDst := TFileStream.Create(SaveAs, fmCreate);
{ Decodifica o conteúdo }
AConteudo := DecodeBase64(AConteudo);
{ Grava o conteúdo decodificado no arquivo }
Buffer := PChar(AConteudo);
lStreamDst.Write(Buffer^, Length(AConteudo));
lStreamDst.Free();
end;
function Codificar(Arquivo: String): AnsiString;
var
lConteudo: String;
lSsAux: TStringStream;
lStreamOrg: TFileStream;
begin
{ Stream auxiliar para recuperar o conteúdo binário do arquivo }
lSsAux := TStringStream.Create('');
lStreamOrg := TFileStream.Create(Arquivo, fmOpenRead);
{ Copia o conteúdo do arquivo para o stream em memória }
lSsAux.CopyFrom(lStreamOrg, 0);
lSsAux.Position := 0;
{ Prepara o conteúdo binário, codificando-o como um texto }
lConteudo := lSsAux.DataString;
lConteudo := EncodeBase64(lConteudo);
lStreamOrg.Free();
lSsAux.Free();
Result := lConteudo;
end;
Pessoal, para quem não está conseguindo, fiz como no post e também não deu certo, mas dando uma olhada no código e com a ajuda de nosso amigo google, consegui da seguinte maneira utilizando o delphi 2010:
procedure Decodificar(AConteudo : AnsiString; SaveAs: String);
var
lStreamDst: TFileStream;
Buffer: PChar;
begin
{ Cria o novo arquivo vazio }
lStreamDst := TFileStream.Create(SaveAs, fmCreate);
{ Decodifica o conteúdo }
AConteudo := DecodeBase64(AConteudo);
{ Grava o conteúdo decodificado no arquivo }
Buffer := PChar(AConteudo);
lStreamDst.Write(Buffer^, Length(AConteudo));
lStreamDst.Free();
end;
function Codificar(Arquivo: String): AnsiString;
var
lConteudo: String;
lSsAux: TStringStream;
lStreamOrg: TFileStream;
begin
{ Stream auxiliar para recuperar o conteúdo binário do arquivo }
lSsAux := TStringStream.Create('');
lStreamOrg := TFileStream.Create(Arquivo, fmOpenRead);
{ Copia o conteúdo do arquivo para o stream em memória }
lSsAux.CopyFrom(lStreamOrg, 0);
lSsAux.Position := 0;
{ Prepara o conteúdo binário, codificando-o como um texto }
lConteudo := lSsAux.DataString;
lConteudo := EncodeBase64(lConteudo);
lStreamOrg.Free();
lSsAux.Free();
Result := lConteudo;
end;
Valeu pelo excelento blog. Espero ter ajudado.
Faltou que deve ser declarada a uses synacode.
Muito obrigado, resolvi meu problema com este post ;)
Pessoal, como eu recupero stream do Excel?
O que você chama de "stream" do excel? Se for o conteúdo completo de uma planilha, carregue com um TFileStream o próprio arquivo com a planilha salva.
[]s
bem simples mas porem me ajudou na mosca! obrigado
Olá Luís,
Gostei muito do post, sempre vejo os assuntos do Balaio Tecnológico e são sempre muito bons.
Teria como disponibilizar novamente o aquivo Base64.pas pois o link para download está com erro.
Obrigado
Yrakitan
O site Koders se fundiu ao Ohloh e deixou de publicar projetos. Há uma versão do fonte base64.pas neste endereço mas vc pode pesquisar o site Ohloh.net pra encontrar outras.
[]s
Olá Luís,
Obrigado por compartilhar seu conhecimento, me salvou :)
O próprio Delphi possui uma unit para Encode e Decode, acrescentei no uses EncdDecd e utilizei as funções EncodeString e DecodeString
Olá Luís, obrigado por compartilhar seu conhecimento :)
Como o arquivo base64 não estava disponível, fucei o google para procurar algo nativo para encode e decode, encontrei no stackoverflow uma dica.
Basta acrescentar no uses EncdDecd e utilizar os métodos EncodeString e DecodeString.
Obrigado!
Dyego
Obrigado pela dica. A Embarcadero vem fazendo um bom trabalho com o Delphi/C++ Builder, acrescentando recursos e funcionalidades à ferramenta; como efeito colateral, muitos bons recursos acabam ficando escondidos e só mesmo "fuçando" é que acabamos descobrindo-os.
[]s
olá bom dia, muito bom o post, mais eu gostaria de saber como fazer pra decodificar um captcha ? por exemplo: eu tenho uma captcha num componente timage com as letras: asgo, eu quero uma função que retorne digitado num edit as mesmas letras do captcha, eu quero resolver o captcha automaticamente sabe me dizer como ?
Diron
O objetivo do CAPTCHA é justamente tornar impossível automatizar o acesso a um recurso (página, arquivo, operação, etc.). Isto é, somente um humano deveria ser capaz de olhar pra imagem e extrair o texto contido nela; mas nunca um programa ou script.
Se é seu próprio programa que está gerando a imagem, a abordagem correta é armazenar sob seu controle tanto o texto em si quanto a imagem gerada a partir dele.
[]s
Opa Luis Gustavo,
você possui o arquivo Base64.pas funcional, porque os que encontrei na internet não funcionam.
Se você tiver, fico grato.
Grande abraço.
Fernando
Veja o comentário do Dyego Brito; ele encontrou as funções necessárias no próprio Delphi. Basta incluir na cláusula uses a unit EncdDecd e utilizar os métodos EncodeString e DecodeString.
Caso esteja usando uma versão muito antiga do Delphi, o fonte Base64.pas pode ser baixado no site Ohloh - veja aqui.
[]s
No delphi Xe3 consegui dessa forma:
procedure Decode64(AConteudo : AnsiString; SaveAs: String);
var
lStreamDst: TFileStream;
Buffer: PChar;
begin
{ Cria o novo arquivo vazio }
lStreamDst := TFileStream.Create(SaveAs, fmCreate);
{ Decodifica o conteúdo }
AConteudo:=DecodeString(AConteudo);
//Grava o conteúdo decodificado no arquivo }
Buffer := PChar(AConteudo);
lStreamDst.Write(Buffer^, Length(AConteudo));
lStreamDst.Free();
end;
function EncodeB64(_ImgFilePath:string): AnsiString;
var
lConteudo: String;
lSsAux: TStringStream;
lStreamOrg: TFileStream;
begin
{ Stream auxiliar para recuperar o conteúdo binário do arquivo }
lSsAux := TStringStream.Create('');
lStreamOrg := TFileStream.Create(_ImgFilePath, fmOpenRead or fmShareDenyWrite);
{ Copia o conteúdo do arquivo para o stream em memória }
lSsAux.CopyFrom(lStreamOrg, 0);
lSsAux.Position := 0;
{ Prepara o conteúdo binário, codificando-o como um texto }
lConteudo := lSsAux.DataString;
lConteudo:=EncodeString(lConteudo);
lStreamOrg.Free();
lSsAux.Free();
Result := lConteudo;
end;
Luis muito obrigado por compartilhar o seu conhecimento
Postar um comentário
OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.
Observação: somente um membro deste blog pode postar um comentário.