Load base64-encoded data from INI file back to TPicture?

半世苍凉 提交于 2020-08-09 06:54:30

问题


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

  1. Base64Enc.EncodeBytesToString
  2. MyIni.WriteString

So, to decode, I do the opposite procedures in the opposite order:

  1. MyIni.ReadString
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!