Jump to content
CHackbart

Draw over a native element

Recommended Posts

Hi,

 

i am wondering if there is an elegant way to draw over a native control like the Media player or Webbrowser under mobile platforms resp. MacOS. In my case I have to use AVPlayerItemVideoOutput because the content is DRM protected and I can not get the surface texture in order to paint it to the screen, embedded in Firemonkey controls. My solution is to render the FireMonkey output into a bitmap and to draw it on a NSImageView Layer which is on top. This is slow as hell and I wonder how it can be achieved in a better way than using an image. 

 

Christian 

Edited by CHackbart

Share this post


Link to post

I am not quite happy with the solution, but it works on iOS and MacOS. Android is missing, during the lack of a working device here at the moment, maybe somebody would like to add the Android native Image handling into. 

 

The idea is to have a TLayout descendant which renders the Firemonkey content into a bitmap and draws this to a native Image View.

 

What I have so far is the following:

 

unit UImageLayout;

interface

uses System.Types, System.Classes, FMX.Layouts, FMX.Types
{$IFDEF MACOS}
{$IFDEF IOS}
    , iOSApi.UIKit
{$ELSE}
    , Macapi.AppKit
{$ENDIF}
{$ENDIF};

type
  TImageLayout = class(TLayout)
  private
    FActive: Boolean;
    FLastUpdate: UInt64;
{$IFDEF MACOS}
{$IFDEF IOS}
    FView: UIImageView;
{$ELSE}
    FView: NSImageView;
{$ENDIF}
{$ENDIF}
    procedure SetActive(AValue: Boolean);
  protected
    procedure AfterPaint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    property Active: Boolean read FActive write SetActive;
  end;

implementation

uses System.SysUtils, FMX.Graphics, FMX.Forms, System.IOUtils
{$IFDEF MACOS}
{$IFDEF IOS}
    , Macapi.Helpers, iOSApi.Foundation, FMX.Platform.iOS
{$ELSE}
    , Macapi.CocoaTypes, FMX.Platform.Mac, FMX.Helpers.Mac
{$ENDIF}
{$ENDIF};

{$IFDEF MACOS}
{$IFDEF IOS}

function BitmapToUIImage(const Bitmap: TBitmap): UIImage;
var
  mem: TMemoryStream;
  Data: NSData;
  TmpImage: UIImage;
  AutoreleasePool: NSAutoreleasePool;
begin
  if Bitmap <> nil then
  begin
    Data := nil;
    TmpImage := nil;
    AutoreleasePool := TNSAutoreleasePool.Create;
    try
      mem := TMemoryStream.Create;
      try
        Bitmap.SaveToStream(mem);
        Data := TNSData.Wrap(TNSData.OCClass.dataWithBytes(mem.Memory,
          mem.size));
        TmpImage := TUIImage.Alloc;
        Result := TUIImage.Wrap(TmpImage.initWithData(Data));
      finally
        mem.Free;
      end;
    finally
      AutoreleasePool.release;
    end;
  end;
end;
{$ENDIF}

function MakeNSRect(const ox, oy, sx, sy: Single): NSRect;
begin
  Result.origin.x := ox;
  Result.origin.y := oy;
  Result.size.width := sx;
  Result.size.height := sy;
end;
{$ENDIF}

function GetParentForm(Control: TFmxObject): TCommonCustomForm;
begin
  if (Control.Root <> nil) and (Control.Root.GetObject is TCommonCustomForm)
  then
    Result := TCommonCustomForm(Control.Root.GetObject)
  else
    Result := nil;
end;

{ TImageLayout }

constructor TImageLayout.Create(AOwner: TComponent);
begin
  inherited;
end;

destructor TImageLayout.Destroy;
begin
  Active := false;
  inherited;
end;

procedure TImageLayout.AfterPaint;
var
  LBitmap: TBitmap;
{$IFDEF MACOS}
  LBounds: TRectF;
  Form: TCommonCustomForm;
{$ENDIF}
begin
  inherited;
  if (not Active) then
    exit;

  if TThread.GetTickCount64 - FLastUpdate < 100 then
    exit;
  FLastUpdate := TThread.GetTickCount64;

{$IFDEF MACOS}
  if assigned(FView) then
  begin
    LBounds := GetAbsoluteRect;
    Form := GetParentForm(self);

    if assigned(Form) then
      FView.setFrame(MakeNSRect(LBounds.Left, Form.ClientHeight -
        LBounds.Bottom, LBounds.width, LBounds.height));
  end;
{$ENDIF}
  LBitmap := TBitmap.Create(Trunc(width), Trunc(height));
  try
    if LBitmap.Canvas.BeginScene then
    begin
      LBitmap.Canvas.Clear(0);
      PaintTo(LBitmap.Canvas, LBitmap.BoundsF);

      LBitmap.Canvas.EndScene;
      //LBitmap.SaveToFile(TPath.Combine(TPath.GetDocumentsPath, 'test.png'));
{$IFDEF MACOS}
{$IFDEF IOS}
      FView.setAlpha(AbsoluteOpacity);
      FView.setImage(BitmapToUIImage(LBitmap));
{$ELSE}
      FView.setAlphaValue(AbsoluteOpacity);
      FView.setImage(BitmapToMacBitmap(LBitmap));
{$ENDIF}
{$ENDIF}
    end;
  finally
    LBitmap.Free;
  end;
end;

procedure TImageLayout.SetActive(AValue: Boolean);
{$IFDEF MACOS}
var
  LBounds: TRectF;
  Form: TCommonCustomForm;
{$IFDEF IOS}
  FormView: UIView;
{$ELSE}
  FormView: NSView;
{$ENDIF}
{$ENDIF}
begin
  if AValue <> Active then
  begin
{$IFDEF MACOS}
    LBounds := AbsoluteRect;
    Form := GetParentForm(self);

    if (AValue) and (not assigned(FView)) and (assigned(Form)) then
    begin
{$IFDEF IOS}
      FView := TUIImageView.Wrap(TUIImageView.Alloc.initWithFrame
        (MakeNSRect(LBounds.Left, Form.ClientHeight - LBounds.Bottom,
        LBounds.width, LBounds.height)));

      FView.retain;

      FormView := TUIView.Wrap
        (NSObjectToID(WindowHandleToPlatform(Form.Handle).View));
      FormView.addSubview(FView);
{$ELSE}
      FView := TNSImageView.Wrap(TNSImageView.Alloc.initWithFrame
        (MakeNSRect(LBounds.Left, Form.ClientHeight - LBounds.Bottom,
        LBounds.width, LBounds.height)));
      FView.retain;
      FView.setWantsLayer(true);

      FormView := TNSView.Wrap(WindowHandleToPlatform(Form.Handle)
        .Wnd.contentView);
      FormView.addSubview(FView, NSWindowAbove, nil);
{$ENDIF}
    end;
    if assigned(FView) then
      FView.setHidden(not AValue) else
      AValue := false;
{$ENDIF}
    FActive := AValue;
  end;
end;

end.

 

Bildschirmfoto 2021-09-30 um 10.22.01.png

Edited by CHackbart

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×