Delphi XE3 indy 10 UDP - delphi

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;

Related

Nicely scale image withour external libraries

I'm using Delphi 10.4.2 and I'm trying to find a way to scale images that mantains the image quality and doesn't request external libraries.
This is what I tried, you can find the two tests in {$REGION}:
procedure TFrmTestGenImg.Test;
var
LOldWidth, LOldHeight, LNewWidth, LNewHeight: integer;
LImageNameIn, LImageNameOut, LExt: string;
LClass: TGraphicClass;
LImageIn, LImageOut: TGraphic;
LBitmap, LResized: TBitmap;
begin
// Original image: 1366 x 768
LOldWidth := 1366;
LOldHeight := 768;
LNewWidth := 800;
LNewHeight := 449;
LImageNameIn := 'C:\temp\Input.png';
LImageNameOut := 'C:\temp\Output_' + FormatDateTime('yyyy.mm.dd hh.nn.ss.zzz', Now) + '.png';
LExt := TPath.GetExtension(LImageNameIn);
Delete(LExt, 1, 1);
if (CompareText(LExt, 'bmp') = 0) then
LClass := TBitmap
else if (CompareText(LExt, 'gif') = 0) then
LClass := TGIFImage
else
LClass := TWICImage;
LImageIn := LClass.Create;
try
LImageOut := LClass.Create;
try
LImageIn.Transparent := True;
LImageIn.LoadFromFile(Trim(LImageNameIn));
LBitmap := TBitmap.Create;
try
LBitmap.PixelFormat := pf24bit;
LBitmap.Assign(LImageIn);
{$REGION '1st test'}
LBitmap.Canvas.StretchDraw(
Rect(0, 0, LNewWidth, LNewHeight),
LImageIn); // -> poor quality
LBitmap.SetSize(LNewWidth, LNewHeight);
LImageOut.Assign(LBitmap);
{$ENDREGION}
{$REGION '2nd test'}
LResized := TBitmap.Create;
try
LResized.Assign(LBitmap);
LResized.Width := LNewWidth;
LResized.Height := LNewHeight;
GraphUtil.ScaleImage(LBitmap, LResized, (LNewWidth/LOldWidth)); // -> empty image
LResized.SetSize(LNewWidth, LNewHeight);
LImageOut.Assign(LResized);
finally
LResized.Free;
end;
{$ENDREGION}
if LImageIn is TWICImage then
begin
if (CompareText(LExt, 'jpg') = 0) or (CompareText(LExt, 'jpeg') = 0) then
TWICImage(LImageOut).ImageFormat := wifJpeg
else
TWICImage(LImageOut).ImageFormat := TWICImage(LImageIn).ImageFormat;
end;
LImageOut.SaveToFile(LImageNameOut);
finally
LBitmap.Free;
end;
finally
LImageOut.Free;
end;
finally
LImageIn.Free;
end;
end;
As you can see, for the second test I used GraphUtil.ScaleImage but the output is an empty image, so I'm not sure I used it correctly, unfortunately I haven't found any example of this method..
procedure ResizeBitmap(const Bitmap: TBitmap; const NewWidth, NewHeight: integer);
var
Factory: IWICImagingFactory;
Scaler: IWICBitmapScaler;
Source : TWICImage;
begin
Source := TWICImage.Create;
try
Factory := TWICImage.ImagingFactory;
Source.Assign(Bitmap);
Factory.CreateBitmapScaler(Scaler);
Scaler.Initialize(Source.Handle, NewWidth, NewHeight, WICBitmapInterpolationModeHighQualityCubic);
Source.Handle := IWICBitmap(Scaler);
Bitmap.Assign(Source);
Scaler := nil;
Factory := nil;
finally
Source.Free;
end;
end;
A little simpler
procedure ResizeBitmap(const Bitmap: TBitmap; const NewWidth, NewHeight: integer);
Var vImage,v2: TWICImage;
begin
vImage := TWICImage.Create;
try
vImage.Assign(Bitmap);
v2 := vImage.CreateScaledCopy(NewWidth, NewHeight, wipmHighQualityCubic);
Bitmap.Assign(v2);
finally
v2.Free;
vImage.Free;
end;
end;

