Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Remote Desktop Imaging

Status
Not open for further replies.

djjd47130

Programmer
Nov 1, 2010
480
US
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:


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 :D
 
And by the way, that's a ready to install component, so you can add it to any package and install it also.
 
it's not returning anything and I cant figure it out...

Have you tried stepping through the code with the debugger if you can't see what is going on?

FWIW, in looking through the code, I couldn't find where you assign the values in the following:

Code:
for X:= 0 to Self.fBitmap.HCount - 1 do begin
for Y:= 0 to Self.fBitmap.VCount - 1 do begin


I'm not greedy like most people, I like to share my work.

Since you are new to this site, I'll take the time to point out the FAQ section. This is where people tend to share any code that is working and typically is good reference or to solve a problem for others (I always have questions about that), because everything in the regular forum area has a limited shelf life.

and third of all, because in most cases, no one can help unless they have full code.

But people tend to be discouraged from helping if you post a lot of code, because it tends to snow them in and they move on. You should have an idea of what is working and what is not and be able to present the relevant sections along with a description. Again, if you wish to share working code that seems to be novel and relevant, the FAQ section exists for that.

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.

I'm usually hard as a code reviewer (with a number of reasons born out of my work experience), but I'm a little more lenient on cases like this. That being said, there are two notes with this code that hopefully most will agree on here:

1) If you intend on sharing code, try to document it. This means descriptive variable names and procedure and function names along with comments. Along with the variable names, enumerated types and constants are good if there are random constant values that have significance.

For Delphi code and comments, it doesn't have to be very extravagant, but for other kinds of code (assembler for instance), you have to do almost every line. There are a lot of rules for good commenting, but at minimum every method usually (if the procedure/variable names aren't enough) requires a description of function (including meaning of passed parameters and return values) and every non-standard (i.e. not obvious from the code to most programmers) process requires a description.

Even if you don't share the code, remember that you will be sharing it with yourself when the code is far from fresh in your mind, so documentation is a necessity!

2) procedure TJDRMCustomDesktop.TimerOnTimer(Sender: TObject); has a problem with a complexity issue. Most readable code is generally considered to have a maximum logic depth of 3. You might consider breaking this procedure up into two or three separate things to make it easier to read.

It is not possible for anyone to acknowledge truth when their salary depends on them not doing it.
 
Update: I finally got it working. I wasn't setting the width and height of the bitmap, so it was always presumed to be 0x0 in size...

JD Solutions
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top