2

在 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!(发送一个TPicture通常CodeSite.Send确实有效,在这种情况下,AV 显然意味着图片已损坏)。

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

4

1 回答 1

2

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

INI 文件的数据是二进制图像的 Base64 表示,即字符串。所以你需要读取这个 Base64 字符串并使用Base64Enc.

但是您的代码使用该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

摆脱不必要的副本

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

通过将 替换TMemoryStreamTBytesStream( 的后代TMemoryStream),我们可以将 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;
于 2020-08-03T09:47:18.887 回答