PDA

View Full Version : TJPEGImage.SaveToStream problem



peterbone
07-04-2004, 10:33 AM
I have a problem saving and loading a TJPEGImage to a TMemoryStream. When I load the JPEG from the stream the stream position seems to be in the wrong place as if it's loading less or more memory than it saved. In the help file it says this in TJPEGImage.SaveToStream :
"SaveToStream expects jpeg image data and may incur overhead of a compression cycle if the source is a bitmap."
I think that may have something to do with it but I don't know what it means.
Has anyone else had this problem? If not could someone just explain what that sentence means in the help files.
Thanks

Peter

Alimonster
07-04-2004, 11:39 AM
All it means, AFAICT, is that if you pass bitmap data it'll have to compress it to JPEG data, which is overhead, before going any further (otherwise it'd be saving a bitmap, not a JPEG). Nothing to worry about there, probably, and it could explain the position difference.

I'm not sure though. Does the application appear to work correctly despite your unexpected position in the stream?

peterbone
07-04-2004, 04:33 PM
yes, the application works fine apart from that



BackIsJPG : boolean;
LNumFrames : integer;

//write
LStream.Seek(0, 0);
LStream.Write(BackIsJPG, SizeOf(boolean));
if BackIsJPG then BackgroundJPG.SaveToStream(LStream)
else BackgroundBmp.SaveToStream(LStream);
LStream.Write(LNumFrames, SizeOf(integer));

//read
LStream.Seek(0, 0);
LStream.Read(BackIsJPG, SizeOf(boolean));
if BackIsJPG then begin
BackgroundJPG.LoadFromStream(LStream);
BackgroundBmp.Assign(BackgroundJPG);
end else BackgroundBmp.LoadFromStream(LStream);
LStream.Read(LNumFrames, SizeOf(integer));


When BackIsJPG is False it works (so writing and reading a TBitmap works) but when BackIsJPG is True the LNumFrames value is wrong as if reading the Jpeg positions the stream wrongly.

Peter

peterbone
17-07-2004, 02:26 PM
I have still not solved this problem - and it is really starting to frustrate me as you might expect!
I have made a simple demo to demonstrate the problem.
http://atlas.walagata.com/w/peterbone/JPEG_SaveToStream.zip
load a jpeg (from the file menu), set the spinedit to anything between 0 and 255 and then click save stream. It will create a stream containing the jpeg and then a byte containing the spin edit number - it will save it to your c route directory. Then close the application, run it again and click load stream. The jpeg will load fine, but the spin edit value will not be what you set it to because of the error in reading the byte after reading the jpeg. If you can get the demo to work so that it correctly reads the byte I can then correct the error in the main program I'm writing.

Thanks - any help will be greatly appreciated.

Peter

Paulius
17-07-2004, 11:14 PM
It looks like TJPegImage.LoadFromStream corrupts streams offset, you could store the size of each image in the stream and seek with recalculated offsets after every call to TJPegImage.LoadFromStream

peterbone
18-07-2004, 12:02 PM
good idea, thanks. I just use SizeOf to get the size of the jpeg right?
Do you think this is a bug in the delphi TJpegImage class or is there a good reason for it - like that overhead of a compression cycle thing?

Peter

Paulius
18-07-2004, 02:28 PM
No, SizeOf would return the size of a pointer (that is 4 bytes), I'd suggest when writing JPGs do: keep current Position of the stream, skip 8 bytes, write the JPG, keep the new Position, seek back to the old position and write the new position there, and finally seek to the end of the stream. Looks like a bug.

peterbone
18-07-2004, 03:42 PM
thanks, i did that and it works.
http://atlas.walagata.com/w/peterbone/JPEG_SaveToStream.zip
I wrote 2 procedures to save and load a jpeg to a stream properly.

// save a jpeg to a memory stream with correct positioning
procedure JPegSaveToStream(AJPG : TJPegImage ; AStream : TMemoryStream);
Var
LStart, LEnd : Int64;
begin
// store current stream position
LStart := AStream.Position;
// skip size of stream position
AStream.Seek(8, LStart);
// write the jpeg
AJPG.SaveToStream(AStream);
// store the new position
LEnd := AStream.Position;
// seek back to start position
AStream.Position := LStart;
// write jpeg end position to correct position when loading
AStream.Write(LEnd, 8);
// seek back to end of stream
AStream.Position := LEnd;
end;

