1+ using Uno ;
2+ using Uno . IO ;
3+ using Uno . Graphics ;
4+ using Uno . Compiler . ExportTargetInterop ;
5+ using Uno . Runtime . InteropServices ;
6+ using Uno . Threading ;
7+ using Fuse . Scripting ;
8+ using Fuse . Drawing ;
9+ using OpenGL ;
10+ using Fuse . Drawing . DotNetNative ;
11+
12+ namespace Fuse . Controls
13+ {
14+
15+ [ ForeignInclude ( Language . ObjC , "OpenGLES/ES2/glext.h" ) ]
16+ [ Require ( "Source.Include" , "uImage/Bitmap.h" ) ]
17+ [ Require ( "Source.Include" , "uImage/Png.h" ) ]
18+ [ Require ( "Source.Include" , "uBase/Memory.h" ) ]
19+ [ Require ( "Source.Include" , "Uno/Support.h" ) ]
20+ public partial class Panel
21+ {
22+ string _imagePath ;
23+ Action < string > _callback ;
24+
25+ static Panel ( )
26+ {
27+ ScriptClass . Register ( typeof ( Panel ) , new ScriptPromise < Panel , string , string > ( "capture" , ExecutionThread . Any , Save ) ) ;
28+ }
29+
30+ static Future < string > Save ( Context context , Panel panel , object [ ] args )
31+ {
32+ var p = new Promise < string > ( ) ;
33+ panel . Capture ( p . Resolve ) ;
34+ return p ;
35+ }
36+
37+ public void Capture ( Action < string > resolve ) {
38+ _callback = resolve ;
39+ SetupImagePath ( ) ;
40+ _captureNextFrame = true ;
41+ InvalidateVisual ( ) ;
42+ }
43+
44+ private void SetupImagePath ( )
45+ {
46+ if defined( Android )
47+ _imagePath = Path . Combine ( Directory . GetUserDirectory ( UserDirectory . Data ) , Guid . NewGuid ( ) . ToString ( ) + ".png" ) ;
48+ if defined( iOS )
49+ _imagePath = Path . Combine ( Directory . GetUserDirectory ( UserDirectory . Cache ) , Guid . NewGuid ( ) . ToString ( ) + ".png" ) ;
50+ if defined( CIL )
51+ _imagePath = Path . Combine ( Directory . GetUserDirectory ( UserDirectory . Cache ) , Guid . NewGuid ( ) . ToString ( ) + ".png" ) ;
52+ }
53+
54+ protected override void DrawWithChildren ( DrawContext dc )
55+ {
56+ if ( _captureNextFrame )
57+ {
58+ ScreenshotContent ( dc ) ;
59+ if ( _callback != null )
60+ _callback ( _imagePath ) ;
61+
62+ _captureNextFrame = false ;
63+ }
64+ base . DrawWithChildren ( dc ) ;
65+ }
66+
67+ bool _captureNextFrame ;
68+ bool TryGetCaptureRect ( out Recti rect )
69+ {
70+ var bounds = RenderBoundsWithEffects ;
71+ if ( bounds . IsInfinite || bounds . IsEmpty )
72+ {
73+ rect = new Recti ( 0 , 0 , 0 , 0 ) ;
74+ return false ;
75+ }
76+
77+ var scaled = Rect . Scale ( bounds . FlatRect , AbsoluteZoom ) ;
78+ int2 origin = ( int2 ) Math . Floor ( scaled . LeftTop ) ;
79+ int2 size = ( int2 ) Math . Ceil ( scaled . Size ) ;
80+ rect = new Recti ( origin . X , origin . Y , origin . X + size . X , origin . Y + size . Y ) ;
81+ return true ;
82+ }
83+
84+ void ScreenshotContent ( DrawContext dc )
85+ {
86+ if defined( OPENGL )
87+ {
88+ Recti rect ;
89+ if ( ! TryGetCaptureRect ( out rect ) )
90+ {
91+ rect = dc . Scissor ;
92+ }
93+
94+ var size = rect . Size ;
95+
96+ var fb = new framebuffer ( size , Format . RGBA8888 , FramebufferFlags . None ) ;
97+ var cc = new OrthographicFrustum
98+ {
99+ Origin = float2 ( rect . Minimum . X , rect . Minimum . Y ) / AbsoluteZoom ,
100+ Size = float2 ( size . X , size . Y ) / AbsoluteZoom ,
101+ LocalFromWorld = WorldTransformInverse
102+ } ;
103+
104+ dc . PushRenderTargetFrustum ( fb , cc ) ;
105+
106+ dc . Clear ( float4 ( 1 , 1 , 1 , 1 ) ) ;
107+ base . DrawWithChildren ( dc ) ;
108+
109+ var buffer = new byte [ size . X * size . Y * 4 ] ;
110+ GL . PixelStore ( GLPixelStoreParameter . PackAlignment , 1 ) ;
111+ GL . ReadPixels ( 0 , 0 , size . X , size . Y , GLPixelFormat . Rgba , GLPixelType . UnsignedByte , buffer ) ;
112+
113+ dc . PopRenderTargetFrustum ( ) ;
114+ SavePng ( buffer , size . X , size . Y , _imagePath ) ;
115+ fb . Dispose ( ) ;
116+ }
117+ }
118+
119+ extern ( DOTNET ) void SavePng ( byte [ ] data , int w , int h , string path )
120+ {
121+ // flip r and b
122+ int size = w * h * 4 ;
123+ for ( var i = 0 ; i < size; i += 4 )
124+ {
125+ var a = data [ i ] ;
126+ var b = data [ i + 2 ] ;
127+ data [ i ] = b ;
128+ data [ i + 2 ] = a ;
129+ }
130+
131+ IntPtr buffer = Fuse . Drawing . DotNetNative . Marshal . UnsafeAddrOfPinnedArrayElement ( data , 0 ) ;
132+ var image = new Bitmap ( w , h , w * 4 , PixelFormat . Format32bppPArgb , buffer ) ;
133+ image . RotateFlip ( RotateFlipType . Rotate180FlipX) ;
134+ image . Save ( path , ImageFormat . Png) ;
135+ image . Dispose ( ) ;
136+ }
137+
138+ extern ( CPlusPlus ) void SavePng ( byte [ ] data , int w , int h , string path )
139+ @{
140+ uImage ::Bitmap * bmp = new uImage ::Bitmap ( w , h , uImage ::FormatRGBA_8_8_8_8_UInt_Normalize ) ;
141+ int pitch = w * 4 ;
142+ // OpenGL stores the bottom scan-line first, PNG stores it last. Flip image while copying to compensate.
143+ for ( int y = 0 ; y < h ; ++ y ) {
144+ uint8_t * src = ( ( uint8_t * ) data ->Ptr ( ) ) + y * pitch ;
145+ uint8_t * dst = bmp ->GetScanlinePtr ( h - y - 1 ) ;
146+ memcpy ( dst , src , pitch ) ;
147+ }
148+ uCString temp ( path ) ;
149+ uImage ::Png ::Save ( temp . Ptr , bmp ) ;
150+ delete bmp ;
151+ @}
152+ }
153+ }
0 commit comments