Extracting images from zip into memory delphi - delphi

I'm wanting to extract a zip file loaded with images into memory in some way. I don't really care what type of stream they go into, as long as I can load them afterwards. I do not have that great of an understanding with streams, and explanations on the subject don't seem to go into much detail.
Essentially, what I am doing now is extracting the files to (getcurrentdir + '\temp\'). This works, but isn't quite what I am wanting to do. I would be more happy to have the jpg's end up in memory and then be able to read from memory into a TImage.bitmap.
I am currently using jclcompresion to handle zips and rars, but was considering moving back to system.zip because I really only need to be able to handle zip files. If it would be easier to stay with jclcompression though that would work for me.

The read method of the TZipFile class can be used with a stream
procedure Read(FileName: string; out Stream: TStream; out LocalHeader: TZipHeader); overload;
procedure Read(Index: Integer; out Stream: TStream; out LocalHeader: TZipHeader); overload;
from here you can access the compressed file using the index or the filename.
Check this sample which uses a TMemoryStream to hold the uncompressed data.
uses
Vcl.AxCtrls,
System.Zip;
procedure TForm41.Button1Click(Sender: TObject);
var
LStream : TStream;
LZipFile : TZipFile;
LOleGraphic: TOleGraphic;
LocalHeader: TZipHeader;
begin
LZipFile := TZipFile.Create;
try
//open the compressed file
LZipFile.Open('C:\Users\Dexter\Desktop\registry.zip', zmRead);
//create the memory stream
LStream := TMemoryStream.Create;
try
//LZipFile.Read(0, LStream, LocalHeader); you can use the index of the file
LZipFile.Read('SAM_0408.JPG', LStream, LocalHeader); //or use the filename
//do something with the memory stream
//now using the TOleGraphic to detect the image type from the stream
LOleGraphic := TOleGraphic.Create;
try
LStream.Position:=0;
//load the image from the memory stream
LOleGraphic.LoadFromStream(LStream);
//load the image into the TImage component
Image1.Picture.Assign(LOleGraphic);
finally
LOleGraphic.Free;
end;
finally
LStream.Free;
end;
finally
LZipFile.Free;
end;
end;

Related

how to convert memorystream to filestream in Delphi 7?

I have this code, where I am reading buffers to memory stream and I want to save it to file stream. The problem is that memoryStream.GetBuffer() does not work for my Delphi 7 as it is undeclared identifier.
procedure Dictionary.WriteHeaderObj(filename: String);
var MemStream: TMemoryStream;
i: Integer;
begin
self.fileName := filename;
try
MemStream := TMemoryStream.Create;
try
fsOutput := TFileStream.Create(fileName, fmCreate);
try
MemStream.Write(VAR_META, lengths.VR_META);
for i:=0 to length(buffers)-1 do
MemStream.Write(self.buffers[i].b^,self.buffers[i].l^);
fsOutput.Write(MemStream.GetBuffer(), 0, memoryStream.Position);
finally
MemStream.Free;
end;
finally
fsOutput.Free;
end;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end;
Your code showed several mistakes common to beginners.
There really is no need for the TMemoryStream. As David said, it hurts performance.
It's also not a good idea to write everything to a single TMemoryStream, because you may ran into "Out of memory" problem.
If the buffer size is really small and using a memory buffer is beneficial, you should do it in two loops - the inner loop writes a certain number of data to the memory stream and the out loop copies the memory stream to the file stream and empty the memory stream.
Unfortunately Capacity (protected) of TMemoryStream is useless because TMemoryStream reallocates memory whenever Size is changed. Using a sufficiently big TBytes avoids memory reallocation but you need to keep track of the length.
Why did you just write the header object to the file? Do you intend to append the body to the file later? Append a file is usually a bad idea.
Some thing trivial: always pass string constants with const.
Some thing trivial: always start a type name with 'T'.
Here is how I would do it:
procedure TDictionary.SaveHeaderToStream(AStream: TStream);
// You want to write to a stream, not a file. This is more reusable.
// You don't want to put the create/free code here,
// this is more flexible as it can also be used by SaveToStream
var
i: Integer;
begin
for i := 0 to length(buffers) - 1 do
AStream.Write(self.buffers[i].b^, self.buffers[i].l^);
end;
procedure TDictionary.SaveHeaderToFile(const Filename: string);
var
fsOutput: TFileStream;
begin
fsOutput := TFileStream.Create(Filename, fmCreate);
try
SaveHeaderToStream(fsOutput); // fsOutput IS TStream
finally
fsOutput.Free;
end;
end;
procedure TDictionary.SaveBodyToStream(AStream: TStream);
begin
// Codes here
end;
procedure TDictionary.SaveToStream(AStream: TStream);
begin
SaveHeaderToStream(AStream);
SaveBodyToStream(AStream);
end;
procedure TDictionary.SaveToFile(const Filename: string);
var
fsOutput: TFileStream;
begin
fsOutput := TFileStream.Create(Filename, fmCreate);
try
SaveToStream(fsOutput);
finally
fsOutput.Free;
end;
end;

Delphi Write TResourceStream into TStream

I have embedded an exe file in Resorce file.
when I just Use Stream.SaveToFile('test.exe'); everything works fine,produced exe file works with no error. but when i try to Stream.SaveToStream(Stin); , I get error " Stream write error " . what's wrong with my code ?
var
list: TStringList;
Stream: TResourceStream;
Stin, Stout: TStream;
MemStream: TMemoryStream;
begin
Stream := TResourceStream.Create(HInstance, 'phprogram' , RT_RCDATA);
try
Stin := TStream.Create;
Stout := TStream.Create;
Stream.Position := 0;
Stream.SaveToStream(Stin);
EnDecryptStream(Stin, Stout, 2913);
MemStream.LoadFromStream(Stout);
MemStream.SaveToFile('test.exe');
//Stream.SaveToFile('test.exe');
finally
Stream.Free;
end;
end;
Edited :
Thanks to David ... I changed my code and it worked fine :
var
Stream: TResourceStream;
MemStream: TMemoryStream;
begin
Stream := TResourceStream.Create(HInstance, 'testres' , RT_RCDATA);
MemStream := TMemoryStream.Create;
try
EnDecryptStream(Stream, MemStream, 2913);
MemStream.SaveToFile('test.exe');
finally
MemStream.Free;
Stream.Free;
end;
end;
TStream is an abstract class. You must not instantiate instances of TStream. You must always instantiate a concrete class derived from TStream, such as TFileStream, TMemoryStream, TStringStream, etc.
Furthermore, you use MemStream without initialising it.
It looks like you need to do something like this:
Create the resource stream.
Create a memory stream.
Call EnDecryptStream providing the resource stream as input and the memory stream as output.
Call SaveToFile on the memory stream to save it.
Or even simpler:
Create the resource stream.
Create a file stream.
Call EnDecryptStream providing the resource stream as input and the file stream as output.
One of the most common anti-patterns that we see on Stack Overflow is the excessive use of the memory stream. You appear to want to write to a file, so why not cut out the memory stream, and go straight to the file.
I don't particularly want to write any code here because we can only see a small part of the picture here, and any code that I would write would likely be wrong.
I suspect that you have not enabled compiler warnings and hints, or are perhaps ignoring them. Don't do that. Enable warnings and hints, and heed them.

Issue with loading png image from inifile

I store PNG image into Ini file with the following code :
procedure TfrmParametres.SaveIni;
var
IniFile: TIniFile;
MS: TMemoryStream;
PNG: TPngImage;
begin
IniFile := TIniFile.Create(IniFileName);
try
PNG := TPngImage.Create;
try
PNG.LoadFromFile(edtLogo.Text);//edtlogo contain image file path
MS := TMemoryStream.Create;
try
PNG.SaveToStream(MS);
MS.Seek(0, 0);
IniFile.WriteBinaryStream('REPORT_HEADER', 'LOGO', MS);
finally
MS.Free;
end;
finally
PNG.Free;
end;
finally
FreeAndNil(IniFile);
end;
end;
and to show the picture in another form OnShow event I used the same approach :
Load DATA in TMemoryStream object
Load DATA from MemoryStream into TPngImage object
Show the picture in TImage component
procedure TfrmLoadPicture.FormShow(Sender: TObject);
var
IniFile: TIniFile;
MS: TMemoryStream;
PNG: TPngImage;
begin
IniFile:= TIniFile.Create(frmParametres.IniFileName);
try
MS:= TMemoryStream.Create;
try
IniFile.ReadBinaryStream('REPORT_HEADER', 'LOGO', MS);
PNG := TPngImage.Create;
try
MS.Seek(0, 0);
PNG.LoadFromStream(MS);
Image.Picture.Assign(PNG);
finally
PNG.Free;
end;
finally
MS.Free;
end;
finally
IniFile.Free;
end;
end;
however I always get Exception error:
TIniFile has a hard-coded cap on the size of any one value it can read, partly due to the fact the underlying Windows API (GetPrivateProfileString and friends) doesn't allow querying the size of a saved value. IMO TIniFile should really raise an exception on an attempt to write a larger value, but regardless, if you use TMemIniFile instead of TIniFile you should be fine (I've just tried it).
Chris has answered the direct question that you asked. I have some other comments.
No need to decode the PNG to transfer it to the INI file
You can copy the file directly without decoding the PNG and then recoding.
Stream := TFileStream.Create(FileName, fmOpenRead);
try
IniFile.WriteBinaryStream('REPORT_HEADER', 'LOGO', Stream);
finally
Stream.Free;
end;
Hexadecimal encoding is not efficient
WriteBinaryStream is not a very efficient way to encode binary as text. In effect you are using base16 and it would be more conventional and efficient to use base64.
I suggest that you encode the binary file stream to a base64 string and write that string to the INI file.
INI files are not suited to binary data
INI files were never intended to be used to store large binary blobs. On first inspection it would seem odd that you are trying to shoe horn a PNG image into an INI file.

