问题
In Delphi 10.4, I have sucessfully saved a valid TPicture
base64-encoded to an INI file, using this code:
procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
// https://stackoverflow.com/questions/63216011/tinifile-writebinarystream-creates-exception
var
LInput: TMemoryStream;
MyIni: TMemIniFile;
Base64Enc: TBase64Encoding;
ThisFile: string;
begin
if FileSaveDialog1.Execute then
ThisFile := FileSaveDialog1.FileName
else EXIT;
//CodeSite.Send('TForm1.btnSaveToIniClick: VOR Speichern');
LInput := TMemoryStream.Create;
try
APicture.SaveToStream(LInput);
LInput.Position := 0;
MyIni := TMemIniFile.Create(ThisFile);
try
Base64Enc := TBase64Encoding.Create(Integer.MaxValue, '');
try
MyIni.WriteString('Custom', 'IMG', Base64Enc.EncodeBytesToString(LInput.Memory, LInput.Size));
finally
Base64Enc.Free;
end;
MyIni.UpdateFile;
finally
MyIni.Free;
end;
finally
LInput.Free;
end;
//CodeSite.Send('TForm1.btnSaveToIniClick: NACH Speichern'); // 0,024 Sek.
end;
Now I want to REVERSE this process, i.e. load the data back from the INI file to a TPicture
:
procedure TForm1.btnLoadFromIniClick(Sender: TObject);
var
LInput: TMemoryStream;
LOutput: TMemoryStream;
ThisFile: string;
MyIni: TMemIniFile;
Base64Enc: TBase64Encoding;
ThisEncodedString: string;
ThisPicture: TPicture;
begin
if FileOpenDialog1.Execute then
ThisFile := FileOpenDialog1.FileName
else EXIT;
MyIni := TMemIniFile.Create(ThisFile);
try
Base64Enc := TBase64Encoding.Create(Integer.MaxValue, '');
try
(*ThisEncodedString := MyIni.ReadString('Custom', 'IMG', '');
Base64Enc.Decode(ThisEncodedString); // And now???*)
LInput := TMemoryStream.Create;
LOutput := TMemoryStream.Create;
try
MyIni.ReadBinaryStream('Custom', 'IMG', LInput);
MyIni.UpdateFile;
LInput.Position := 0;
Base64Enc.Decode(LInput, LOutput);
LOutput.Position := 0;
ThisPicture := TPicture.Create;
try
ThisPicture.LoadFromStream(LOutput);
CodeSite.Send('TForm1.btnLoadFromIniClick: ThisPicture', ThisPicture); // AV!
finally
ThisPicture.Free;
end;
finally
LOutput.Free;
LInput.Free;
end;
finally
Base64Enc.Free;
end;
finally
MyIni.Free;
end;
end;
But when sending the Picture with CodeSite.Send
creates an AV! (Sending a TPicture
with CodeSite.Send
usually DOES work, in this case, the AV obviously means the Picture is corrupted).
So how can I load the data back from the INI file to a TPicture
?
回答1:
This is essentially the same problem as in the original question.
The INI file's data is a Base64 representation of the binary image, that is, a string. So you need to read this Base64 string and convert it to a binary blob using Base64Enc
.
But your code uses the ReadBinaryStream
method, which treats the text not as a Base64 string but as a hexadecimal byte sequence and returns it as a binary blob, and then you give it to Base64Enc
.
Do this instead:
var
ImgData: TBytes;
begin
MyIni := TMemIniFile.Create('D:\img.ini');
try
Base64Enc := TBase64Encoding.Create(Integer.MaxValue, '');
try
LInput := TMemoryStream.Create;
try
ImgData := Base64Enc.DecodeStringToBytes(MyIni.ReadString('Custom', 'IMG', ''));
LInput.WriteData(ImgData, Length(ImgData));
LInput.Position := 0;
ThisPicture := TPicture.Create;
try
ThisPicture.LoadFromStream(LInput);
// Use ThisPicture
finally
ThisPicture.Free;
end;
finally
LInput.Free;
end;
finally
Base64Enc.Free;
end;
finally
MyIni.Free;
end;
One way you could have realised this is by thinking like this:
How do I encode? Well, I do
Base64Enc.EncodeBytesToString
MyIni.WriteString
So, to decode, I do the opposite procedures in the opposite order:
MyIni.ReadString
Base64Enc.DecodeStringToBytes
Getting rid of the unnecessary copy
In the comments, Remy Lebeau correctly points out that the code above performs an unnecessary in-memory copy of the binary image data. Although this is unlikely to be a problem (or even measurable!) in practice, given that we are reading the image from a Base64-encoded field in an INI file, it is nevertheless wasteful and ugly.
By replacing the TMemoryStream
with a TBytesStream
(a descendant of TMemoryStream
), we can decode the Base64 data directly into the stream:
var
ImgStream: TBytesStream;
begin
MyIni := TMemIniFile.Create('D:\img.ini');
try
Base64Enc := TBase64Encoding.Create(Integer.MaxValue, '');
try
ImgStream := TBytesStream.Create(Base64Enc.DecodeStringToBytes(MyIni.ReadString('Custom', 'IMG', '')));
try
ThisPicture := TPicture.Create;
try
ThisPicture.LoadFromStream(ImgStream);
// Use ThisPicture
finally
ThisPicture.Free;
end;
finally
ImgStream.Free;
end;
finally
Base64Enc.Free;
end;
finally
MyIni.Free;
end;
来源:https://stackoverflow.com/questions/63226950/load-base64-encoded-data-from-ini-file-back-to-tpicture