【问题标题】:Load base64-encoded data from INI file back to TPicture?将 INI 文件中的 base64 编码数据加载回 TPicture?
【发布时间】:2020-11-23 08:57:33
【问题描述】:

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

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;

现在我想反转这个过程,即将数据从 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

【问题讨论】:

  • 什么是CodeSite? AV 可能出现ThisPicture 未创建或CodeSite 未创建。
  • CodeSite 是一个标准的 Delphi 调试工具。您甚至可以从 GetIt 获得它。
  • 另外,这个问题和CodeSite一点关系都没有。
  • @mrNone 询问了CodeSite,所以我给了他一个答案。
  • @user1580348 CodeSite 不是一个标准 Delphi 工具。它是一种常用的第三方工具。有区别。

标签: delphi stream base64 ini delphi-10.4-sydney


【解决方案1】:

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

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

但是您的代码使用ReadBinaryStream 方法,该方法不将文本视为Base64 字符串,而是将其视为十六进制字节序列并将其作为二进制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

摆脱不必要的副本

在 cmets 中,Remy Lebeau 正确指出上面的代码对二进制图像数据执行了不必要的内存复制。尽管这在实践中不太可能成为问题(甚至是可测量的!),但鉴于我们正在从 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;

【讨论】:

  • @user1580348: UpdateFile 用于在您写入 INI 文件时将内存中的INI 文件保存到磁盘。在这种情况下,我们只是从中读取
  • TBytes 定义为TArray<Byte>,与array of Byte 相同。因此,TBytes 变量实际上只是一个dynamic array(字节)!因此,Length(ImgData) = 0orImgData = nil 测试“空虚”。
  • 如果你解码到TBytes,你可以使用TBytesStream而不是TMemoryStream来避免在内存中产生重复的副本。否则,我会使用Decode() 而不是DecodeStringToBytes() 直接解码为TMemoryStream
  • @RemyLebeau 是绝对正确的。上面的代码对整个图像执行了不必要的复制(不过,考虑到我们正在从 INI 文件中的 Base64 编码字段读取图像,这在实践中不太可能成为问题)。使用TByteStream 及其TBytes-accepting 构造函数可以解决这个问题。 (另一种方法迫使我们创建一个额外的流对象,TStringStream,不是吗?)
  • @AndreasRejbrand 是的,使用 Decode() 需要为 string 数据输入 TStream。在 D2009+ 中,TStringStream 在保存之前将输入 string 转换为 TBytes。因此,您可以通过将 INI string 分配给 AnsiString 然后使用 TCustomMemoryStream 来避免这种情况。或者,您可以自己使用TEncoding.GetBytes()string 转换为TBytes,然后使用TBytesStream(这是TStringStream 所做的)。关键是要尽量避免在内存中同时有2个base64数据的副本。
猜你喜欢
  • 2021-04-24
  • 2019-12-03
  • 1970-01-01
  • 2023-04-03
  • 1970-01-01
  • 2021-05-15
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多