使用TFileStream读写位图文件

6
我已经搜索了很多内容,但没有找到任何描述Delphi代码中我想要实现的内容。有些解决方案接近,但不足以让我理解。所以我在这里询问。
我有许多位图,从屏幕截图中检索。我一直在保存为bitmaps_001.bmp,但它占用了很多存储空间,因此我升级了例程,将其保存为bitmaps_001.png,这可以节省更大的空间,但现在我想保存到一个TFileStream文件中,并使用TProgressBar从中读取,我可以拖动左/右以显示屏幕上的图像。
基本上,我正在尝试实现以下内容:
procedure SaveBMPtoStream(st: tfilestream; bmp: tbitmap);
procedure ReadBMPfrStream(st: tfilestream; bmp: tbitmap; bnum: integer);

到目前为止,以下代码可以正常工作(在按下tbutton时同时编写和读取一个位图图像),但我只能编写一张位图图像。我需要实时在每个会话中将尽可能多的图像写入到tfilestream中,可能使用ttimer控件,并让它写入尽可能多的图像,直到我按下stop tbutton。该怎么修改以下代码以解决这个问题?谢谢。
我正在运行Windows XP,连接到带有NTFS文件系统的外部USB3.0 1TB驱动器。
type
  TMS = TFileStream; 
var
  MS:  TMS; 
  pos: int64;   // bnum for 0-99,999 images.
  sz:  integer; // size of the image/stream ?

//write bitmaps to stream
procedure SaveBMPtoStream(ms: TMS; Bmp: TBitmap; bnum: integer);
begin
  // create (or append to) stream
  if fileexists('d:\streams\s.stm') then MS := TFileStream.Create('d:\streams\s.stm', fmOpenReadWrite)
    else MS := TFileStream.Create('d:\streams\s.stm', fmCreate);
  //sz:=MS.Size; pos:=ms.Position;
  bmp.SaveToStream(MS); 
  // free stream
  ms.free;
end;

//read bitmaps from stream
procedure ReadBMPfrStream(ms: TMS; Bmp: TBitmap; bnum: integer);
begin
  // open stream.
  MS := TFileStream.Create ('d:\streams\s.stm', fmOpenReadWrite); 
  // read in bitmap from stream
  //sz:=MS.Size; pos:=ms.Position;
  bmp.LoadFromStream(MS);
  // free stream
  ms.free;
end;

哦,那是一个打错字。现在已经更正了。谢谢。 - johnm2
在代码片段的各个地方尝试它们时,我正在研究(//sz:=MS.Size; pos:=ms.Position;)部分的代码片段。这一切对我来说很混乱,我似乎无法弄清楚。这超出了我的能力范围,但我需要搞清楚所有这些,所以几个月后我仍在继续努力。这就是为什么我来到这里寻求答案。但仍然没有找到。因此,我将继续搜索。谢谢你的尝试。 - johnm2
谢谢,Rob。我知道它们。但是它们没有回答我的问题,如何编写自己的自定义例程以将多个位图写入TFileStream。 - johnm2
使用JPEG(用于自然照片)来截取屏幕就像在文本字符串上进行积分计算一样。为了制作屏幕截图,引入了其他存档程序,如过时的GIF和PNG。我想知道是否可以将屏幕保存为动画PNG :-) - Arioch 'The
TAR是一种著名的标准文件格式,用于保存大量文件而不进行压缩。如果需要压缩,则应在生成的TAR上应用压缩。因此,它可能适合您的需求(尽管存在“非本土”情况)。至于代码 - 特别是我提到的代码速度 - 我提到了至少两个站点,您可以在其中尝试查找Delphi的现成TAR实现。至于PNG压缩器,有许多Delphi包装器,速度可能也不同。顺便说一句,VCL TBitmap基于Windows GDI内核,因此无论如何都不会很快。 - Arioch 'The
显示剩余5条评论
2个回答

7
Function LoadBMPFromStream(const fn: String; Bmp: TBitmap; Nr: Integer):Boolean;
var // Nr is 0 based first Bitmap=0
  fs: TFileStream;
  ms: TMemoryStream;
  intNr: Integer;
  pos: Cardinal;
  size: DWord;
