将 base64 编码的数据从 INI 文件加载回 TPicture?

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

在 Delphi 10.4 中,我已使用以下代码成功地将有效的 TPicture base64 编码保存到 INI 文件中:

procedure TForm1.SavePictureToIniFile(const APicture: TPicture);
// 
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;

现在我想反转这个过程,即将数据从 INI 文件加载回 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;

但是当用 CodeSite.Send 发送图片时会创建一个 AV! (发送 TPictureCodeSite.Send 通常有效,在这种情况下,AV 显然意味着图片已损坏)。

那么如何将数据从 INI 文件加载回 TPicture

这基本上与原始问题中的问题相同。

INI文件的数据是二进制图像的Base64表示,即字符串。因此,您需要读取此 Base64 字符串并使用 Base64Enc.

将其转换为二进制 blob

但是你的代码使用了ReadBinaryStream方法,它把文本当做十六进制字节序列而不是Base64字符串,returns它当做二进制blob,然后你把它给Base64Enc.

改为这样做:

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;

您可能已经意识到这一点的一种方法是这样思考:

如何编码?嗯,我愿意

  1. Base64Enc.EncodeBytesToString
  2. MyIni.WriteString

因此,为了解码,我以相反的顺序执行相反的程序:

  1. MyIni.ReadString
  2. Base64Enc.DecodeStringToBytes

删除不必要的副本

在评论中,Remy Lebeau 正确地指出上面的代码执行了不必要的 in-memory 二进制图像数据副本。虽然这在实践中不太可能成为问题(甚至是可衡量的!),但考虑到我们正在从 INI 文件中的 Base64 编码字段中读取图像,它仍然是浪费和丑陋的。

通过将 TMemoryStream 替换为 TBytesStreamTMemoryStream 的后代),我们可以将 Base64 数据直接解码到流中:

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;