unit uCEFBrowserBitmap;

{$IFDEF FPC}
  {$MODE OBJFPC}{$H+}
{$ENDIF}

{$I cef.inc}

interface

uses
  {$IFDEF DELPHI16_UP}
    {$IFDEF MSWINDOWS}Winapi.Windows,{$ELSE}System.SyncObjs,{$ENDIF} System.Classes, System.SysUtils, Vcl.Graphics;
  {$ELSE}
    {$IFDEF MSWINDOWS}Windows,{$ENDIF} Classes, SysUtils, Graphics
    {$IFDEF FPC}, LCLProc, LCLType, LCLIntf, LResources, InterfaceBase{$ENDIF}
    {$IFNDEF MSWINDOWS}, SyncObjs{$ENDIF};
  {$ENDIF}

type
  TCEFBrowserBitmap = class(TBitmap)
    protected
      FScanlineSize            : integer;
      FDeviceScaleFactor       : single;
      {$IFDEF MSWINDOWS}
      FSyncObj                 : THandle;
      {$ELSE}
      FSyncObj                 : TCriticalSection;
      {$ENDIF}

      function  GetBufferBits : pointer;

      procedure CreateSyncObj;
      procedure DestroySyncObj;

    public
      constructor Create; override;
      destructor  Destroy; override;
      function    BeginBufferDraw : boolean;
      procedure   EndBufferDraw;
      function    UpdateBufferDimensions(aWidth, aHeight : integer) : boolean;
      function    BufferIsResized(aUseMutex : boolean = True) : boolean;
      procedure   BufferDraw(const aBitmap : TBitmap; const aSrcRect, aDstRect : TRect);

      property ScanlineSize              : integer                   read FScanlineSize;
      property BufferBits                : pointer                   read GetBufferBits;
      property DeviceScaleFactor         : single                    read FDeviceScaleFactor         write FDeviceScaleFactor;
  end;

implementation

uses
  uCEFMiscFunctions;

constructor TCEFBrowserBitmap.Create;
begin
  inherited Create;

  FScanlineSize      := 0;
  FDeviceScaleFactor := 1;
  CreateSyncObj;
end;

destructor TCEFBrowserBitmap.Destroy;
begin
  DestroySyncObj;

  inherited Destroy;
end;

procedure TCEFBrowserBitmap.CreateSyncObj;
begin
  {$IFDEF MSWINDOWS}
  FSyncObj := CreateMutex(nil, False, nil);
  {$ELSE}
  FSyncObj := TCriticalSection.Create;
  {$ENDIF}
end;

procedure TCEFBrowserBitmap.DestroySyncObj;
begin
  {$IFDEF MSWINDOWS}
  if (FSyncObj <> 0) then
    begin
      CloseHandle(FSyncObj);
      FSyncObj := 0;
    end;
  {$ELSE}
  if (FSyncObj <> nil) then FreeAndNil(FSyncObj);
  {$ENDIF}
end;

function TCEFBrowserBitmap.GetBufferBits : pointer;
begin
  if (Height <> 0) then
    Result := Scanline[pred(Height)]
   else
    Result := nil;
end;

function TCEFBrowserBitmap.BeginBufferDraw : boolean;
begin
  {$IFDEF MSWINDOWS}
  Result := (FSyncObj <> 0) and (WaitForSingleObject(FSyncObj, 5000) = WAIT_OBJECT_0);
  {$ELSE}
  if (FSyncObj <> nil) then
    begin
      FSyncObj.Acquire;
      Result := True;
    end
   else
    Result := False;
  {$ENDIF}
end;

procedure TCEFBrowserBitmap.EndBufferDraw;
begin
  {$IFDEF MSWINDOWS}
  if (FSyncObj <> 0) then ReleaseMutex(FSyncObj);
  {$ELSE}
  if (FSyncObj <> nil) then FSyncObj.Release;
  {$ENDIF}
end;

function TCEFBrowserBitmap.UpdateBufferDimensions(aWidth, aHeight : integer) : boolean;
begin
  Result        := False;
  FScanlineSize := aWidth * SizeOf(TRGBQuad);

  if (Width  <> aWidth)  or
     (Height <> aHeight) then
    begin
      {$IFDEF DELPHI16_UP}
      SetSize(aWidth, aHeight);
      {$ELSE}
      Width  := aWidth;
      Height := aHeight;
      {$ENDIF}
      Result := True;
    end;
end;

function TCEFBrowserBitmap.BufferIsResized(aUseMutex : boolean) : boolean;
var
  TempDevWidth, TempLogWidth, TempDevHeight, TempLogHeight : integer;
begin
  Result := False;

  if not(aUseMutex) or BeginBufferDraw then
    begin
      if (FDeviceScaleFactor = 1) then
        Result := (Width  = Width) and
                  (Height = Height)
       else
        begin
          // CEF and Chromium use 'floor' to round the float values in Device <-> Logical unit conversions
          // and Delphi uses MulDiv, which uses the bankers rounding, to resize the components in high DPI mode.
          // This is the cause of slight differences in size between the buffer and the panel in some occasions.

          TempLogWidth  := DeviceToLogical(Width,  FDeviceScaleFactor);
          TempLogHeight := DeviceToLogical(Height, FDeviceScaleFactor);

          TempDevWidth  := LogicalToDevice(TempLogWidth,  FDeviceScaleFactor);
          TempDevHeight := LogicalToDevice(TempLogHeight, FDeviceScaleFactor);

          Result := (Width  = TempDevWidth) and
                    (Height = TempDevHeight);
        end;

      if aUseMutex then EndBufferDraw;
    end;
end;

procedure TCEFBrowserBitmap.BufferDraw(const aBitmap : TBitmap; const aSrcRect, aDstRect : TRect);
begin
  if (aBitmap <> nil) then
    begin
      Canvas.Lock;
      aBitmap.Canvas.Lock;
      Canvas.CopyRect(aDstRect, aBitmap.Canvas, aSrcRect);
      aBitmap.Canvas.UnLock;
      Canvas.UnLock;
    end;
end;

end.