Solved

Saving (and Loading) multiple TBitmaps to (and from) a TMemoryStream

Posted on 2004-04-23
13
508 Views
Last Modified: 2010-04-04
Hi,

I have been coding a simple 2D TSprite class that I can use when making little games. Most of the class is complete but I must provide a mechanism for saving and loading objects to a file so that I can create sprite (and eventually level) files and load them. I have made a function called TSprite.SaveToStream that successfully (I think) returns a stream containing the sprite's coordinates etc aswell as texture bitmaps required by the sprite. I have also made the TSprite.LoadFromStream procedure but when it comes to loading the bitmaps I have some trouble. I always get an exception at 'S.CopyFrom(Stream, I);' with an Access violation! Can anyone help? Below is the relevant source:

function TSprite.SaveToStream : TMemoryStream;
var
  I : Integer;
  W : Word;
  D : Double;
  B : Boolean;
  U : Byte;
  T : Word;
  S : TMemoryStream;
begin
  //Create stream
  Result := TMemoryStream.Create;
  //Write coordinates
  I := Top;
  Result.Write(I, 32);
  I := Left;
  //Write dimensions
  Result.Write(I, 32);
  W := Height;
  Result.Write(W, 16);
  W := Width;
  Result.Write(W, 16);
  //Write velocities
  I := HorizSpeed;
  Result.Write(I, 32);
  I := VertSpeed;
  Result.Write(I, 32);
  D := Gravity;
  Result.Write(D, 64);
  D := VertAccel;
  Result.Write(D, 64);
  D := Upthrust;
  Result.Write(D, 64);
  //Write Settings
  I := TextureAnimOffset;
  Result.Write(I, 32);
  B := Visible;
  Result.Write(B, 1);
  B := CollisionDetection;
  Result.Write(B, 1);
  U := TextureMode;
  Result.Write(U, 1);
  //Write texture array length
  U := Length(Textures);
  Result.Write(U, 1);
  //Write Textures
  For T := 0 to Length(Textures) - 1 do
  begin
    //Get texture stream
    S := TMemoryStream.Create;
    Textures[T].SaveToStream(S);
    I := S.Size;
    Result.Write(I, 64);
    S.Seek(0, soFromBeginning);
    I := Result.CopyFrom(S, I);
  end;
end;

procedure TSprite.LoadFromStream(Stream : TMemoryStream);
var
  I : Integer;
  W : Word;
  D : Double;
  B : Boolean;
  U : Byte;
  T : Word;
  S : TMemoryStream;
begin
  //Seek to front
  Stream.Seek(0, soFromBeginning);
  //Read coordinates
  Stream.Read(I, 32);
  Top := I;
  Stream.Read(I, 32);
  Left := I;
  //Read dimensions
  Stream.Read(W, 16);
  Height := W;
  Stream.Read(W, 16);
  Width := W;
  //Read velocities
  Stream.Read(I, 32);
  HorizSpeed := I;
  Stream.Read(I, 32);
  VertSpeed := I;
  Stream.Read(D, 64);
  Gravity := D;
  Stream.Read(D, 64);
  VertAccel := D;
  Stream.Read(D, 64);
  Upthrust := D;
  //Read Settings
  Stream.Read(I, 32);
  TextureAnimOffset := I;
  Stream.Read(B, 1);
  Visible := B;
  Stream.Read(B, 1);
  CollisionDetection := B;
  Stream.Read(U, 1);
  TextureMode := U;
  //Read texture array length
  Stream.Read(U, 1);
  SetLength(Textures, U);
  //Read texture array
  For T := 0 to Length(Textures) -1 do
  begin
    S := TMemoryStream.Create;
    //Read stream length
    Stream.Read(I, 64);
    S.CopyFrom(Stream, I);
    Textures[T] := TBitmap.Create;
    Textures[T].LoadFromStream(S);
  end;
  Texture := TBitmap.Create;
  Texture := Textures[TextureAnimOffset];
end;

Calling code:

  GameWindow.Sprites[0] := TSpriteObject.Create;
  with GameWindow.Sprites[0] do begin
    LoadFromTwoBMPs('1.bmp', '2.bmp');
    UpThrust := -30;
    Gravity := 2;
    Texture.Transparent := True;
    Pos(Round(GameWindow.width / 2) - Round(Width / 2),0);
    CollisionDetection := True;
    OnCollide := GameWindow.ScrollingCollision;
    OnMoveUp := GameWindow.sprites[0].Jump;
    OnSpaceKey := GameWindow.sprites[0].Jump;
    OnMoveDown := GameWindow.sprites[0].MoveDown;
    OnMoveLeft := GameWindow.ScrollLeft;
    OnMoveRight := GameWindow.ScrollRight;
  end;

  GameWindow.PlayerSprite := GameWIndow.Sprites[0];
  Stream := TMemoryStream.Create;
  Stream := GameWindow.PlayerSprite.SaveToStream;
  GameWindow.PlayerSprite.LoadFromStream(Stream);

