i have a TMemoryStream of a picture but i want to add a String inside that TMemoryStream that defines what that picture is then read them separately.
the TMemoryStream will be sent from IdTCPclient to IdTCPserver and server will read the string then the picture.
id prefer if the string was in the beginning of the stream.
it is my first encounter on dealing with memory stream with multiple date inside it, please enlighten me.
//Edit: adding current code based on MBo Answer
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
bm: TBitmap;
ms ,ms1: TMemoryStream;
len: Int64;
JPEGImage: TJPEGImage;
begin
IdTCPClient1.Connect;
begin
ms:=TMemoryStream.Create;
ms1:=TMemoryStream.Create;
JPEGImage := TJPEGImage.Create;
s := 'A nice picture';
ms:=CapScreen(100); //function result in memory stream
len := Length(s) * SizeOf(Char);
ms1.Write(len, SizeOf(len));
ms1.Write(PChar(s)^, len);
ms1.copyfrom(ms,ms.Size) ; // <-- stream read error
ms1.Position := 0;
Caption := s;
JPEGImage.LoadFromStream(ms1);
Image1.Picture.Assign(JPEGImage);
end;
IdTCPClient1.IOHandler.Write(ms1, 0, True);
ms.Free;
ms1.Free;
JPEGImage.Free;
end;
Simple example to start from. Picture is loaded from disk here
var
s: string;
bm: TBitmap;
ms: TMemoryStream;
len: Int64;
begin
ms := TMemoryStream.Create;
bm := TBitmap.Create;
try
bm.LoadFromFile('d:\d.bmp');
//write string body size, body itself
s := 'A nice picture';
len := Length(s) * SizeOf(Char);
ms.Write(len, SizeOf(len));
ms.Write(PChar(s)^, len);
//now picture
bm.SaveToStream(ms);
//change string and picture to be sure we load new ones
bm.Canvas.FillRect(rect(0,0,100,100));
s := '';
//now restore and show
ms.Position := 0;
ms.Read(len, sizeof(len));
SetLength(s, len div SizeOf(Char));
ms.Read(PChar(s)^, len);
Caption := s;
bm.LoadFromStream(ms); //reads picture from current position
Canvas.Draw(0, 0, bm);
finally
ms.Free;
bm.Free;
end;
Example for jpeg loaded from another stream:
var
s: string;
ms, jpstream: TMemoryStream;
len: Int64;
jp: TJpegImage;
begin
ms := TMemoryStream.Create;
jpstream := TMemoryStream.Create;
jp := TJpegImage.Create;
try
//write string body size, body itself
s := 'A nice picture';
len := Length(s) * SizeOf(Char);
ms.Write(len, SizeOf(len));
ms.Write(PChar(s)^, len);
jpstream.LoadFromFile('d:\d.jpg');
jpstream.Position := 0;
ms.CopyFrom(jpstream, jpstream.Size);
//now restore ans show
ms.Position := 0;
ms.Read(len, sizeof(len));
SetLength(s, len div SizeOf(Char));
ms.Read(PChar(s)^, len);
Caption := s;
jp.LoadFromStream(ms);
Canvas.Draw(0, 0, jp);
finally
ms.Free;
jp.Free;
jpstream.Free;
end;
Related
I want to know the width and height of an image file before opening that file.
So, how can I do that?
This refers to JPEG, BMP, PNG and GIF types of image files.
If by 'image file' you mean those raster image files recognised by the VCL's graphics system, and by 'before opening' you mean 'before the user is likely to notice that the file is opened', then you can do this very easily:
var
pict: TPicture;
begin
with TOpenDialog.Create(nil) do
try
if Execute then
begin
pict := TPicture.Create;
try
pict.LoadFromFile(FileName);
Caption := Format('%d×%d', [pict.Width, pict.Height])
finally
pict.Free;
end;
end;
finally
Free;
end;
Of course, the file is opened, and this requires a lot of memory if the image is big. However, if you need to obtain metatada (like dimensions) without loading the file, I believe you need a more 'complicated' solution.
You can try this page. I have not tested it, but it seems pretty reasonable that it will work.
Also, different file types have different ways of getting the width and height.
One of the page answers:
unit ImgSize;
interface
uses Classes;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
implementation
uses SysUtils;
function ReadMWord(f: TFileStream): word;
type
TMotorolaWord = record
case byte of
0: (Value: word);
1: (Byte1, Byte2: byte);
end;
var
MW: TMotorolaWord;
begin
// It would probably be better to just read these two bytes in normally and
// then do a small ASM routine to swap them. But we aren't talking about
// reading entire files, so I doubt the performance gain would be worth the trouble.
f.Read(MW.Byte2, SizeOf(Byte));
f.Read(MW.Byte1, SizeOf(Byte));
Result := MW.Value;
end;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
const
ValidSig : array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
var
Sig: array[0..1] of byte;
f: TFileStream;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
Len: word;
ReadLen: LongInt;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
ReadLen := f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
ReadLen := 0;
if ReadLen > 0 then
begin
ReadLen := f.Read(Seg, 1);
while (Seg = $FF) and (ReadLen > 0) do
begin
ReadLen := f.Read(Seg, 1);
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) then
begin
ReadLen := f.Read(Dummy[0], 3); // don't need these bytes
wHeight := ReadMWord(f);
wWidth := ReadMWord(f);
end
else
begin
if not (Seg in Parameterless) then
begin
Len := ReadMWord(f);
f.Seek(Len - 2, 1);
f.Read(Seg, 1);
end
else
Seg := $FF; // Fake it to keep looping.
end;
end;
end;
end;
finally
f.Free;
end;
end;
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
type
TPNGSig = array[0..7] of byte;
const
ValidSig: TPNGSig = (137, 80, 78, 71, 13, 10, 26, 10);
var
Sig: TPNGSig;
f: tFileStream;
x: integer;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
exit;
f.Seek(18, 0);
wWidth := ReadMWord(f);
f.Seek(22, 0);
wHeight := ReadMWord(f);
finally
f.Free;
end;
end;
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
type
TGIFHeader = record
Sig: array[0..5] of char;
ScreenWidth, ScreenHeight: word;
Flags, Background, Aspect: byte;
end;
TGIFImageBlock = record
Left, Top, Width, Height: word;
Flags: byte;
end;
var
f: file;
Header: TGifHeader;
ImageBlock: TGifImageBlock;
nResult: integer;
x: integer;
c: char;
DimensionsFound: boolean;
begin
wWidth := 0;
wHeight := 0;
if sGifFile = '' then
exit;
{$I-}
FileMode := 0; // read-only
AssignFile(f, sGifFile);
reset(f, 1);
if IOResult <> 0 then
// Could not open file
exit;
// Read header and ensure valid file
BlockRead(f, Header, SizeOf(TGifHeader), nResult);
if (nResult <> SizeOf(TGifHeader)) or (IOResult <> 0)
or (StrLComp('GIF', Header.Sig, 3) <> 0) then
begin
// Image file invalid
close(f);
exit;
end;
// Skip color map, if there is one
if (Header.Flags and $80) > 0 then
begin
x := 3 * (1 SHL ((Header.Flags and 7) + 1));
Seek(f, x);
if IOResult <> 0 then
begin
// Color map thrashed
close(f);
exit;
end;
end;
DimensionsFound := False;
FillChar(ImageBlock, SizeOf(TGIFImageBlock), #0);
// Step through blocks
BlockRead(f, c, 1, nResult);
while (not EOF(f)) and (not DimensionsFound) do
begin
case c of
',': // Found image
begin
BlockRead(f, ImageBlock, SizeOf(TGIFImageBlock), nResult);
if nResult <> SizeOf(TGIFImageBlock) then
begin
// Invalid image block encountered
close(f);
exit;
end;
wWidth := ImageBlock.Width;
wHeight := ImageBlock.Height;
DimensionsFound := True;
end;
',' : // Skip
begin
// NOP
end;
// nothing else, just ignore
end;
BlockRead(f, c, 1, nResult);
end;
close(f);
{$I+}
end;
end.
And for BMP (also found at the page I mentioned):
function FetchBitmapHeader(PictFileName: String; Var wd, ht: Word): Boolean;
// similar routine is in "BitmapRegion" routine
label ErrExit;
const
ValidSig: array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
BmpSig = $4d42;
var
// Err : Boolean;
fh: HFile;
// tof : TOFSTRUCT;
bf: TBITMAPFILEHEADER;
bh: TBITMAPINFOHEADER;
// JpgImg : TJPEGImage;
Itype: Smallint;
Sig: array[0..1] of byte;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
skipLen: word;
OkBmp, Readgood: Boolean;
begin
// Open the file and get a handle to it's BITMAPINFO
OkBmp := False;
Itype := ImageType(PictFileName);
fh := CreateFile(PChar(PictFileName), GENERIC_READ, FILE_SHARE_READ, Nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (fh = INVALID_HANDLE_VALUE) then
goto ErrExit;
if Itype = 1 then
begin
// read the BITMAPFILEHEADER
if not GoodFileRead(fh, #bf, sizeof(bf)) then
goto ErrExit;
if (bf.bfType <> BmpSig) then // 'BM'
goto ErrExit;
if not GoodFileRead(fh, #bh, sizeof(bh)) then
goto ErrExit;
// for now, don't even deal with CORE headers
if (bh.biSize = sizeof(TBITMAPCOREHEADER)) then
goto ErrExit;
wd := bh.biWidth;
ht := bh.biheight;
OkBmp := True;
end
else
if (Itype = 2) then
begin
FillChar(Sig, SizeOf(Sig), #0);
if not GoodFileRead(fh, #Sig[0], sizeof(Sig)) then
goto ErrExit;
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
goto ErrExit;
Readgood := GoodFileRead(fh, #Seg, sizeof(Seg));
while (Seg = $FF) and Readgood do
begin
Readgood := GoodFileRead(fh, #Seg, sizeof(Seg));
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) or (Seg = $C2) then
begin
Readgood := GoodFileRead(fh, #Dummy[0],3); // don't need these bytes
if ReadMWord(fh, ht) and ReadMWord(fh, wd) then
OkBmp := True;
end
else
begin
if not (Seg in Parameterless) then
begin
ReadMWord(fh,skipLen);
SetFilePointer(fh, skipLen - 2, nil, FILE_CURRENT);
GoodFileRead(fh, #Seg, sizeof(Seg));
end
else
Seg := $FF; // Fake it to keep looping
end;
end;
end;
end;
ErrExit: CloseHandle(fh);
Result := OkBmp;
end;
As a complement to Rafael's answer, I believe that this much shorter procedure can detect BMP dimensions:
function GetBitmapDimensions(const FileName: string; out Width,
Height: integer): boolean;
const
BMP_MAGIC_WORD = ord('M') shl 8 or ord('B');
var
f: TFileStream;
header: TBitmapFileHeader;
info: TBitmapInfoHeader;
begin
result := false;
f := TFileStream.Create(FileName, fmOpenRead);
try
if f.Read(header, sizeof(header)) <> sizeof(header) then Exit;
if header.bfType <> BMP_MAGIC_WORD then Exit;
if f.Read(info, sizeof(info)) <> sizeof(info) then Exit;
Width := info.biWidth;
Height := abs(info.biHeight);
result := true;
finally
f.Free;
end;
end;
If anyone yet interested in retrieving TIFF image dimensions without loading the graphic, there is a proven method that works perfectly for me in all environments. I also found another solution for that, but it returned wrong values from Illustrator-generated TIFFs. But there is a fantastic graphic library, called GraphicEx by Mike Lischke (TVirtualStringTree's very talented developer). There are implementations of many popular image formats and all of them descend from the base class TGraphicExGraphic, that implements ReadImageProperties virtual method. It is stream-based and only reads the fileheader in all implementations. So it is lightning-fast... :-)
So, here is a sample code, that retrieves a TIFF's dimensions (the method is the same for all graphic implementation, PNG,PCD,TGA,GIF,PCX,etc):
Uses ..., GraphicEx,...,...;
Procedure ReadTifSize (FN:String; Var iWidth,iHeight:Integer);
Var FS:TFileStream;
TIFF:TTIFFGraphic;
Begin
iWidth:=0;iHeight:=0;
TIFF:=TTIFFGraphic.Create;
FS:=TFileStream.Create(FN,OF_READ);
Try
TIFF.ReadImageProperties(FS,0);
iWidth:=TIFF.ImageProperties.Width;
iHeight:=TIFF.ImageProperties.Height;
Finally
TIFF.Destroy;
FS.Free;
End;
End;
That's all... :-) And this is the same for all the graphic implementations in the unit.
I don't like Rafael's solution for JPEG files too much because his algorithm parses every single byte until it hits FFC0. It doesn't make use of the fact that almost all markers (except FFD8, FFD9 and FFFE) are followed by two length bytes, allowing to skip from marker to marker. So I suggest the following procedure (which I condensed even a little more by stuffing checking for a marker and retrieving a value into the same function):
procedure GetJPGSize(const Filename: string; var ImgWidth, ImgHeight: word);
const
SigJPG : TBytes = [$FF, $D8];
SigC01 : TBytes = [$FF, $C0];
SigC02 : TBytes = [$FF, $C1];
var
FStream: TFileStream;
Buf: array[0..1] of Byte;
Offset,CheckMarker : Word;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function SameValue(Sig:TBytes):Boolean;
begin
Result := CompareMem(#Sig[0], #Buf[0], Length(Sig));
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
function CheckMarkerOrVal(var Value:Word):Boolean;
begin
FStream.ReadData(Buf, Length(Buf));
Value := Swap(PWord(#Buf[0])^);
Result := (Buf[0] = $FF);
end;
//--------------------------------------------------------------------------------------------------------------------------------------------------------------
begin
FStream := TFileStream.Create(Filename, fmOpenRead);
Try
// First two bytes in a JPG file MUST be $FFD8, followed by the next marker
If not (CheckMarkerOrVal(CheckMarker) and SameValue(SigJPG))
then exit;
Repeat
If not CheckMarkerOrVal(CheckMarker)
then exit;
If SameValue(SigC01) or SameValue(SigC02) then begin
FStream.Position := FStream.Position + 3;
CheckMarkerOrVal(ImgHeight);
CheckMarkerOrVal(ImgWidth);
exit;
end;
CheckMarkerOrVal(Offset);
FStream.Position := FStream.Position + Offset - 2;
until FStream.Position > FStream.Size div 2;
Finally
FStream.Free;
end;
end;
Since GetGIFSize in Rafael's answer is broken and utterly complicated, here is my personal version of it:
function GetGifSize(var Stream: TMemoryStream; var Width: Word; var Height: Word): Boolean;
var
HeaderStr: AnsiString;
begin
Result := False;
Width := 0;
Height := 0;
//GIF header is 13 bytes in length
if Stream.Size > 13 then
begin
SetString(HeaderStr, PAnsiChar(Stream.Memory), 6);
if (HeaderStr = 'GIF89a') or (HeaderStr = 'GIF87a') then
begin
Stream.Seek(6, soFromBeginning);
Stream.Read(Width, 2); //Width is located at bytes 7-8
Stream.Read(Height, 2); //Height is located at bytes 9-10
Result := True;
end;
end;
end;
I found it by reading the RFC.
I am trying to send a TStringStream from client to server, then send it back from server to client, using Indy TCP components.
Here is my client code:
var
Jpg: TJPEGImage;
StringStream: TStringStream;
strcams, StringImageData: String;
byt, i: integer;
procedure SendCommandWithParams(Command, Params: String);
begin
Lock;
try
if not FTCP.Connected then
begin
exit;
end;
FTCP.Socket.WriteLn('1' + Command, IndyTextEncoding_UTF8);
FTCP.Socket.WriteLn(Params, IndyTextEncoding_UTF8);
finally
Unlock;
end;
end;
begin
Jpg := TJPEGImage.Create;
StringStream := TStringStream.Create('');
try
try
Jpg.Performance := jpBestSpeed;
Jpg.ProgressiveEncoding := True;
Jpg.ProgressiveDisplay := True;
Jpg.Assign(Image2.Picture.Bitmap);
Jpg.CompressionQuality := 25;
Jpg.Compress;
Jpg.SaveToStream(StringStream);
StringImageData := StringStream.DataString;
strcams := '<[S:' + IntToStr(Length(StringImageData)) + 'B]>' +
StringImageData;
if Length(strcams) < byt then
begin
SendCommandWithParams('SIMGSEND', strcams + sep + 'IMGID5423' + sep);
end;
except
on e: exception do
//
end;
finally
StringImageData := '';
FreeAndNil(Jpg);
FreeAndNil(StringStream);
end;
end;
I can receive the TStringStream data, but the data received is corrupted, and some times it gets replaced with the second parameter that I send which is 'IMGID5423' + sep. I am not sure if this is because of some limit of packet sending through TCP so the data does not arrive complete, or is this a parser issue?
My current parser should separate each text that ended with #13#10. Here is how it looks:
var
ReceiveParams, ReceiveStream: Boolean;
S: string;
Command: String;
begin
Command := Fholdcommand;
ReceiveParams := false;
ReceiveStream := false;
if Command[1] = '1' then // command with params
begin
Command := Copy(Command, 2, MaxInt);
ReceiveParams := True;
end;
if ReceiveParams then // params incomming
begin
S := FTCP.Socket.ReadLn(IndyTextEncoding_UTF8);
FCMD := Command;
FPRMS := S;
FSTREAM := false;
if Assigned(FOnCallbackProc) then
begin
Synchronize(DoCallbackProc);
end;
end;
I am still confused about the real issue. I try to send the TStringStream in a local procedure, and it is received normally without any corruption.
Am I sending the data wrong altogether through Indy?
This is how I am receiving the data:
procedure CreateJpg(Data:string);
var
StringStream : TStringStream;
JpegImage : TJPEGImage;
Bitmap : TBitmap;
tmpPos:integer;
pp:string;
label check;
begin
GData := Data;
if LeftStr(GData,4) = '<[S:' then
begin
tmpPos := Pos(WideString('B]>'),GData);
pp := Copy(GData,5,tmpPos-5);
CDataELen := StrToInt(pp); //MidStr(st,5,tmppos - 5);
CData := RightStr(GData,length(GData)-(tmppos+2));
goto check;
end;
CData := CData + GData;
check:
//if CDataELen = length(CData) then
begin
StringStream := TStringStream.Create('');
JpegImage := TJpegImage.Create;
StringStream.WriteString(CData);
CData := '';
try
try
StringStream.Seek(0, soFromBeginning);
JpegImage.LoadFromStream(StringStream);
Bitmap := TBitmap.Create;
with Bitmap do
begin
Canvas.Lock;
try
Width := JpegImage.Width;
Height := JpegImage.Height;
Canvas.Draw(0, 0, JpegImage);
finally
Canvas.Unlock;
end;
end;
img.Picture.Bitmap.Width := Bitmap.Width;
img.Picture.Bitmap.Height := Bitmap.Height;
img.Picture.Bitmap.Canvas.Draw(0, 0, Bitmap);
except
on E: Exception do
//
end;
finally
FreeAndNil(StringStream);
FreeAndNil(JpegImage);
FreeAndNil(Bitmap);
end;
end;
end;
The problem is that you are saving the JPG binary data to a TStringStream and then letting it reinterpret the binary data as if it were string data. You can't do that. You need to save the JPG data to a binary stream instead, like TMemoryStream, and then encode the binary data using a string-safe encoding, like Base64.
Try something more like this instead:
uses
..., IdCoder, IdCoderMIME;
...
var
Jpg: TJPEGImage;
JpegStream: TMemoryStream;
strcams, StringImageData: String;
begin
try
JpegStream := TMemoryStream.Create;
try
Jpg := TJPEGImage.Create;
try
Jpg.Performance := jpBestSpeed;
Jpg.ProgressiveEncoding := True;
Jpg.ProgressiveDisplay := True;
Jpg.Assign(Image2.Picture.Bitmap);
Jpg.CompressionQuality := 25;
Jpg.Compress;
Jpg.SaveToStream(JpegStream);
finally
Jpg.Free;
end;
JpegStream.Position := 0;
StringImageData := TIdEncoderMIME.EncodeStream(JpegStream);
finally
JpegStream.Free;
end;
strcams := '<[S:' + IntToStr(Length(StringImageData)) + 'B]>' + StringImageData;
SendCommandWithParams('SIMGSEND', strcams + sep + 'IMGID5423' + sep);
except
on e: exception do
//
end;
end;
And then on the receiving end:
procedure CreateJpg(Data: string);
var
JpegStream: TMemoryStream;
JpegImage: TJPEGImage;
Bitmap: TBitmap;
tmpPos, tmpLen: integer;
pp: string;
begin
try
if not TextStartsWith(Data, '<[S:') then
begin
// bad data, do something else...
Exit;
end;
tmpPos := Pos('B]>', Data);
pp := Copy(Data, 5, tmpPos-5);
tmpLen := StrToInt(pp);
Data := Copy(Data, tmpPos+3, tmpLen);
Bitmap := TBitmap.Create;
try
JpegImage := TJpegImage.Create;
try
JpegStream := TMemoryStream.Create;
try
TIdDecoderMIME.DecodeStream(Data, JpegStream);
JpegStream.Position := 0;
JpegImage.LoadFromStream(JpegStream);
finally
JpegStream.Free;
end;
with Bitmap do
begin
Canvas.Lock;
try
Width := JpegImage.Width;
Height := JpegImage.Height;
Canvas.Draw(0, 0, JpegImage);
finally
Canvas.Unlock;
end;
end;
finally
JpegImage.Free;
end;
img.Picture.Assign(Bitmap);
finally
Bitmap.Free;
end;
except
on E: Exception do
//
end;
end;
Your problem appears to be that you are treating binary data as though it is text. Binary data can contain anything, for instance #13#10 line breaks or indeed anything whatsoever.
If you wish to send that data as text, then you need to use a text encoding. For example, encode it as base64.
Or transmit the content as binary rather than text.
I want to create a client-server UDP. The problem is that the server can not mopulit picture
type
TPacket = record
Image: TJPEGImage;
student: string;
end;
var
Image: TBitmap;
Desktop: TDesktop;
by: TBytes;
Packet: TPacket;
implementation
{$R *.dfm}
procedure TDesktop.Button1Click(Sender: TObject);
var
can: TCanvas;
begin
can := TCanvas.Create;
can.Handle := GetWindowDC(GetDesktopWindow);
ZeroMemory(by, 0);
Packet.Image := TJPEGImage.Create;
Image := TBitmap.Create;
Image.Width := Screen.Width;
Image.Height := Screen.Height;
Image.Canvas.CopyRect(
Rect(0, 0, Screen.Width, Screen.Height),
can,
Rect(0, 0, Screen.Width, Screen.Height)
);
Packet.Image.Assign(Image);
Packet.Image.CompressionQuality := 50;
Packet.student := 'student';
IdUDPClient1.BufferSize := SizeOf(packet);
SetLength(by, sizeof(packet));
Move(packet, by[0], sizeof(packet));
IdUDPClient1.SendBuffer('127.0.0.1', 5, by);
Image.Free;
Packet.Image.Free;
ReleaseDC(0, can.Handle);
end;
procedure TDesktop.FormShow(Sender: TObject);
begin
IdUDPServer1.BufferSize:=SizeOf(packet);
end;
procedure TDesktop.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
AData: array of Byte; ABinding: TIdSocketHandle);
begin
Packet.Image := TJPEGImage.Create;
Move(AData[0], packet, sizeof(AData));
Caption := Packet.student;
Packet.Image.SaveToFile('E:\1.jpg');
Packet.Image.Free;
end;
Your TPacket contains an object pointer and a dynamically allocated string. You cannot transmit those values as-is to another machine, or even another process on the same machine. You must serialize the values into raw bytes, then transmit and receive the bytes, then deserialize the bytes back into viable data on the receiving end.
Try something more like this:
procedure ScreenshotToJpgStream(Stream: TStream);
var
R: TRect;
can: TCanvas;
dc: HDC;
Bmp: TBitmap;
Jpg: TJPEGImage;
begin
R := Rect(0, 0, Screen.Width, Screen.Height);
Jpg := TJPEGImage.Create;
try
Bmp := TBitmap.Create;
try
Bmp.Width := R.Width;
Bmp.Height := R.Height;
can := TCanvas.Create;
try
dc := GetWindowDC(0);
can.Handle := dc;
try
Bmp.Canvas.CopyRect(R, can, R);
finally
can.Handle := 0;
ReleaseDC(0, dc);
end;
finally
can.Free;
end;
Jpg.Assign(Bmp);
finally
Bmp.Free;
end;
Jpg.CompressionQuality := 50;
Jpg.SaveToStream(Stream);
finally
Jpg.Free;
end;
end;
procedure TDesktop.Button1Click(Sender: TObject);
var
ImageStrm: TBytesStream;
Student: String;
Packet: TIdBytes;
Offset, ImageLen, StudentLen: Integer;
begin
ImageStrm := TBytesStream.Create;
try
ScreenshotToJpgStream(ImageStrm);
ImageLen := ImageStrm.Size;
Student := 'student';
StudentLen := TIdTextEncoding.UTF8.GetByteCount(Student);
SetLength(Packet, (SizeOf(Integer)*2) + ImageLen + StudentLen);
Offset := 0;
CopyTIdLongInt(ImageLen, Packet, Offset);
Inc(Offset, 4);
CopyTIdByteArray(ImageStrm.Bytes, 0, Packet, Offset, ImageLen);
Inc(Offset, ImageLen);
CopyTIdLongInt(StudentLen, Packet, Offset);
Inc(Offset, 4);
CopyTIdString(Student, Packet, Offset, -1, TIdTextEncoding.UTF8);
finally
ImageStrm.Free;
end;
IdUDPClient1.SendBuffer('127.0.0.1', Port, Packet);
end;
procedure TDesktop.IdUDPServer1UDPRead(AThread: TIdUDPListenerThread;
AData: array of Byte; ABinding: TIdSocketHandle);
type
PIdBytes = ^TIdBytes;
var
ImageStrm: TIdMemoryStream;
Student: String;
Offset, Len: Integer;
begin
Offset := 0;
Len := BytesToLongInt(PIdBytes(#AData)^, Offset);
Inc(Offset, 4);
Assert(Length(AData) >= (Offset+Len));
ImageStrm := TIdMemoryStream.Create(#AData[Offset], Len);
try
Inc(Offset, Len);
Len := BytesToLongInt(PIdBytes(#AData)^, Offset);
Inc(Offset, 4);
Student := BytesToString(PIdBytes(#AData)^, Offset, Len, TIdTextEncoding.U2TF8);
Caption := Student;
Jpg := TJPEGImage.Create;
try
Jpg.LoadFromStream(ImageStrm);
Jpg.SaveToFile('E:\1.jpg');
finally
Jpg.Free;
end;
finally
ImageStrm.Free;
end;
end;
I am trying to get the data from the chunk IDATA data from this png picture. When this picture is opened in a TextEditor I can appreciate the chunk IDAT after PLTE and before IEND, the length is about 336, of course is not 0, but when I get the datasize width Delphi the result is 0 and data is empty.
This is my code:
procedure TForm1.Button1Click(Sender: TObject);
var
png: TPngImage;
i,size: Integer;
Buffer: Pointer;
begin
for i := 0 to pred(png.Chunks.Count) do
begin
if(png.Chunks.Item[i].Name='IDAT')then
begin
Buffer := png.Chunks.Item[i].Data;//this is empty
size:= png.Chunks.Item[i].DataSize;// is 0, How is that possible ?
break;
end;
end;
end;
#TLama, #Remy I try this but not working:
procedure TForm1.Button1Click(Sender: TObject);
var
png: TPngImage;
i,size: Integer;
Buffer: Pointer;
stream: TStream;
begin
png := TPngImage.Create;
png.LoadFromFile('C:\temp\example.png');
stream := TMemoryStream.Create;
for i := 0 to pred(png.Chunks.Count) do
begin
if(png.Chunks.Item[i] is TChunkIDAT )then
begin
TChunkIDAT(Png.Chunks.Item[I]).SaveToStream(stream);
if Assigned(Stream) then
begin
Buffer := AllocMem(Stream.Size);
Stream.Position := 0;
Stream.Read(Buffer^, Stream.Size);
end;
end;
end;
The result of Buffer is:
IDATxÚíÙÙƒ Ðæÿ?Ú.V¥H åN|r_„„ ‹þÞTË»Vn†Á„s[dHÁ¬T¶1c•…®=Õ¨Íèì#½Z˜q+–'Š£–,‡Æ¬Ù2sñ`KPÖ÷bïÔoÏÓͬ´ãËfZÒôwSîjE¸‰w6bÜ<îQî)R´{î~MŠww” îGEÂñî›Â÷%/xÁ~0ÆòS/xx¾ßâ„[Ü.·¯Æ$pg'Üiw>Æep9wšÍú8˵<—#ÝÈìyÑ‚\¦-ÌÞZÓùjKº3CodÔ$ôöxF«ëN#¸Y¥MÎ;Թת¦Z^0ø Cý?Dã2k‚
But if you open the image, the IDAt is this one:
X…íØÙ
Ã0Ðì¿tEŽ6vt‘jsgèÇ¢–eÆœ×"Õ‹€„ð´ y<£Æí¼²«X']É:èjÖH#XbŸh¤Û‘±l›Æ»÷2ý“9î¯Ìr¿ež{•™îY溇ÌvwYóݬ‚î[žð„Q°ÆrÔ ï[põÑm™º½Z×$tÝI×uýXwÐÝ#àt“Ë=H?°(ÚÀ"h#[M;ØJÚÉÙ5oÇÕžF#xjçdë$ê/²‚¯©Œ
The IDAT chunk contains the actual image data, but the TChunkIDAT item for that chunk does not keep a copy of the image data inside of itself, which is why its DataSize is 0. When loading the IDAT chunk, TChunkIDAT decodes the image data and stores it in the ImageData and ImageAlpha members of the TChunkIHDR item for the IHDR chunk.
After the whole investigation day, I was able to create my own function to get IDAT chunk, I post for everyone needs it.
function GetChunk(fileName,chunkName:String;var chunkLen:Integer):Pointer;
var
bytes,iData: TArray<byte>;
data,chunkType: AnsiString;
i,p,cont:Integer;
function getBytes(data:AnsiString;pos,num:Integer):Integer;
var
i:Integer;
begin
Result := 0;
for i := 1 to num do
begin
Result := Result * 256;
Result := Result + ORD(data[pos+i]);
end;
end;
begin
if(not FileExists(FileName))then
Exit(nil);
data := '';
bytes := System.IOUtils.TFile.ReadAllBytes(fileName);
for i := Low(bytes) to High(bytes) do
data := data + chr(bytes[i]);
p := 8;
while (p<Length(bytes)) do
begin
chunkLen := getBytes(data,p,4);
chunkType := copy(data,p+5,4);
if(chunkType=chunkName)then
begin
inc(p,8);
break;
end;
inc(p,chunkLen+12);
end;
SetLength(iData,chunkLen+1);
cont := 0;
for i := p to p+chunkLen do
begin
iData[cont] := bytes[i];
inc(cont);
end;
Result := iData;
end;
This question already has answers here:
How to insert image into database using TADOQuery Component Only
(2 answers)
Store images in MS-Access Database using Delphi6
(1 answer)
Closed 10 years ago.
I'm using this code to load images into my Timage:
begin
if OpenPictureDialog1.Execute(Self.Handle) then
Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
end;
Then I'm using this code to store into my ms access database:
var
AStream : TMemoryStream;
begin
Adotable1.Append;
AStream := TMemoryStream.Create;
try
Image1.Picture.Graphic.SaveToStream(AStream);
AStream.Position := 0;
if Adotable1.Active then
begin
TBlobField(Adotable1.FieldByName('Termograma')).LoadFromStream(AStream);
end;
finally
AStream.Free;
end;
adotable1.Post;
But now I want to display these saved images on a Timage, can anyone help me?
The images are .jpeg format
As far as TPicture is not able to decide which kind of TGraphic it has to create for loading, since a stream does not have an extension like a filename you have to decide it, and assign the Graphic.
In this case a TJPEGImage to the Picture.
var
JPG:TJPEGImage;
ms:TMemoryStream;
begin
JPG:=TJPEGImage.Create;
ms:=TMemoryStream.Create;
try
TBlobField(AdoTable1.FieldByName('Termograma')).SaveToStream(ms);
ms.Position := 0;
JPG.LoadFromStream(ms);
Image2.Picture.Assign(JPG);
finally
JPG.Free;
ms.Free;
end;
end;
The following unit is able to store diffent graphicformats in blobfields.
Storage is not compatible to simple storing of image data because information about then graphicformat is stored too, to give the ability to create the needed class for loading.
unit LoadSaveImageBlobs;
// 20120224 by Thomas Wassermann
// Adapt. RegisterClasses and uses for your requirements
// based on an Idea of Emiliano Sos
interface
uses Classes,DB,Graphics,Jpeg,PngImage;
Procedure SavePicture2Blob(Blob: TBlobField; Picture: TPicture);
Procedure LoadPictureFromBlob(Picture: TPicture; Blob: TBlobField);
implementation
Procedure SavePicture2Blob(Blob: TBlobField; Picture: TPicture);
var
ms, ms2: TMemoryStream;
theClassName: AnsiString;
len: Byte;
begin
ms := TMemoryStream.Create;
try
Blob.Clear;
theClassName := Picture.Graphic.ClassName;
len := Length(theClassName);
ms.WriteBuffer(len, 1);
if len > 0 then
ms.WriteBuffer(theClassName[1], len);
ms2 := TMemoryStream.Create;
try
Picture.Graphic.SaveToStream(ms2);
ms2.Position := 0;
if ms2.Size > 0 then
ms.CopyFrom(ms2, ms2.Size);
finally
ms2.Free;
end;
Blob.LoadFromStream(ms);
finally
ms.Free;
end;
end;
Procedure LoadPictureFromBlob(Picture: TPicture; Blob: TBlobField);
var
ms, ms2: TMemoryStream;
len: Byte;
theClassName: AnsiString;
Graphic: TGraphic;
GraphicClass: TGraphicClass;
begin
ms := TMemoryStream.Create;
Blob.SaveToStream(ms);
ms.Position := 0;
try
ms.ReadBuffer(len, 1);
SetLength(theClassName, len);
if len > 0 then
ms.ReadBuffer(theClassName[1], len);
GraphicClass := TGraphicClass(FindClass(theClassName));
if (GraphicClass <> nil) and (len > 0) then
begin
Graphic := GraphicClass.Create;
ms2 := TMemoryStream.Create;
try
ms2.CopyFrom(ms, ms.Size - len - 1);
ms2.Position := 0;
Graphic.LoadFromStream(ms2);
finally
ms2.Free;
end;
Picture.Assign(Graphic);
end;
finally
ms.Free;
end;
end;
initialization
// you might register others if wished
RegisterClasses([TIcon, TMetafile, TBitmap, TJPEGImage,TPngImage]);
end.