var BitmapImage: TBitmap; PNGImage: TPngImage; PngStream: TStream; begin // draw on BitmapImage ... PNGImage := TPngImage.Create; PNGStream := TMemoryStream.Create; Try PNGImage.Assign(BitmapPicture.Bitmap); // Step 1: assign TBitmap to PNG PNGImage.SaveToStream(PNGStream); // Step 2: save PNG to stream WS.Shapes.AddPicture(PNGStream,PNGImage.Width,PNGImage.Height); // Step 3: Add PNG from Stream to Excel finally PNGImage.Free; PNGStream.Free; end; ...
这是通过70000张图片测试的,以下是时间:
第1步:7秒
第2步:93秒
第3步:6秒
为什么保存到Stream这么慢?有什么建议可以优化吗?
使用Delphi XE7
编辑
这是带有简单bmp的示例(MCVE),它将转换为PNG,然后保存到流中.只是为了另一个验证,我添加了SaveToFile,当然需要更长时间,但它保存到磁盘,所以我认为可以接受.
img1.bmp为49.5KB,保存的PNG为661字节.
链接到img1.bmp = http://www.filedropper.com/img1_1
TMemoryStreamAccess = class(TMemoryStream) end; procedure TForm1.Button1Click(Sender: TObject); var BitmapImage:TBitmap; PNGImage:TPngImage; PNGStream:TMemoryStream;//TStream; i,t1,t2,t3,t4,t5,t6: Integer; vFileName:string; begin BitmapImage:=TBitmap.Create; BitmapImage.LoadFromFile('c:\tmp\img1.bmp'); t1:=0; t2:=0; t3:=0; t4:=0; t5:=0; t6:=0; for i := 1 to 70000 do begin PNGImage:=TPngImage.Create; PNGStream:=TMemoryStream.Create; try t1:=GetTickCount; PNGImage.Assign(BitmapImage); t2:=t2+GetTickCount-t1; t3:=GetTickCount; TMemoryStreamAccess(PNGStream).Capacity := 1000; PNGImage.SaveToStream(PNGStream); // BitmapImage.SaveToStream(PNGStream); <-- very fast! t4:=t4+GetTickCount-t3; finally PNGImage.Free; PNGstream.Free end; end; showmessage('Assign = '+inttostr(t2)+' - SaveToStream = '+inttostr(t4)); end;
解决方法
This is tested with 70000 images and here are the timings:
Step 1: 7 s
Step 2: 93 s
Step 3: 6 s
Why is saving to Stream so slow?
让我们来处理一些数字:
步骤1:7s = 7000ms. 7000/70000 =每张图像0.1ms
步骤2:93s = 93000ms.每张图像93000/70000 = ~1.33ms
第3步:6s = 6000ms.每张图像6000/70000 = ~0.086ms
你认为每个SaveToStream()的1.33毫秒是慢吗?你只是做了很多,所以他们随着时间的推移加起来,就是这样.
话虽这么说,内存中的PNG数据不会被压缩.保存数据时会压缩它.这是减速的一个原因.此外,保存PNG会对流进行大量写入操作,这会导致流执行多个内存(重新)分配(TPNGImage还会在保存期间执行内部内存分配),这是另一个减速.
Any suggestion to optimize this?
您无法对压缩开销做任何事情,但在调用SaveToStream()之前,您至少可以预先将TMemoryStream.Capacity设置为合理的值,以减少TMemoryStream在写入期间需要执行的内存重新分配.你不需要精确它.如果写入流导致其大小超过其当前容量,则只会相应地增加其容量.由于您已经处理了70000个图像,因此请考虑它们的平均大小并向其添加几个KB,并将其用作初始容量.
type TMemoryStreamAccess = class(TMemoryStream) end; var BitmapImage: TBitmap; PNGImage: TPngImage; PngStream: TMemoryStream; begin // draw on BitmapImage ... PNGImage := TPngImage.Create; Try PNGImage.Assign(BitmapPicture.Bitmap); // Step 1: assign TBitmap to PNG PNGStream := TMemoryStream.Create; try TMemoryStreamAccess(PNGStream).Capacity := ...; // some reasonable value PNGImage.SaveToStream(PNGStream); // Step 2: save PNG to stream WS.Shapes.AddPicture(PNGStream,PNGImage.Height); // Step 3: Add PNG from Stream to Excel finally PNGStream.Free; end; finally PNGImage.Free; end; ...
如果仍然不够快,请考虑使用线程并行处理多个图像.不要按顺序处理它们.