Skip to content

Commit cbcad96

Browse files
authored
Merge pull request #1461 from ichan-mb/feature-drawing-panel
Add capability to capture/make screenshot content of a Panel and save it to the file
2 parents d0ac35f + 224bd8e commit cbcad96

File tree

2 files changed

+177
-0
lines changed

2 files changed

+177
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
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+
}

Source/Fuse.Drawing.Surface/DotNetSurface.uno

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ namespace Fuse.Drawing
10011001
extern(DOTNET) internal class ImageFormat
10021002
{
10031003
public static extern ImageFormat Bmp { get; }
1004+
public static extern ImageFormat Png { get; }
10041005
}
10051006

10061007
[DotNetType("System.Drawing.Drawing2D.GraphicsPath")]
@@ -1120,12 +1121,14 @@ namespace Fuse.Drawing
11201121
public extern Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0);
11211122
public extern int Width { get; }
11221123
public extern int Height { get; }
1124+
public extern void Save(string filename, ImageFormat format);
11231125
public extern void Save(Stream stream, ImageFormat format);
11241126
public extern void Dispose();
11251127
public extern PixelFormat PixelFormat { get; }
11261128
public extern BitmapData LockBits( Rectangle rect, ImageLockMode flags, PixelFormat format );
11271129
public extern void UnlockBits(BitmapData bitmapdata);
11281130
public extern void SetResolution(float xDpi, float yDpi);
1131+
public extern void RotateFlip (RotateFlipType rotateFlipType);
11291132
}
11301133

11311134
[DotNetType("System.Drawing.Imaging.BitmapData")]
@@ -1176,6 +1179,27 @@ namespace Fuse.Drawing
11761179
public extern void RotateTransform(float angle);
11771180
}
11781181

1182+
[DotNetType("System.Drawing.RotateFlipType")]
1183+
extern(DOTNET) internal enum RotateFlipType
1184+
{
1185+
Rotate180FlipNone = 2,
1186+
Rotate180FlipX = 6,
1187+
Rotate180FlipXY = 0,
1188+
Rotate180FlipY = 4,
1189+
Rotate270FlipNone = 3,
1190+
Rotate270FlipX = 7,
1191+
Rotate270FlipXY = 1,
1192+
Rotate270FlipY = 5,
1193+
Rotate90FlipNone = 1,
1194+
Rotate90FlipX = 5,
1195+
Rotate90FlipXY = 3,
1196+
Rotate90FlipY = 7,
1197+
RotateNoneFlipNone = 0,
1198+
RotateNoneFlipX = 4,
1199+
RotateNoneFlipXY = 2,
1200+
RotateNoneFlipY = 6
1201+
}
1202+
11791203
[DotNetType("System.Drawing.GraphicsUnit")]
11801204
extern(DOTNET) internal enum GraphicsUnit
11811205
{

0 commit comments

Comments
 (0)