writing/reading bitmaps to a tfilestream
Asked Answered
S

2

6

i've searched and searched and can't seem to find anything that describes what i'm look to do in delphi code. the solutins are sometimes close but not close enough for me to figure out. so here i am asking..

i have many bitmaps that i am retreaving from screenshots. what i have been doing is saving to bitmaps_001.bmp, but it takes a lot a storage space, so i upgraded the routine to save as bitmaps_001.png, and this saves even greater space, but now i want to save to one file, a tfilestream, and read from it using a tprogressbar that i can drag left/right as the images show on screen.

basically, i am trying to acomplish the following:

procedure SaveBMPtoStream(st: tfilestream; bmp: tbitmap);
procedure ReadBMPfrStream(st: tfilestream; bmp: tbitmap; bnum: integer);

so far the code (below)works as is, (it writes and reads in one bitmap image at the press of a tbutton) but i can only write one bitmap image. i need to write as many images as necessary per session to the tfilestream in realtime, possibly using a ttimer control and let it write as many images until i press the stop tbutton. what can i do to modify the code below to solve this? thank you.

i am running windows xp, attached to external usb3.0 1tb drive with NTFS file system.

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;
Superaltar answered 16/12, 2012 at 2:50 Comment(10)
You've got some copy/paste errors (?), like type TMS: TFileStream;. ;) If the information does not have to persist accross sessions, you can probably keep a separate index of bitmap number - stream position/size and copy that part to a temporary stream for reading. Otherwise, you can read bitmap info headers from the stream to advance the stream until the requested index and then copy again, or build an index the same way at the start of the application. But the latter would be more work.Barracuda
oh, that was a typo. corrected now. thank you.Superaltar
where you see (//sz:=MS.Size; pos:=ms.Position;) i was researching parts of code snippets in various places while trying them out. this is all confusing to me and i can't seem to figure it out. its over my head but i need to figure it all out, so i'm still at it, months later. that's why i came here, to get the answers. but still none. so, i will keep searching. thank you for trying.Superaltar
I just realized i left something out (about the images) and maybe that might help me get some answers. the images are the same dimensions, 300x300 pixels. but the dimension may change at runtime. the other evening, i fill a folder with over 60 thousand png images. it causes my hdd to lock up. i can get down to maybe 40 thousdand images but it will take a long time for the folder to give me the files in view. thats why i think that going the tfilestream route is the best since i will be writing to one file.Superaltar
It sounds like what you're really trying to do is record the screen. If that's all your program will do, then don't bother writing your own. Just use one off the shelf.Mokas
thanks, Rob. i am aware of them. but they don't answer my question, how to code my own custom routine for writing multiple bitmaps to a tfilestream.Superaltar
what u may want is TAR file format and Google or torry.net/pages.php?id=302 shows a number of examples of TAR in delphi. But why do you think you would save much space than storing PNGs as separate files ?Nugatory
@ Arioch, bitmaps are very large due to R/G/B size, and jpeg even at 100% compression shows too much noticable pixel/compression artifacts. The alternative is png, smaller than bitmap and lossless quality. I'm not aware of TAR let alone if delphi 6 has a component/unit for it (only aware of tjpeg and pngunit). Saving to files takes more time, resources and hdd space. The solution is a single stream file, and last, i needed the knowledge about tfilestream and multiple bitmaps since i could not find any actual working examples anywhere. And thanks to bummi for supplying the first examples.Superaltar
Using JPEG(introduced for natural photoshots) for screenshots is like doing integral calculations over text strings. To make screenshots other archivers were introduced, like obsolete GIF and PNG. I wonder if you can save you screen into animated PNG :-)Nugatory
TAR is a well-known standard file format for saving multitude of files without compression. If compression is needed - it was applied over resulting TAR. So it might suit your needs (despite being NIH). What about the code - especially the spped of that code - i mentioned above at least two sites where u can try to find ready-made implementations of TAR for Delphi. What about PNG compressors there are a number of Delphi wrappers, with probably different speeds as well. BTW VCL TBitmap is based on Windows GDI kernel, so would not be fast anyway.Nugatory
F
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;
Furrier answered 16/12, 2012 at 8:4 Comment(8)
@ bummi, thank you, your read/write routine worked as-is. i added the progressbar and can scan left/right. However, one slight problem, since i don't know the last image number, when i go past a certain number the routine crashes. Is there a way to store the last image number? i am using gl_cntr as the number of images recorded. i can add another tbutton that will close out the file with the final number. thank you.Superaltar
I did a modification, should not crash anymore. There are different possibilities for storing additional informations as total count. You could have a fixed header with eg. 4Bytes Dword for count, reading and writing would have to be adapted. You also could append the count as DWord at the end of your stream, which would mean writing would start at fs.Seek(4, soEnd); Another way could be using a kind of "FAT" at the start eg. with space for 100 Images and an pointer to the next "FAT" like sructure if needed, and so on. As far I don't know what you would prefer, I didn't further modifications.Furrier
hi bummi. i discovered a small problem. your routine is correct as is. but, when the screen capture starts to grow, the capture starts to pause or hesitate every certain number of seconds. example, every 5 seconds, it pauses for two seconds and continue, consistantly like this. So on account, it is dropping frames. I believe that its due to open/closing/seeking for each frame captured. I think this time, it needs to be opened once, at start of app (given filename) and when stopped, to flush/close, etc. does this sound about right to you?Superaltar
Yes, of course, you are right. It isn't optimized jet, just kept simple for demonstration. You can open once and close if you are finisihed.Furrier
i'd like to take a shot at changing it to a contiguous open stream..i'll let you know how it goes.Superaltar
i managed to revise the SaveBMPtoStream to a contiguous one, and the pause/hesitation has stopped. now i'm not sure if the method i came up with is the best way but it works at least. i'd post the code up here so someone could check it for me but i am unable to in these comments.Superaltar
I don't know how you did the implementation. One way could be intitally creating Streams for writing and reading, using them and if finished freeing them. Maybe you could open a new thread with question about alternatives for your implementation.Furrier
This works well to save TIcon too instead of TBitmap.Floorwalker
T
0

