Загрузить данные в кодировке base64 из файла INI обратно в TPicture? - PullRequest
2 голосов
/ 03 августа 2020

В Delphi 10.4 я успешно сохранил действительный TPicture в кодировке base64 в файл INI, используя этот код:

Теперь я хочу ОБРАТИТЬ этот процесс, т. Е. Загрузить данные обратно из 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?

1 Ответ

2 голосов
/ 03 августа 2020

По сути, это та же проблема, что и в исходном вопросе.

Данные файла INI - это представление двоичного изображения в формате Base64, то есть строка . Итак, вам нужно прочитать эту строку Base64 и преобразовать ее в двоичный blob, используя 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 правильно указывает, что приведенный выше код выполняет ненужную копию данных двоичного изображения в памяти. Хотя на практике это маловероятно (или даже измеримо!), Учитывая, что мы читаем изображение из поля в кодировке Base64 в файле INI, это, тем не менее, расточительно и некрасиво.

Путем замены TMemoryStream с TBytesStream (потомок 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;
...