begin
  intNr := 0;
  if fileexists(fn) then
  begin
    Result := true;
    fs := TFileStream.Create(fn, fmOpenRead or fmShareDenyNone);
    try
      fs.Read(size, SizeOf(DWord)); // Read Size of first Bitmap
      while (Nr > intNr) and (fs.Position < fs.size) do
      begin
        fs.Seek(size, soFromCurrent);
        fs.Read(size, SizeOf(DWord)); // Read Size of  Bitmap with intNr
        inc(intNr);
      end;
      if fs.Position < fs.size then
      begin
        ms := TMemoryStream.Create;
        try
          ms.CopyFrom(fs, size);
          ms.Position := 0;
          Bmp.LoadFromStream(ms);
        finally
          ms.Free;
        end;
      end
      else Result := false;
    finally
      fs.Free;
    end;

  end;
end;


procedure SaveBMPtoStream(const fn: String; Bmp: TBitmap);
var
  fs: TFileStream;
  ms: TMemoryStream;
  pos: Cardinal;
  size: DWord;
begin
  if fileexists(fn) then
  begin
    fs := TFileStream.Create(fn, fmOpenReadWrite or fmShareDenyNone);
    fs.Seek(0, soEnd);
  end
  else
  begin
    fs := TFileStream.Create(fn, fmCreate or fmShareDenyNone);
  end;
  try
    ms := TMemoryStream.Create;
    try
      Bmp.SaveToStream(ms);
      size := ms.size;
      ms.Position := 0;
      fs.Write(size, SizeOf(DWord)); // Write Size of next Bitmap first
      fs.CopyFrom(ms, size);
    finally
      ms.Free;
    end;
  finally
    fs.Free;
  end;

end;

procedure TForm6.Button2Click(Sender: TObject);
begin
  // load first Picture
  LoadBMPFromStream('C:\temp\test.str', Image3.picture.bitmap, 0);
  // load third picture
  // LoadBMPFromStream('C:\temp\test.str', Image3.picture.bitmap, 2);
end;

procedure TForm6.Button1Click(Sender: TObject);
begin
  SaveBMPtoStream('C:\temp\test.str', Image1.picture.bitmap);
  SaveBMPtoStream('C:\temp\test.str', Image2.picture.bitmap);
  SaveBMPtoStream('C:\temp\test.str', Image1.picture.bitmap);
end;

@ bummi,谢谢你,你的读/写例程完美地运行了。我添加了进度条并可以从左到右扫描。然而,有一个小问题,由于我不知道最后一张图像的编号,当我超过某个数字时,程序会崩溃。有没有办法存储最后一张图像的编号?我正在使用gl_cntr作为记录图像数量的变量。我可以添加另一个tbutton来关闭文件并输出最终编号。谢谢。 - johnm2
我进行了修改,现在不应该再崩溃了。存储附加信息的不同可能性包括总计数。您可以使用固定标头,例如4字节Dword来计数,读写需要进行适当调整。您还可以将计数作为DWord附加到流的末尾,这意味着写入将从fs.Seek(4, soEnd)开始。另一种方法是在开头使用一种类似于“FAT”的结构,例如有100个图像的空间和指向下一个“FAT”结构的指针,以此类推。由于我不知道您更喜欢哪种方式,因此我没有进行进一步的修改。 - bummi
1
我想尝试将其更改为连续的开放流,我会告诉你进展如何。 - johnm2
我不知道你是如何实现的。一种方法可能是最初创建用于写入和读取的流,使用它们,完成后释放它们。也许你可以开一个新线程,询问有关你实现的替代方案的问题。 - bummi
这对于保存TIcon也很有效,而不仅仅是TBitmap。 - user30478
显示剩余3条评论

0

你想将所有图像保存到同一个文件中吗?那么最简单的方法就是使用0压缩(仅存储)的7zip文件,这将非常轻松地为您管理归档文件的索引/保存/检索。

这里有一个很好的免费组件可以实现这一点:http://www.rg-software.de/rg/index.php?option=com_content&view=article&id=29&Itemid=51

您还可以尝试在7zip中使用快速压缩设置,同时仅使用位图,这可能比PNG实现更快的速度。


这对我不起作用,因为我正在尽可能快地实时读写图像。 - johnm2
你需要永久存储图片,还是仅在程序运行时临时存储? - hikari
我不明白某些东西。在tmemorystream中,s.Position := 0;这一行的目的是什么?假设's.position'是用来改变图像索引的,为什么它总是等于零?设置它有什么意义呢?好吧,让我继续弄清楚这一切。 - johnm2
尝试详细解释你的应用程序,然后我可以加入一些代码。 - hikari
事实证明,这些是永久性的,一旦写入磁盘就无法更改。但我担心我加入了错误的论坛板块,因为似乎有人告诉我停止。 - johnm2
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接