Thanks

Tristan
0
Comment
Question by:tajmiester
  • 6
  • 5
  • 2
13 Comments
 
LVL 17

Expert Comment

by:mokule
Comment Utility
Probably it has nothing to do with Your problem
but it should be
Result.Write(I, sizeof(I));
not
Result.Write(I, 32);
and so on
 
0
 

Author Comment

by:tajmiester
Comment Utility
I guess thats just bad coding, I looked integers up in the help and they are 32B. Thanks for the tip.
0
 
LVL 17

Expert Comment

by:mokule
Comment Utility

I Hope that when You correct reading in the same manner Your problem disappear.
0
 
LVL 17

Expert Comment

by:mokule
Comment Utility
Thay are 32 bits not bytes. It must be corrected.
0
 
LVL 17

Expert Comment

by:mokule
Comment Utility

Hey, are You there tajmiester.
Did You correct ?
Is it OK now?
0
 
LVL 11

Expert Comment

by:shaneholmes
Comment Utility
I created this unit a while back to write/read to/from streams

assume I have a List of Objects (MyObjects) which has some properties (Integers, Strings, Reals, TDateTimes, etc).

You could also do it with out objects, if you want to save just datatype varibale, or even an array


unit UntCommon;

interface

Uses Classes, SysUtils, Contnrs, Graphics, DateUtils;

type


 procedure SaveToFile(FileName: String);
 procedure LoadFromFile(FileName: String);

 procedure WriteStreamInt(Stream : TStream; Num : integer);
 procedure WriteStreamReal(Stream : TStream; Num : real);
 procedure WriteStreamStr(Stream : TStream; Str : string);
 function ReadStreamInt(Stream : TStream) : integer;
 function ReadStreamreal(Stream : TStream) : real;
 function ReadStreamStr(Stream : TStream) : string;
 procedure WriteStreamDate(Stream : TStream; ADate : TDateTime);
 function ReadStreamDate(Stream : TStream) : TDateTime;

procedure SaveToFile(FileName: String);
var
 MemStr: TMemoryStream;
 I: Integer;
begin
 MemStr:= TMemoryStream.Create;
 //example
 WriteStreamInt(MemStr, MyObjects.Count);
 for I:= 0 to MyObjects.Count - 1 do
 begin
  //write out objects properties
  WriteStreamStr(MemStr, MyObjects[I].Name);
 end;
 MemStr.SaveToFile(FileName);
 MemStr.Free;
end;

procedure LoadFromFile(FileName: String);
var
 MemStr: TMemoryStream;
  I, OCount: Integer;
  MyObject: TMyObject;
  AName: String;
begin
 MemStr:= TMemoryStream.Create;
 MemStr.LoadFromFile(FileName);
  OCount:= ReadStreamInt(MemStr);
  for I:= 0 to OCount - 1 do
  begin
   //read in objects properties
   AName:=  ReadStreamStr(MemStr);
     //other properties here
   MyObject:= TMuObject.Create;
   MyObject.Name:= AName;
   MyObjects.Add(MyObject);  
  end;
 MemStr.Free;
end;

procedure WriteStreamInt(Stream : TStream; Num : integer);
{writes an integer to the stream}
begin
  Stream.Write(Num, SizeOf(Integer));
end;

procedure WriteStreamReal(Stream : TStream; Num : real);
{writes an integer to the stream}
begin
  Stream.Write(Num, SizeOf(Real));
end;

procedure WriteStreamDate(Stream : TStream; ADate : TDateTime);
{writes an integer to the stream}
begin
  Stream.Write(ADate, SizeOf(TDateTime));
end;

procedure WriteStreamStr(Stream : TStream; Str : string);
{writes a string to the stream}
var
  StrLen : integer;
begin
  {get length of string}
  StrLen := Length(Str);
  {write length of string}
  WriteStreamInt(Stream, StrLen);
  {write characters}
  Stream.Write(Str[1], StrLen);
end;

function ReadStreamInt(Stream : TStream) : integer;
{returns an integer from stream}
begin
  if Stream.Read(Result, SizeOf(Integer)) < SizeOf(Integer) then
    Result := -1;
end;

function ReadStreamReal(Stream : TStream) : real;
{returns an integer from stream}
begin
  if Stream.Read(Result, SizeOf(real)) < SizeOf(real) then
    Result := 0.00;
end;

function ReadStreamDate(Stream : TStream) : TDateTime;
{returns an integer from stream}
begin
  if Stream.Read(Result, SizeOf(TDateTime)) < SizeOf(TDateTime) then
    Result := -1;
end;


function ReadStreamStr(Stream : TStream) : string;
{returns a string from the stream}
var
  StrLen : integer;
begin
  {get length of string}
  StrLen := ReadStreamInt(Stream);
  if StrLen > -1 then begin
    {set string to get memory}
    SetLength(Result, StrLen);
    {read characters}
    Stream.Read(Result[1], StrLen);
    end
  else
    Result := '';
  {end; if StrLen > -1 else}
