Skip to content

Commit d67bda7

Browse files
authored
Image.toByteData and Picture.toImage implementations (#3) (flutter#20750)
* `Image.toByteData()` was not implemented in either DomCanvas or CanvasKit. This PR covers **both.** * `Picture.toImage()` was not implemented in either DomCanvas or CanvasKit. This PR covers **CanvasKit**
1 parent 49d6805 commit d67bda7

File tree

6 files changed

+210
-21
lines changed

6 files changed

+210
-21
lines changed

lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/// Bindings for CanvasKit JavaScript API.
66
///
7-
/// Prefer keeping the originl CanvasKit names so it is easier to locate
7+
/// Prefer keeping the original CanvasKit names so it is easier to locate
88
/// the API behind these bindings in the Skia source code.
99
1010
// @dart = 2.10
@@ -31,6 +31,8 @@ class CanvasKit {
3131
external SkBlurStyleEnum get BlurStyle;
3232
external SkTileModeEnum get TileMode;
3333
external SkFillTypeEnum get FillType;
34+
external SkAlphaTypeEnum get AlphaType;
35+
external SkColorTypeEnum get ColorType;
3436
external SkPathOpEnum get PathOp;
3537
external SkClipOpEnum get ClipOp;
3638
external SkPointModeEnum get PointMode;
@@ -62,6 +64,13 @@ class CanvasKit {
6264
external SkParagraphStyle ParagraphStyle(
6365
SkParagraphStyleProperties properties);
6466
external SkTextStyle TextStyle(SkTextStyleProperties properties);
67+
external SkSurface MakeSurface(
68+
int width,
69+
int height,
70+
);
71+
external Uint8List getSkDataBytes(
72+
SkData skData,
73+
);
6574

6675
// Text decoration enum is embedded in the CanvasKit object itself.
6776
external int get NoDecoration;
@@ -128,6 +137,7 @@ class SkSurface {
128137
external int width();
129138
external int height();
130139
external void dispose();
140+
external SkImage makeImageSnapshot();
131141
}
132142

133143
@JS()
@@ -623,6 +633,38 @@ SkTileMode toSkTileMode(ui.TileMode mode) {
623633
return _skTileModes[mode.index];
624634
}
625635

636+
@JS()
637+
class SkAlphaTypeEnum {
638+
external SkAlphaType get Opaque;
639+
external SkAlphaType get Premul;
640+
external SkAlphaType get Unpremul;
641+
}
642+
643+
@JS()
644+
class SkAlphaType {
645+
external int get value;
646+
}
647+
648+
@JS()
649+
class SkColorTypeEnum {
650+
external SkColorType get Alpha_8;
651+
external SkColorType get RGB_565;
652+
external SkColorType get ARGB_4444;
653+
external SkColorType get RGBA_8888;
654+
external SkColorType get RGB_888x;
655+
external SkColorType get BGRA_8888;
656+
external SkColorType get RGBA_1010102;
657+
external SkColorType get RGB_101010x;
658+
external SkColorType get Gray_8;
659+
external SkColorType get RGBA_F16;
660+
external SkColorType get RGBA_F32;
661+
}
662+
663+
@JS()
664+
class SkColorType {
665+
external int get value;
666+
}
667+
626668
@JS()
627669
@anonymous
628670
class SkAnimatedImage {
@@ -634,6 +676,8 @@ class SkAnimatedImage {
634676
external SkImage getCurrentFrame();
635677
external int width();
636678
external int height();
679+
external Uint8List readPixels(SkImageInfo imageInfo, int srcX, int srcY);
680+
external SkData encodeToData();
637681

638682
/// Deletes the C++ object.
639683
///
@@ -652,6 +696,8 @@ class SkImage {
652696
SkTileMode tileModeY,
653697
Float32List? matrix, // 3x3 matrix
654698
);
699+
external Uint8List readPixels(SkImageInfo imageInfo, int srcX, int srcY);
700+
external SkData encodeToData();
655701
}
656702

657703
@JS()
@@ -1662,3 +1708,34 @@ external Object? get _finalizationRegistryConstructor;
16621708

16631709
/// Whether the current browser supports `FinalizationRegistry`.
16641710
bool browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null;
1711+
1712+
@JS()
1713+
class SkData {
1714+
external int size();
1715+
external bool isEmpty();
1716+
external Uint8List bytes();
1717+
}
1718+
1719+
@JS()
1720+
@anonymous
1721+
class SkImageInfo {
1722+
external factory SkImageInfo({
1723+
required int width,
1724+
required int height,
1725+
SkAlphaType alphaType,
1726+
SkColorSpace colorSpace,
1727+
SkColorType colorType,
1728+
});
1729+
external SkAlphaType get alphaType;
1730+
external SkColorSpace get colorSpace;
1731+
external SkColorType get colorType;
1732+
external int get height;
1733+
external bool get isEmpty;
1734+
external bool get isOpaque;
1735+
external SkRect get bounds;
1736+
external int get width;
1737+
external SkImageInfo makeAlphaType(SkAlphaType alphaType);
1738+
external SkImageInfo makeColorSpace(SkColorSpace colorSpace);
1739+
external SkImageInfo makeColorType(SkColorType colorType);
1740+
external SkImageInfo makeWH(int width, int height);
1741+
}

lib/web_ui/lib/src/engine/canvaskit/image.dart

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,25 @@ class CkAnimatedImage implements ui.Image {
7575
@override
7676
Future<ByteData> toByteData(
7777
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
78-
throw 'unimplemented';
78+
Uint8List bytes;
79+
80+
if (format == ui.ImageByteFormat.rawRgba) {
81+
final SkImageInfo imageInfo = SkImageInfo(
82+
alphaType: canvasKit.AlphaType.Premul,
83+
colorType: canvasKit.ColorType.RGBA_8888,
84+
colorSpace: SkColorSpaceSRGB,
85+
width: width,
86+
height: height,
87+
);
88+
bytes = _skAnimatedImage.readPixels(imageInfo, 0, 0);
89+
} else {
90+
final SkData skData = _skAnimatedImage.encodeToData(); //defaults to PNG 100%
91+
// make a copy that we can return
92+
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
93+
}
94+
95+
final ByteData data = bytes.buffer.asByteData(0, bytes.length);
96+
return Future<ByteData>.value(data);
7997
}
8098
}
8199

@@ -105,7 +123,25 @@ class CkImage implements ui.Image {
105123
@override
106124
Future<ByteData> toByteData(
107125
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
108-
throw 'unimplemented';
126+
Uint8List bytes;
127+
128+
if (format == ui.ImageByteFormat.rawRgba) {
129+
final SkImageInfo imageInfo = SkImageInfo(
130+
alphaType: canvasKit.AlphaType.Premul,
131+
colorType: canvasKit.ColorType.RGBA_8888,
132+
colorSpace: SkColorSpaceSRGB,
133+
width: width,
134+
height: height,
135+
);
136+
bytes = skImage.readPixels(imageInfo, 0, 0);
137+
} else {
138+
final SkData skData = skImage.encodeToData(); //defaults to PNG 100%
139+
// make a copy that we can return
140+
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
141+
}
142+
143+
final ByteData data = bytes.buffer.asByteData(0, bytes.length);
144+
return Future<ByteData>.value(data);
109145
}
110146
}
111147

lib/web_ui/lib/src/engine/canvaskit/picture.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,13 @@ class CkPicture implements ui.Picture {
2121
}
2222

2323
@override
24-
Future<ui.Image> toImage(int width, int height) {
25-
throw UnsupportedError(
26-
'Picture.toImage not yet implemented for CanvasKit and HTML');
24+
Future<ui.Image> toImage(int width, int height) async {
25+
final SkSurface skSurface = canvasKit.MakeSurface(width, height);
26+
final SkCanvas skCanvas = skSurface.getCanvas();
27+
skCanvas.drawPicture(skiaObject.skiaObject);
28+
final SkImage skImage = skSurface.makeImageSnapshot();
29+
skSurface.dispose();
30+
return CkImage(skImage);
2731
}
2832
}
2933

lib/web_ui/lib/src/engine/html_image_codec.dart

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,13 @@ class HtmlImage implements ui.Image {
129129
final int height;
130130

131131
@override
132-
Future<ByteData?> toByteData(
133-
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
134-
return futurize((Callback<ByteData?> callback) {
135-
return _toByteData(format.index, (Uint8List? encoded) {
136-
callback(encoded?.buffer.asByteData());
137-
});
138-
});
132+
Future<ByteData?> toByteData({ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
133+
if (imgElement.src?.startsWith('data:') == true) {
134+
final data = UriData.fromUri(Uri.parse(imgElement.src!));
135+
return Future.value(data.contentAsBytes().buffer.asByteData());
136+
} else {
137+
return Future.value(null);
138+
}
139139
}
140140

141141
// Returns absolutely positioned actual image element on first call and
@@ -149,12 +149,4 @@ class HtmlImage implements ui.Image {
149149
return imgElement;
150150
}
151151
}
152-
153-
// TODO(het): Support this for asset images and images generated from
154-
// `Picture`s.
155-
/// Returns an error message on failure, null on success.
156-
String _toByteData(int format, Callback<Uint8List?> callback) {
157-
callback(null);
158-
return 'Image.toByteData is not supported in Flutter for Web';
159-
}
160152
}

lib/web_ui/test/canvaskit/canvaskit_api_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,4 +1188,29 @@ void _canvasTests() {
11881188
20,
11891189
);
11901190
});
1191+
1192+
test('toImage.toByteData', () async {
1193+
final SkPictureRecorder otherRecorder = SkPictureRecorder();
1194+
final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect(
1195+
fLeft: 0,
1196+
fTop: 0,
1197+
fRight: 1,
1198+
fBottom: 1,
1199+
));
1200+
otherCanvas.drawRect(
1201+
SkRect(
1202+
fLeft: 0,
1203+
fTop: 0,
1204+
fRight: 1,
1205+
fBottom: 1,
1206+
),
1207+
SkPaint(),
1208+
);
1209+
final CkPicture picture = CkPicture(otherRecorder.finishRecordingAsPicture(), null);
1210+
final CkImage image = await picture.toImage(1, 1);
1211+
final ByteData rawData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
1212+
expect(rawData, isNotNull);
1213+
final ByteData pngData = await image.toByteData(format: ui.ImageByteFormat.png);
1214+
expect(pngData, isNotNull);
1215+
});
11911216
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// @dart = 2.6
6+
import 'dart:html' as html;
7+
8+
import 'package:ui/ui.dart';
9+
import 'package:ui/src/engine.dart';
10+
11+
import 'package:test/bootstrap/browser.dart';
12+
import 'package:test/test.dart';
13+
14+
void main() {
15+
internalBootstrapBrowserTest(() => testMain);
16+
}
17+
18+
void testMain() async {
19+
final Rect region = Rect.fromLTWH(0, 0, 500, 500);
20+
21+
setUp(() async {
22+
debugShowClipLayers = true;
23+
SurfaceSceneBuilder.debugForgetFrameScene();
24+
for (html.Node scene in html.document.querySelectorAll('flt-scene')) {
25+
scene.remove();
26+
}
27+
28+
await webOnlyInitializePlatform();
29+
webOnlyFontCollection.debugRegisterTestFonts();
30+
await webOnlyFontCollection.ensureFontsLoaded();
31+
});
32+
33+
test('Convert Canvas to Picture', () async {
34+
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
35+
final Picture testPicture = await _drawTestPictureWithCircle(region);
36+
builder.addPicture(Offset.zero, testPicture);
37+
38+
html.document.body.append(builder
39+
.build()
40+
.webOnlyRootElement);
41+
42+
//await matchGoldenFile('canvas_to_picture.png', region: region, write: true);
43+
});
44+
}
45+
46+
Picture _drawTestPictureWithCircle(Rect region) {
47+
final EnginePictureRecorder recorder = PictureRecorder();
48+
final RecordingCanvas canvas = recorder.beginRecording(region);
49+
canvas.drawOval(
50+
region,
51+
Paint()
52+
..style = PaintingStyle.fill
53+
..color = Color(0xFF00FF00));
54+
return recorder.endRecording();
55+
}

0 commit comments

Comments
 (0)