CHackbart 13 Posted September 29, 2021 (edited) 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 September 29, 2021 by CHackbart Share this post Link to post
CHackbart 13 Posted September 30, 2021 (edited) 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. Edited September 30, 2021 by CHackbart Share this post Link to post