end;



SHane
0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
LVL 11

Expert Comment

by:shaneholmes
Comment Utility
sorry, to add Bitmaps into the equation

procedure WriteStreamBMP(Stream : TStream; BMP: TBitmap);
{writes a Bitmap to the stream}
begin
 Bmp.SaveToStream(Stream);
end;

procedure ReadStreamBmp(Stream : TStream; var BMp: TBitmap );
{returns an integer from stream}
begin
 Bmp.LoadFromSTream(Stream);
end;



Shane
0
 
LVL 11

Expert Comment

by:shaneholmes
Comment Utility
Another example Saveing and loading just an integer & bitmap....

Shane


procedure SaveToFile(FileName: String);
var
 MemStr: TMemoryStream;
 I: Integer;
begin
 MemStr:= TMemoryStream.Create;
 //example integer
 WriteStreamInt(MemStr, MyInteger);
 WriteStreamBmp(MemStr, MyBitmap);
 MemStr.SaveToFile(FileName);
 MemStr.Free;
end;


procedure LoadFromFile(FileName: String);
var
 MemStr: TMemoryStream;
 AInteger: Integer;
 ABitmap: TBitmap;
begin
 MemStr:= TMemoryStream.Create;
 MemStr.LoadFromFile(FileName);
 AInteger:= ReadStreamInt(MemStr);
 ABitmap:= TBitmap.Create;
 ReadStreamBmp(MemStr, ABitmap);
 MemStr.Free;
end;



SHane
0
 
LVL 11

Expert Comment

by:shaneholmes
Comment Utility
So, if you have a TSprite

TSprite
 Top
 Left
 Height
 Width
 etc
end;


procedure SaveToFile(FileName: String; Sprite: TSprite);
var
 MemStr: TMemoryStream;
 I: Integer;
begin
 MemStr:= TMemoryStream.Create;
 WriteStreamInt(MemStr, Sprite.Top);
 //other properties
 MemStr.SaveToFile(FileName);
 MemStr.Free;
end;


procedure LoadFromFile(FileName: String; var Sprite: TSprite);
var
 MemStr: TMemoryStream;
 AInteger: Integer;
 ABitmap: TBitmap;
begin
 MemStr:= TMemoryStream.Create;
 MemStr.LoadFromFile(FileName);
 Sprite.Top:= ReadStreamInt(MemStr);
 //other properties
 MemStr.Free;
end;




Or if you have a list of sprites, use the example i showed above, replacing MyObjects with your sprites list


Shane

0
 
LVL 17

Expert Comment

by:mokule
Comment Utility
Hi,
I think more important for tajmiester is to understand where he made an error.
So
sizeof(integer) is now 4
sizeof(word)  is now 2
sizeof(double) is now 8
sizeof(boolean) is now 1
sizeof(byte) is now 1
and
for writing to stream it causes only waste of space
but for reading causes damage of data.

Besides using sizeof(integer) not 4 allows to avoid troubles in case of compiling when size of integer may be other than 4.
0
 

Author Comment

by:tajmiester
Comment Utility
Sorted! I have managed to sort it all out by using sizeof. I would like to point out that shane's loadfromstream may not work for bitmaps as it reads from the pointer to the end of the stream. Also please note that when doing TBitmap.LoadFromStream one must seek to the beginning of the stream.

I do have one more question... Can anyone think of a way of saving an event handler to a stream (and then a file)? Like OnCollide?

Thanks

Tristan
0
 
LVL 17

Expert Comment

by:mokule
Comment Utility
If I get You right.
For that purpose You should create dll library.
0
 
LVL 11

Accepted Solution

by:
shaneholmes earned 350 total points
Comment Utility
procedure WriteStreamBitmap(Stream: TStream; Bitmap: TBitmap);
var  temp:       TMemoryStream;
     size:       Integer;
begin

  // Create temp memory stream
  temp:=TMemoryStream.Create;

  // Save bitmap to temp stream
  Bitmap.SaveToStream(temp);

  // Get size of bitmap stream
  size:=temp.Size;

  // Write the size to user stream
  Stream.Write(size, SizeOf(Integer));

  // Copy the bitmap data to the user stream
  Stream.CopyFrom(temp, 0);

  // Free temp stream
  temp.Free;

end;

function ReadStreamBitmap(Stream: TStream): TBitmap;
var  temp:       TMemoryStream;
     size:       Integer;
begin

  // Create bitmap result
  result:=TBitmap.Create;

  // Create temp memory stream
  temp:=TMemoryStream.Create;

  // Read the size integer from the user stream
  Stream.Read(size, SizeOf(Integer));

  // Check for zero, because we DON'T want to read in the
  // whole user stream
  if (size > 0) then temp.CopyFrom(Stream, size);
  temp.Position:=0;

  // Now load the bitmap from the temp stream
  result.LoadFromStream(temp);

  // Free temp stream
  temp.Free;

end;


Shane
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now