Greetings...
I've been slowly building a remote desktop system, and creating a set of components for it. My main struggle is the handling of the image its self. Now I have most of it down, I've already had a fully-functional version working, so it's not a matter of starting from scratch. However, trying to make it high performance is becoming rather tricky, as I don't have much of a background in working with images.
Now below I have pasted the entire unit which handles almost all of the graphics code. I'm pasting everything I have in this unit for a few reasons, first of all, I'm not greedy like most people, I like to share my work. Second of all, it will help give a better understanding of what I'm trying to accomplish, and third of all, because in most cases, no one can help unless they have full code. So here it is:
Now most of this unit I have down ok. However, there's a component which will be the main component in the RD client application called TJDRMCustomDesktop which I'm currently working on. The stupid part is that I cannot seem to acquire an image from it as expected. The way it works is like this:
- Take screenshot (BMP) of entire screen
- Slice screenshot BMP into smaller pieces (pre-specified col/row count)
- Compare each piece of BMP with the previously sent corresponding piece of BMP
-- If new screenshot bmp section is different from the last one, trigger an event (fNewBlockEvent)
This event is supposed to return the newest piece of the latest screenshot as a TJDRMImageBlock which contains the bmp section along with the x and y position of the screen to place it on the other end.
Unfortunately for some reason, it's not returning anything and I cant figure it out... I think it may have to do with the X,Y loops where I'm scanning the newly taken screenshot in the TimerOnTimer procedure, but I just can't seem to put my finger on it.
If someone gets some time to play around with this unit and see what they come up with and let me know, it would be greatly appreciated. Again, it's mainly the TJDRMCustomDesktop component I need help on at this point, unless someone wants to look over the rest of the code and recommend some more changes. And again, I pasted the entire unit so it's up for grabs to anyone who wants it.
And at the same time, can anyone give me a quality rating from 1 to 10 how well you think I put this together? I'm looking for honest opinions so I know if my practice is up to par.
Thank you much
I've been slowly building a remote desktop system, and creating a set of components for it. My main struggle is the handling of the image its self. Now I have most of it down, I've already had a fully-functional version working, so it's not a matter of starting from scratch. However, trying to make it high performance is becoming rather tricky, as I don't have much of a background in working with images.
Now below I have pasted the entire unit which handles almost all of the graphics code. I'm pasting everything I have in this unit for a few reasons, first of all, I'm not greedy like most people, I like to share my work. Second of all, it will help give a better understanding of what I'm trying to accomplish, and third of all, because in most cases, no one can help unless they have full code. So here it is:
Code:
unit JDRMGraphics;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, JPeg, StrUtils, ExtCtrls, Forms;
type
TJDRMImageBlock = class;
TJDRMImageSplitter = class;
TJDRMCustomDesktop = class;
TJDRMDesktop = class;
TJDRMImageBlockEvent = procedure(Sender: TObject; Block: TJDRMImageBlock) of object;
TJDRMImageBlock = class(TBitmap)
private
fPosition: TPoint;
procedure SetLeft(Value: Integer);
procedure SetTop(Value: Integer);
function GetLeft: Integer;
function GetTop: Integer;
public
constructor Create; overload;
constructor Create(Img: TBitmap; Position: TPoint); overload;
destructor Destroy;
published
property Left: Integer read GetLeft write SetLeft;
property Top: Integer read GetTop write SetTop;
property Width;
property Height;
end;
TJDRMImageSplitter = class(TBitmap)
private
fHCount: Integer;
fVCount: Integer;
fBitWidth: Integer;
fBitHeight: Integer;
procedure SetVCount(Value: Integer);
procedure SetHCount(Value: Integer);
public
constructor Create; override;
destructor Destroy; override;
function GetBlock(X, Y: Integer): TJDRMImageBlock;
procedure SetBlock(Value: TJDRMImageBlock);
published
property VCount: Integer read fVCount write SetVCount;
property HCount: Integer read fHCount write SetHCount;
end;
TJDRMCustomDesktop = class(TComponent)
private
fActive: Boolean;
fTimer: TTimer;
fBitmap: TJDRMImageSplitter;
fLastBitmap: TJDRMImageSplitter;
fBlock: TPoint;
fBusy: Boolean;
fNewBlockEvent: TJDRMImageBlockEvent;
procedure NewBlock(Block: TJDRMImageBlock);
procedure SetActive(Value: Boolean);
procedure SetVerticalCount(Value: Integer);
procedure SetHorizontalCount(Value: Integer);
procedure SetPixelFormat(Value: TPixelFormat);
function GetVerticalCount: Integer;
function GetHorizontalCount: Integer;
function GetPixelFormat: TPixelFormat;
procedure GetScreenshot;
procedure TimerOnTimer(Sender: TObject);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetNextBlock: TJDRMImageBlock;
property Active: Boolean read fActive write SetActive;
property Bitmap: TJDRMImageSplitter read fBitmap;
property VerticalCount: Integer read GetVerticalCount write SetVerticalCount;
property HorizontalCount: Integer read GetHorizontalCount write SetHorizontalCount;
property PixelFormat: TPixelFormat read GetPixelFormat write SetPixelFormat;
property OnNewBlock: TJDRMImageBlockEvent read fNewBlockEvent write fNewBlockEvent;
end;
TJDRMDesktop = class(TJDRMCustomDesktop)
published
property Active;
property Bitmap;
property VerticalCount;
property HorizontalCount;
property PixelFormat;
property OnNewBlock;
end;
function BitmapsAreSame(Bitmap1, Bitmap2: TJDRMImageBlock): Boolean;
function GetPixelSize(informat: TPixelFormat): Integer;
function ScreenShot(DrawCursor: Boolean; Quality: TPixelFormat; Compression: Integer): TBitmap;
function CompressImage(Bitmap: TBitmap; Compression: Integer): TJpegImage;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('JD Remote Desktop', [TJDRMDesktop]);
end;
function GetPixelSize(informat: TPixelFormat): Integer;
// returns proper byte size for input
begin
case informat of
pf8bit: Result := 1;
pf16bit: Result := 2;
pf24bit: Result := 3;
pf32bit: Result := 4;
else
Result := 0;
end;
end;
function BitmapsAreSame(Bitmap1, Bitmap2: TJDRMImageBlock): Boolean;
var
scanptr1, scanptr2: pointer;
i: integer;
PixelSize: byte;
begin
Result := false;
if (Bitmap1.Width = Bitmap2.Width) and
(Bitmap1.Height = Bitmap2.Height) and
(Bitmap1.PixelFormat = Bitmap2.PixelFormat) then
begin
PixelSize := GetPixelSize(Bitmap1.PixelFormat);
for i := 0 to (Bitmap1.Height-1) do
begin
scanptr1 := Bitmap1.ScanLine[i];
scanptr2 := Bitmap2.ScanLine[i];
Result := CompareMem(scanptr1, scanptr2, Bitmap1.Width*PixelSize);
if Result = false then break;
end;
end;
end;
function ScreenShot(DrawCursor: Boolean; Quality: TPixelFormat; Compression: Integer): TBitmap;
var
DC: HDC;
R: TRect;
CI: TCursorInfo;
Icon: TIcon;
II: TIconInfo;
begin
Result:= TBitmap.Create;
DC:= GetDC(GetDesktopWindow);
try
Result.Width:= GetDeviceCaps (DC, HORZRES);
Result.Height:= GetDeviceCaps (DC, VERTRES);
Result.PixelFormat:= Quality;
BitBlt(Result.Canvas.Handle,
0,
0,
Result.Width,
Result.Height,
DC,
0,
0,
SRCCOPY);
finally
ReleaseDC(GetDesktopWindow,DC);
end;
//Draw cursor
R:= Result.Canvas.ClipRect;
Icon:= TIcon.Create;
try
CI.cbSize:= SizeOf(CI);
if GetCursorInfo(CI) then
if CI.Flags = CURSOR_SHOWING then
begin
Icon.Handle:= CopyIcon(CI.hCursor);
if GetIconInfo(Icon.Handle, II) then
begin
Result.Canvas.Draw(
ci.ptScreenPos.x - Integer(II.xHotspot) - r.Left,
ci.ptScreenPos.y - Integer(II.yHotspot) - r.Top,
Icon);
end;
end;
finally
Icon.Free;
end;
end;
function CompressImage(Bitmap: TBitmap; Compression: Integer): TJpegImage;
begin
Result:= TJpegImage.Create;
try
Result.CompressionQuality:= Compression;
Result.Assign(Bitmap);
except
on e: exception do begin end;
end;
end;
//TJDRMImageBlock
constructor TJDRMImageBlock.Create(Img: TBitmap; Position: TPoint);
begin
Self.Create;
Self.fPosition:= Position;
Self.Assign(Img);
end;
constructor TJDRMImageBlock.Create;
begin
inherited Create;
Self.fPosition:= Point(0,0);
end;
destructor TJDRMImageBlock.Destroy;
begin
inherited Destroy;
end;
procedure TJDRMImageBlock.SetLeft(Value: Integer);
begin
Self.fPosition.X:= Value;
end;
procedure TJDRMImageBlock.SetTop(Value: Integer);
begin
Self.fPosition.Y:= Value;
end;
function TJDRMImageBlock.GetLeft: Integer;
begin
Result:= Self.fPosition.X;
end;
function TJDRMImageBlock.GetTop: Integer;
begin
Result:= Self.fPosition.Y;
end;
constructor TJDRMImageSplitter.Create;
begin
inherited Create;
Self.PixelFormat:= pf24bit;
end;
destructor TJDRMImageSplitter.Destroy;
begin
inherited Destroy;
end;
function TJDRMImageSplitter.GetBlock(X, Y: Integer): TJDRMImageBlock;
begin
if (X >= 0) and (X < Self.fHCount) and
(Y >= 0) and (Y < Self.fVCount) then
begin
Result:= TJDRMImageBlock.Create;
try
Result.Width:= Self.fBitWidth;
Result.Height:= Self.fBitHeight;
if Result.PixelFormat <> Self.PixelFormat then
Result.PixelFormat:= Self.PixelFormat;
Result.Left:= fBitWidth * X;
Result.Top:= fBitHeight * Y;
Result.Canvas.CopyRect(
Rect(
0,
0,
fBitWidth,
fBitHeight
),
Self.Canvas,
Rect(
fBitWidth * X,
fBitHeight * Y,
Self.Width - (fBitWidth * (Self.fHCount - X - 1)),
Self.Height - (fBitHeight * (Self.fVCount - Y - 1))
)
);
except
on e: exception do begin
raise exception.Create('Error during block acquisition: '+#10+
e.Message);
end;
end;
end else begin
raise exception.Create('Block index out of bounds ['+IntToStr(X)+','+IntToStr(Y)+']');
end;
end;
procedure TJDRMImageSplitter.SetBlock(Value: TJDRMImageBlock);
var
X, Y: Integer;
begin
if assigned(Value) then begin
if Value <> nil then begin
X:= Value.fPosition.X;
Y:= Value.fPosition.Y;
Self.Canvas.Draw(X, Y, Value);
end;
end;
end;
procedure TJDRMImageSplitter.SetVCount(Value: Integer);
begin
Self.fVCount:= Value;
Self.fBitHeight:= Round(Self.Height / Self.fVCount);
end;
procedure TJDRMImageSplitter.SetHCount(Value: Integer);
begin
Self.fHCount:= Value;
Self.fBitWidth:= Round(Self.Width / Self.fHCount);
end;
constructor TJDRMCustomDesktop.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Self.fBusy:= True;
Self.fBitmap:= TJDRMImageSplitter.Create;
Self.fBitmap.PixelFormat:= pf24bit;
Self.fLastBitmap:= TJDRMImageSplitter.Create;
Self.fLastBitmap.PixelFormat:= pf24bit;
Self.fTimer:= TTimer.Create(nil);
Self.fTimer.Enabled:= False;
Self.fTimer.Interval:= 10;
Self.fTimer.OnTimer:= Self.TimerOnTimer;
Self.fBlock:= Point(0,0);
Self.fBusy:= False;
end;
destructor TJDRMCustomDesktop.Destroy;
begin
Self.fBusy:= True;
if assigned(fBitmap) then fBitmap.Free;
if assigned(fLastBitmap) then fLastBitmap.Free;
if assigned(fTimer) then fTimer.Free;
inherited Destroy;
end;
procedure TJDRMCustomDesktop.TimerOnTimer(Sender: TObject);
var
X, Y: Integer;
B, B2: TJDRMImageBlock;
begin
//Acquire next screenshot and process it
if not Self.fBusy then begin
Self.fBusy:= True;
try
if Self.fActive then begin
Self.GetScreenshot;
for X:= 0 to Self.fBitmap.HCount - 1 do begin
for Y:= 0 to Self.fBitmap.VCount - 1 do begin
if not Self.fActive then Exit;
B:= Self.fBitmap.GetBlock(X, Y);
B2:= Self.fLastBitmap.GetBlock(X, Y);
try
if not BitmapsAreSame(B, B2) then
begin
Self.NewBlock(B);
Self.fLastBitmap.SetBlock(B);
Application.ProcessMessages;
end;
finally
B.Free;
B2.Free;
end;
end;
end;
end;
finally
Self.fBusy:= False;
end;
end;
end;
procedure TJDRMCustomDesktop.SetActive(Value: Boolean);
begin
Self.fActive:= Value;
Self.fTimer.Enabled:= Value;
end;
procedure TJDRMCustomDesktop.SetVerticalCount(Value: Integer);
begin
Self.fBitmap.VCount:= Value;
Self.fLastBitmap.VCount:= Value;
end;
procedure TJDRMCustomDesktop.SetHorizontalCount(Value: Integer);
begin
Self.fBitmap.HCount:= Value;
Self.fLastBitmap.HCount:= Value;
end;
procedure TJDRMCustomDesktop.SetPixelFormat(Value: TPixelFormat);
begin
Self.fBitmap.PixelFormat:= Value;
Self.fLastBitmap.PixelFormat:= Value;
end;
function TJDRMCustomDesktop.GetVerticalCount: Integer;
begin
Result:= Self.fBitmap.VCount;
end;
function TJDRMCustomDesktop.GetHorizontalCount: Integer;
begin
Result:= Self.fBitmap.HCount;
end;
function TJDRMCustomDesktop.GetPixelFormat: TPixelFormat;
begin
Result:= Self.fBitmap.PixelFormat;
end;
procedure TJDRMCustomDesktop.GetScreenshot;
var
B: TBitmap;
begin
B:= ScreenShot(True, pf24bit, 100);
try
Self.fBitmap.Assign(B);
finally
B.Free;
end;
end;
function TJDRMCustomDesktop.GetNextBlock: TJDRMImageBlock;
begin
if Self.fBlock.X >= Self.fBitmap.HCount then
begin
Self.fBlock.X:= 0;
end else begin
Self.fBlock.X:= Self.fBlock.X + 1;
end;
if Self.fBlock.Y >= Self.fBitmap.VCount then
begin
Self.fBlock.Y:= 0;
end else begin
Self.fBlock.Y:= Self.fBlock.Y + 1;
end;
Result:= Self.fBitmap.GetBlock(fBlock.X, fBlock.Y);
end;
procedure TJDRMCustomDesktop.NewBlock(Block: TJDRMImageBlock);
begin
if assigned(Self.fNewBlockEvent) then
begin
Self.fNewBlockEvent(Self, Block);
end;
end;
end.
Now most of this unit I have down ok. However, there's a component which will be the main component in the RD client application called TJDRMCustomDesktop which I'm currently working on. The stupid part is that I cannot seem to acquire an image from it as expected. The way it works is like this:
- Take screenshot (BMP) of entire screen
- Slice screenshot BMP into smaller pieces (pre-specified col/row count)
- Compare each piece of BMP with the previously sent corresponding piece of BMP
-- If new screenshot bmp section is different from the last one, trigger an event (fNewBlockEvent)
This event is supposed to return the newest piece of the latest screenshot as a TJDRMImageBlock which contains the bmp section along with the x and y position of the screen to place it on the other end.
Unfortunately for some reason, it's not returning anything and I cant figure it out... I think it may have to do with the X,Y loops where I'm scanning the newly taken screenshot in the TimerOnTimer procedure, but I just can't seem to put my finger on it.
If someone gets some time to play around with this unit and see what they come up with and let me know, it would be greatly appreciated. Again, it's mainly the TJDRMCustomDesktop component I need help on at this point, unless someone wants to look over the rest of the code and recommend some more changes. And again, I pasted the entire unit so it's up for grabs to anyone who wants it.
And at the same time, can anyone give me a quality rating from 1 to 10 how well you think I put this together? I'm looking for honest opinions so I know if my practice is up to par.
Thank you much