You want to save All images to the SAME file? Your easiest option then would be using a 7zip file with 0 compression (only store), this will manage the indexing/saving/retrieving of files from the archive for you very easily.

Here's a good free component that can do this: http://www.rg-software.de/rg/index.php?option=com_content&view=article&id=29&Itemid=51

You could also try using only bitmaps now again, while using a fast compression setting in the 7zip, this will probably achieve better speeds than PNG.

Turbulent answered 16/12, 2012 at 3:47 Comment(7)
this won't work for me since i am reading/writing images as fast as possible and in realtime.Superaltar
Do you need to store the images permanently, or only while the program is running, temporarily?Turbulent
i'm not sure. but the way i have been doing up to this point was saving the png image to disk, one by one as it runs. there's a counter to let me know who many png images were created plus that number is the index padded to the filename i.e., bmp001.png bmp002.png, ... bmp998.png, etc. then i open inside another app to view. but i want to do all these (and more) from inside my own app. writing to one file (stream) will save me space and help make the process more efficient.Superaltar
i don't understand something. what is the purpose of this line (s.Position := 0;) where s=tmemorystream, assuming that 's.position' is to change image index, why is it always equal to zero? whats the point of setting that? well, let me continue figuring this all out.Superaltar
It would help if I knew what exactly your app needs to do. If you write to a filestream, you have to Seek to the end of it first then write the bmp from there in a different way, but then you also need to keep the position in the file for every image. If you only need to hold the images while the app is running, you should use an array of TMemoryStream, or an ObjectList if you need to be able to delete images dynamically. Make an Array of TMemoryStream to start with, then create a new stream for every pic and load from there as needed, this will be extremelly faster than disk.Turbulent
Try to explain your app in detail, then I can throw some code in.Turbulent
as it turns out, these are permanent, once written to disk. but i'm afraid i joined the wrong forum board since it seems i being told to stop.Superaltar

© 2022 - 2024 — McMap. All rights reserved.