TMemoryStream of a file with a string attached to it

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;

How get size of a Jpeg? [duplicate]

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.

Canvas does not allow drawing

I want to Draw a Screenshot from the entire screen to a TForm1 Canvas.
This code works well in Delphi XE3
procedure TForm1.Button1Click(Sender: TObject);
var
c,scr: TCanvas;
r,r2: TRect;
begin
c := TCanvas.Create;
scr := TCanvas.Create;
c.Handle := GetWindowDC(GetDesktopWindow);
try
r := Rect(0, 0, 200, 200);
form1.Canvas.CopyRect(r, c, r);
finally
ReleaseDC(0, c.Handle);
c.Free;
end;
Now I want to copy the screenshot to another canvas first.
Is there a way to do this without getting this error?
procedure TForm1.Button1Click(Sender: TObject);
var
c,scr: TCanvas;
r,r2: TRect;
begin
c := TCanvas.Create;
scr := TCanvas.Create;
c.Handle := GetWindowDC(GetDesktopWindow);
try
r := Rect(0, 0, 200, 200);
scr.CopyRect(r,c,r); <-- Error, canvas does not allow drawing
form1.Canvas.CopyRect(r, scr, r); <-- Error, canvas does not allow drawing
finally
ReleaseDC(0, c.Handle);
c.Free;
end;
If you need to work with an additional canvas you will have to assign a HDC e.g.
var
WindowHandle:HWND;
ScreenCanvas,BufferCanvas: TCanvas;
r,r2: TRect;
ScreenDC,BufferDC :HDC;
BufferBitmap : HBITMAP;
begin
WindowHandle := 0;
ScreenCanvas := TCanvas.Create;
BufferCanvas := TCanvas.Create;
ScreenDC:=GetWindowDC(WindowHandle);
ScreenCanvas.Handle := ScreenDC;
BufferDC := CreateCompatibleDC(ScreenDC);
BufferCanvas.Handle := BufferDC;
BufferBitmap := CreateCompatibleBitmap(ScreenDC,
GetDeviceCaps(ScreenDC, HORZRES),
GetDeviceCaps(ScreenDC, VERTRES));
SelectObject(BufferDC, BufferBitmap);
try
r := Rect(0, 0, 200, 200);
BufferCanvas.CopyRect(r,ScreenCanvas,r);
form1.Canvas.CopyRect(r, BufferCanvas, r);
finally
ReleaseDC(WindowHandle, ScreenCanvas.Handle);
DeleteDC(BufferDC);
DeleteObject(BufferBitmap);
BufferCanvas.Free;
ScreenCanvas.Free;
end;
end;
It's a time to toss my solution into the pot!
procedure TForm1.FormClick(Sender: TObject);
var
ScreenCanvas: TCanvas;
begin
ScreenCanvas := TCanvas.Create;
try
ScreenCanvas.Handle := GetWindowDC(GetDesktopWindow);
Win32Check(ScreenCanvas.HandleAllocated);
Canvas.CopyRect(Canvas.ClipRect, ScreenCanvas, ScreenCanvas.ClipRect);
finally
ReleaseDC(GetDesktopWindow, ScreenCanvas.Handle);
ScreenCanvas.Free;
end;
end;

How to take a screenshot of the Active Window in Delphi?