// load a jpeg from a memory stream with correct positioning
procedure JPegLoadFromStream(AJPG : TJPegImage ; AStream : TMemoryStream);
Var
LEnd : Int64;
begin
// load the jpeg end position
AStream.Read(LEnd, 8);
// load the jpeg
AJPG.LoadFromStream(AStream);
// go to the correct end position
AStream.Position := LEnd;
end;

seems messy though - I wish I knew why it gets the position wrong.

Peter

cairnswm
19-07-2004, 08:55 AM
I'm doing something similar with JPEGs but I always set DIBNeeded := True first and I dont seen to have any problems.

peterbone
19-07-2004, 09:25 AM
if I set DIBNeeded won't that make the jpeg the same size as a bitmap when I save it to stream? The reason I'm using jpeg is to make the stream as small as possible.

my method still isn't working, If I put some data in the stream before the jpeg I get a jpeg error #53 when I try to read the jpeg even though it's now in the right position. Anyone know what #53 is?

Peter

Paulius
19-07-2004, 11:39 AM
When saving,
AStream.Seek(8, LStart);
should be
AStream.Seek(8, soFromCurrent);
Edit: Error 53 is "File Not Found", check msdn for error codes

cairnswm
19-07-2004, 11:46 AM
What I have done is the following:

procedure TForm1.Button1Click(Sender: TObject);
Var
F : TFileStream;
M,M2 : TMemoryStream;
J,J1 : TJPEGImage;
X,Y,Z : Integer;
begin
J := TJPEGImage.Create;
J.LoadFromFile('C:\cairnsgames\letter.jpg'); // Load an Image
X := 100;
M := TMemoryStream.Create;
M2 := TMemoryStream.Create;
M.Write(X,SizeOf(Integer)); // Write Tag Information
J.SaveToStream(M2);
X := M2.Size; // Calculate the size of the JPEG
M2.Free;
M.Write(X,SizeOf(Integer)); // Write Size
J.SaveToStream(M); // Write Image
J1 := TJPEGImage.Create; // Create a New Image
M.Seek(0,soBeginning); // Move to start of stream
M.Read(Y,SizeOf(Integer)); // Load Tag Information
M.Read(Z,SizeOf(Integer)); // Load Size Information
J1.LoadFromStream(M); // Load JPEG
J1.SaveToFile('C:\cairnsgames\letter1.jpg'); // Write Out (to compare vs Original)
ShowMessage(IntToStr(Z)); // Display Size info
J1.Free;
J.Free;
end;

And it works fine

I think I understand what it is doing. As JPEGs allow additional information such as who took the photo, commetns and even a thumbnail of the image the load from stream seems to read from the current position in the stream until the end, but only uses the JPEG information it needs to recreate the image in memory. Therefore when you add information at the end of the stream the LoadFromStream reads right past the JPEG size and over tha additional information.

Hope this helps.

peterbone
19-07-2004, 04:20 PM
When saving,
AStream.Seek(8, LStart);
should be
AStream.Seek(8, soFromCurrent);
Edit: Error 53 is "File Not Found", check msdn for error codes

Thanks, but wouldn't soFromCurrent be the same as LStart as LStart is the current position - since the line before it is LStart := AStream.Position; ?

Peter

Paulius
19-07-2004, 06:57 PM
Thanks, but wouldn't soFromCurrent be the same as LStart as LStart is the current position - since the line before it is LStart := AStream.Position; ?
No, you misunderstood the origin parameter, itis only supposed to be constants soFromBeginning, soFromCurrentg or soFromEnd. It probably defaults to one of these if the value is incorrect.

peterbone
20-07-2004, 09:55 AM
oh, I see - thanks. The word origin makes it sound like the position you're offsetting from. It works fine now!

Thanks for everyone's help

Peter

peterbone
29-01-2020, 02:44 PM
15 years later and I realise that my code above has an issue with loading extra data into the jpeg if the stream contains other data after the jpeg. This can cause an out of memory error if the stream is large. Fixed version is below and avoids this by copying to a new stream containing just the jpeg data.

procedure JPegLoadFromStream(AJPG : TJPegImage ; AStream : TStream);
Var
LEnd : Int64;
LMStream : TMemoryStream;
begin
// load the jpeg end position
AStream.Read(LEnd, 8 );

// Load into temporary stream as TJPeg.LoadFromStream will load to the end
LMStream := TMemoryStream.Create;
LMStream.CopyFrom(AStream, LEnd - AStream.Position);
LMStream.Seek(0, soFromBeginning);

// load the jpeg
AJPG.LoadFromStream(LMStream);

LMStream.Free;
end;