stuck with streaming file to string

okay, so I (VERY) recently started playing with lazaruz/free pascal, and I'm a little stuck with reading files with TMemoryStream and it's streaming kin.
I'm trying to write a simple base64 encoder, that can encode strings of text, or files (like images and WAVs) to then be used in html and javascript.
The following code compiles great but I get EReadError Illegal stream image when trying to load a file. I'll include the working string only procedure for reference:
procedure TForm1.TextStringChange(Sender: TObject);
begin
Memo1.Lines.Text := EncodeStringBase64(TextString.Text);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Lines.Text := '';
Form1.BorderIcons := [biSystemMenu,biMinimize];
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
if OpenDialog1.Execute then
begin
filename := OpenDialog1.Filename;
stream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
stream.LoadFromFile(filename);
stream.Seek(0, soFromBeginning);
ObjectBinaryToText(stream, StrStream);
StrStream.Seek(0, soFromBeginning);
Memo1.Lines.Text := EncodeStringBase64(StrStream.DataString);
finally
StrStream.Free;
end;
finally
stream.Free;
end;
end;
end;
Can anyone help me out?
You get the "illegal stream image" exception because the file you're loading probably isn't a binary DFM file. That's what ObjectBinaryToText is meant to process. It's not for arbitrary data. So get rid of that command.
You can skip the TMemoryStream, too. TStringStream already has a LoadFromFile method, so you can call it directly instead of involving another buffer.
StrStream.LoadFromFile(filename);
But a string isn't really the right data structure to store your file in prior to base64-encoding it. The input to base64 encoding is binary data; the output is text. Using a text data structure as an intermediate format means you may introduce errors into your data because of difficulties in encoding certain data as valid characters. The right interface for your encoding function is this:
function Base64Encode(Data: TStream): string;
You don't need to load the entire file into memory prior to encoding it. Just open the file with a TFileStream and pass it to your encoding function. Read a few bytes from it at a time with the stream's Read method, encode them as base64, and append them to the result string. (If you find that you need them, you can use an intermediate TStringBuilder for collecting the result, and you can add different buffering around the file reads. Don't worry about those right away, though; get your program working correctly first.)
Use it something like this:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
filename: string;
stream: TStream;
begin
if OpenDialog1.Execute then begin
filename := OpenDialog1.Filename;
stream := TFileStream.Create(filename, fmOpenRead);
try
Memo1.Lines.Text := Base64Encode(stream);
finally
stream.Free;
end;
end;
end;
I never heard before about ObjectBinaryToText(), but looks like funky one. Also, what is EncodeStringBase64() function?
At first place, you shouldn't convert binary stream to text to encode it, instead you should directly B64 encode binary data. B64 algorithm is intended to work on array of bytes.
Since Delphi 6, there is EncdDecd.pas unit, which implements B64 encoding methods. I'm not sure if Lazarus/FPC have this, but if they do, your code to B64 encode file should look like this (add EncdDecd to uses list):
procedure TForm1.Button1Click(Sender: TObject);
var
instream : TFileStream;
outstream: TStringStream;
begin
if OpenDialog1.Execute then
begin
instream := TFileStream.Create(OpenDialog1.FileName, fmOpenRead or fmShareDenyNone);
try
outstream := TStringStream.Create;
try
EncodeStream(instream, outstream);
Memo1.Lines.Text := outstream.DataString;
finally
outstream.Free;
end;
finally
instream.Free;
end;
end;
end;

Delphi: open a zip archive from a stream -> extract to a stream

Are there any zip components with such features? I need to download a zip archive from the Internet to a stream, then to open the archive from the stream and then to extract files to another stream.
E.g. ZipForge can open an archive from a stream ZipForge.OpenArchive(MyStream, false);
but how to extract to another one...?
procedure ExtractToStream(FileName: WideString; Stream: TStream);
Description
Use ExtractToStream to decompress data stored in the file inside the
archive to a TStream descendant object like TFileStream, TMemoryStream
or TBlobStream.
The FileName parameter specifies file name being extracted.
And what use of the OpenArchive(MyStream, false) method if extraction isn't supported...
The zip file component that is built into XE2 will do this.
There is an overloaded Open method that receives a TStream as its input parameters.
To extract individual files you can call an overloaded Read method passing the name of the file that you wish to extract. The extracted file is returned as a new instance of TStream. You can that use CopyFrom on that instance to transfer the extracted file to your stream.
var
ZipFile: TZipFile;
DownloadedStream, DecompressionStream, MyStream: TStream;
LocalHeader: TZipHeader;
...
ZipFile := TZipFile.Create;
try
ZipFile.Open(DownloadedStream, zmRead);
ZipFile.Read('myzippedfile', DecompressionStream, LocalHeader);
try
MyStream.CopyFrom(DecompressionStream, DecompressionStream.Size);
finally
DecompressionStream.Free;
end;
finally
ZipFile.Free;
end;
Note that I've not tested this code, I've just written it based on the source code for TZipFile and the documentation contained in that source code. There may be a few wrinkles in this but if the code behaves as advertised it meets your needs perfectly.
OK, now I tested it because I was curious. Here's the program that shows that this all works as advertised:
program ZipTest;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Classes,
System.Zip;
procedure ExtractToFile(
const ZipFileName: string;
const ZippedFileIndex: Integer;
const ExtractedFileName: string
);
var
ZipFile: TZipFile;
DownloadedStream, DecompressionStream, OutputStream: TStream;
LocalHeader: TZipHeader;
begin
DownloadedStream := TFileStream.Create(ZipFileName, fmOpenRead);
try
ZipFile := TZipFile.Create;
try
ZipFile.Open(DownloadedStream, zmRead);
ZipFile.Read(ZippedFileIndex, DecompressionStream, LocalHeader);
try
OutputStream := TFileStream.Create(ExtractedFileName, fmCreate);
try
OutputStream.CopyFrom(DecompressionStream, DecompressionStream.Size);
finally
OutputStream.Free;
end;
finally
DecompressionStream.Free;
end;
finally
ZipFile.Free;
end;
finally
DownloadedStream.Free;
end;
end;
begin
try
ExtractToFile('C:\desktop\test.zip', 0, 'C:\desktop\out.txt');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Note that I extracted by index rather than file name since that was more convenient for me. And I used file streams rather than memory streams which I imagine you would use. However, since the TZipFile methods work with TStream I'm sure that the code will work with streams of any form.
This is the latest in a series of questions about ZIP files. I know that you are using XE2 and I wonder why you seem reluctant to use the built in ZIP class that XE2 provides. I've not seen anything to indicate that it will not fulfil your requirements. In fact, it is precisely this ability to work directly with streams that makes me feel it has sufficient generality for any application.

Resources