For full screenshots, I use this code:
form1.Hide;
sleep(500);
bmp := TBitmap.Create;
bmp.Height := Screen.Height;
bmp.Width := Screen.Width;
DCDesk := GetWindowDC(GetDesktopWindow);
BitBlt(bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, DCDesk, 0, 0, SRCCOPY);
form1.Show ;
FileName := 'Screenshot_'+FormatDateTime('mm-dd-yyyy-hhnnss',now());
bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
ReleaseDC(GetDesktopWindow, DCDesk);
bmp.Free;
How can I convert that to take a screenshot of only the active window.
First of all you have to get the right window. As sharptooth already noted you should use GetForegroundWindow instead of GetDesktopWindow. You have done it right in your improved version.
But then you have to resize your bitmap to the actual size of the DC/Window. You haven't done this yet.
And then make sure you don't capture some fullscreen window!
When I executed your code, my Delphi IDE was captured and as it is on fullscreen by default, it created the illusion of a fullscreen screenshot. (Even though your code is mostly correct)
Considering the above steps, I was successfully able to create a single-window screenshot with your code.
Just a hint: You can GetDC instead of GetWindowDC if you are only interested in the client area. (No window borders)
EDIT: Here's what I made with your code:
You should not use this code! Look at the improved version below.
procedure TForm1.Button1Click(Sender: TObject);
const
FullWindow = True; // Set to false if you only want the client area.
var
hWin: HWND;
dc: HDC;
bmp: TBitmap;
FileName: string;
r: TRect;
w: Integer;
h: Integer;
begin
form1.Hide;
sleep(500);
hWin := GetForegroundWindow;
if FullWindow then
begin
GetWindowRect(hWin,r);
dc := GetWindowDC(hWin) ;
end else
begin
Windows.GetClientRect(hWin, r);
dc := GetDC(hWin) ;
end;
w := r.Right - r.Left;
h := r.Bottom - r.Top;
bmp := TBitmap.Create;
bmp.Height := h;
bmp.Width := w;
BitBlt(bmp.Canvas.Handle, 0, 0, w, h, DC, 0, 0, SRCCOPY);
form1.Show ;
FileName := 'Screenshot_'+FormatDateTime('mm-dd-yyyy-hhnnss',now());
bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
ReleaseDC(hwin, DC);
bmp.Free;
end;
EDIT 2: As requested I'm adding a better version of the code, but I'm keeping the old one as a reference. You should seriously consider using this instead of your original code. It'll behave much nicer in case of errors. (Resources are cleaned up, your form will be visible again, ...)
procedure TForm1.Button1Click(Sender: TObject);
const
FullWindow = True; // Set to false if you only want the client area.
var
Win: HWND;
DC: HDC;
Bmp: TBitmap;
FileName: string;
WinRect: TRect;
Width: Integer;
Height: Integer;
begin
Form1.Hide;
try
Application.ProcessMessages; // Was Sleep(500);
Win := GetForegroundWindow;
if FullWindow then
begin
GetWindowRect(Win, WinRect);
DC := GetWindowDC(Win);
end else
begin
Windows.GetClientRect(Win, WinRect);
DC := GetDC(Win);
end;
try
Width := WinRect.Right - WinRect.Left;
Height := WinRect.Bottom - WinRect.Top;
Bmp := TBitmap.Create;
try
Bmp.Height := Height;
Bmp.Width := Width;
BitBlt(Bmp.Canvas.Handle, 0, 0, Width, Height, DC, 0, 0, SRCCOPY);
FileName := 'Screenshot_' +
FormatDateTime('mm-dd-yyyy-hhnnss', Now());
Bmp.SaveToFile(Format('C:\Screenshots\%s.bmp', [FileName]));
finally
Bmp.Free;
end;
finally
ReleaseDC(Win, DC);
end;
finally
Form1.Show;
end;
end;
Your code could be a lot simpler. When you have decided on which form you want to save, try the code I use:
procedure SaveFormBitmapToBMPFile( AForm : TCustomForm; AFileName : string = '' );
// Copies this form's bitmap to the specified file
var
Bitmap: TBitMap;
begin
Bitmap := AForm.GetFormImage;
try
Bitmap.SaveToFile( AFileName );
finally
Bitmap.Free;
end;
end;
This combines all the approaches described so far. It also handles multiple-monitor scenarios.
Pass in the kind of screenshot you want, and a TJpegImage, and it will assign your requested screenshot to that image.
///////////
uses
Jpeg;
type //define an ENUM to describe the possible screenshot types.
TScreenShotType = (sstActiveWindow, sstActiveClientArea,
sstPrimaryMonitor, sstDesktop);
///////////
procedure TfrmMain.GetScreenShot(shotType: TScreenShotType;
var img: TJpegImage);
var
w,h: integer;
DC: HDC;
hWin: Cardinal;
r: TRect;
tmpBmp: TBitmap;
begin
hWin := 0;
case shotType of
sstActiveWindow:
begin
//only the active window
hWin := GetForegroundWindow;
dc := GetWindowDC(hWin);
GetWindowRect(hWin,r);
w := r.Right - r.Left;
h := r.Bottom - r.Top;
end; //sstActiveWindow
sstActiveClientArea:
begin
//only the active client area (active window minus title bars)
hWin := GetForegroundWindow;
dc := GetDC(hWin);
GetWindowRect(hWin,r);
w := r.Right - r.Left;
h := r.Bottom - r.Top;
end; //sstActiveClientArea
sstPrimaryMonitor:
begin
//only the primary monitor. If 1 monitor, same as sstDesktop.
hWin := GetDesktopWindow;
dc := GetDC(hWin);
w := GetDeviceCaps(DC,HORZRES);
h := GetDeviceCaps(DC,VERTRES);
end; //sstPrimaryMonitor
sstDesktop:
begin
//ENTIRE desktop (all monitors)
dc := GetDC(GetDesktopWindow);
w := Screen.DesktopWidth;
h := Screen.DesktopHeight;
end; //sstDesktop
else begin
Exit;
end; //case else
end; //case
//convert to jpg
tmpBmp := TBitmap.Create;
try
tmpBmp.Width := w;
tmpBmp.Height := h;
BitBlt(tmpBmp.Canvas.Handle,0,0,tmpBmp.Width,
tmpBmp.Height,DC,0,0,SRCCOPY);
img.Assign(tmpBmp);
finally
ReleaseDC(hWin,DC);
FreeAndNil(tmpBmp);
end; //try-finally
end;
JCL comes to the rescue once again..
hwnd := GetForegroundWindow;
Windows.GetClientRect(hwnd, r);
JclGraphics.ScreenShot(theBitmap, 0, 0, r.Right - r.Left, r.Bottom - r.Top, hwnd);
// use theBitmap...
Thank you for this useful submission I thought I might make the code offered into a unit to use all over my application, here is the code I have running on DX10.2 Tokyo. Please note the example, watch out for memory leaks.
unit ScreenCapture;
interface
uses Windows, Vcl.Controls, Vcl.StdCtrls, VCL.Graphics,VCL.Imaging.JPEG, VCL.Forms;
function getScreenCapture( FullWindow: Boolean = True ) : TBitmap;
implementation
function getScreenCapture( FullWindow: Boolean ) : TBitmap;
var
Win: HWND;
DC: HDC;
WinRect: TRect;
Width: Integer;
Height: Integer;
begin
Result := TBitmap.Create;
//Application.ProcessMessages; // Was Sleep(500);
Win := GetForegroundWindow;
if FullWindow then
begin
GetWindowRect(Win, WinRect);
DC := GetWindowDC(Win);
end
else
begin
Windows.GetClientRect(Win, WinRect);
DC := GetDC(Win);
end;
try
Width := WinRect.Right - WinRect.Left;
Height := WinRect.Bottom - WinRect.Top;
Result.Height := Height;
Result.Width := Width;
BitBlt(Result.Canvas.Handle, 0, 0, Width, Height, DC, 0, 0, SRCCOPY);
finally
ReleaseDC(Win, DC);
end;
end;
end.
Example :
//Any event or button click, screenCapture is a TBitmap
screenCapture := getScreenCapture();
try
//Do some things with screen capture
Image1.Picture.Graphic := screenCapture;
finally
screenCapture.Free;
end;
Use GetForegroundWindow() instead of GetDesktopWindow().
You'll have to save the handle which GetForegroundWindow() return and pass the saved value into ReleaseDC() - to be sure that GetWindowDC() and ReleaseDC() are called exactly for the same window in case the active window changes between calls.
In case anyone is looking for a more cross-platform solution, this one claims Windows and MacOS-X support:
https://github.com/z505/screenshot-delphi
The shortest version of the Brian Frost code:
Screen.ActiveForm.GetFormImage.SaveToFile(Screen.ActiveForm.Caption+'.bmp');
Just one line of the code (Screenshot of the active window in the MDI application).

Resources