Skip to content

Commit

Permalink
Image.toByteData and Picture.toImage implementations (#3) (flutter#20750
Browse files Browse the repository at this point in the history
)

* `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**
  • Loading branch information
deakjahn authored Sep 2, 2020
1 parent 49d6805 commit d67bda7
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 21 deletions.
79 changes: 78 additions & 1 deletion lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

/// Bindings for CanvasKit JavaScript API.
///
/// Prefer keeping the originl CanvasKit names so it is easier to locate
/// Prefer keeping the original CanvasKit names so it is easier to locate
/// the API behind these bindings in the Skia source code.
// @dart = 2.10
Expand All @@ -31,6 +31,8 @@ class CanvasKit {
external SkBlurStyleEnum get BlurStyle;
external SkTileModeEnum get TileMode;
external SkFillTypeEnum get FillType;
external SkAlphaTypeEnum get AlphaType;
external SkColorTypeEnum get ColorType;
external SkPathOpEnum get PathOp;
external SkClipOpEnum get ClipOp;
external SkPointModeEnum get PointMode;
Expand Down Expand Up @@ -62,6 +64,13 @@ class CanvasKit {
external SkParagraphStyle ParagraphStyle(
SkParagraphStyleProperties properties);
external SkTextStyle TextStyle(SkTextStyleProperties properties);
external SkSurface MakeSurface(
int width,
int height,
);
external Uint8List getSkDataBytes(
SkData skData,
);

// Text decoration enum is embedded in the CanvasKit object itself.
external int get NoDecoration;
Expand Down Expand Up @@ -128,6 +137,7 @@ class SkSurface {
external int width();
external int height();
external void dispose();
external SkImage makeImageSnapshot();
}

@JS()
Expand Down Expand Up @@ -623,6 +633,38 @@ SkTileMode toSkTileMode(ui.TileMode mode) {
return _skTileModes[mode.index];
}

@JS()
class SkAlphaTypeEnum {
external SkAlphaType get Opaque;
external SkAlphaType get Premul;
external SkAlphaType get Unpremul;
}

@JS()
class SkAlphaType {
external int get value;
}

@JS()
class SkColorTypeEnum {
external SkColorType get Alpha_8;
external SkColorType get RGB_565;
external SkColorType get ARGB_4444;
external SkColorType get RGBA_8888;
external SkColorType get RGB_888x;
external SkColorType get BGRA_8888;
external SkColorType get RGBA_1010102;
external SkColorType get RGB_101010x;
external SkColorType get Gray_8;
external SkColorType get RGBA_F16;
external SkColorType get RGBA_F32;
}

@JS()
class SkColorType {
external int get value;
}

@JS()
@anonymous
class SkAnimatedImage {
Expand All @@ -634,6 +676,8 @@ class SkAnimatedImage {
external SkImage getCurrentFrame();
external int width();
external int height();
external Uint8List readPixels(SkImageInfo imageInfo, int srcX, int srcY);
external SkData encodeToData();

/// Deletes the C++ object.
///
Expand All @@ -652,6 +696,8 @@ class SkImage {
SkTileMode tileModeY,
Float32List? matrix, // 3x3 matrix
);
external Uint8List readPixels(SkImageInfo imageInfo, int srcX, int srcY);
external SkData encodeToData();
}

@JS()
Expand Down Expand Up @@ -1662,3 +1708,34 @@ external Object? get _finalizationRegistryConstructor;

/// Whether the current browser supports `FinalizationRegistry`.
bool browserSupportsFinalizationRegistry = _finalizationRegistryConstructor != null;

@JS()
class SkData {
external int size();
external bool isEmpty();
external Uint8List bytes();
}

@JS()
@anonymous
class SkImageInfo {
external factory SkImageInfo({
required int width,
required int height,
SkAlphaType alphaType,
SkColorSpace colorSpace,
SkColorType colorType,
});
external SkAlphaType get alphaType;
external SkColorSpace get colorSpace;
external SkColorType get colorType;
external int get height;
external bool get isEmpty;
external bool get isOpaque;
external SkRect get bounds;
external int get width;
external SkImageInfo makeAlphaType(SkAlphaType alphaType);
external SkImageInfo makeColorSpace(SkColorSpace colorSpace);
external SkImageInfo makeColorType(SkColorType colorType);
external SkImageInfo makeWH(int width, int height);
}
40 changes: 38 additions & 2 deletions lib/web_ui/lib/src/engine/canvaskit/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,25 @@ class CkAnimatedImage implements ui.Image {
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
throw 'unimplemented';
Uint8List bytes;

if (format == ui.ImageByteFormat.rawRgba) {
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width,
height: height,
);
bytes = _skAnimatedImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = _skAnimatedImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
}

final ByteData data = bytes.buffer.asByteData(0, bytes.length);
return Future<ByteData>.value(data);
}
}

Expand Down Expand Up @@ -105,7 +123,25 @@ class CkImage implements ui.Image {
@override
Future<ByteData> toByteData(
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
throw 'unimplemented';
Uint8List bytes;

if (format == ui.ImageByteFormat.rawRgba) {
final SkImageInfo imageInfo = SkImageInfo(
alphaType: canvasKit.AlphaType.Premul,
colorType: canvasKit.ColorType.RGBA_8888,
colorSpace: SkColorSpaceSRGB,
width: width,
height: height,
);
bytes = skImage.readPixels(imageInfo, 0, 0);
} else {
final SkData skData = skImage.encodeToData(); //defaults to PNG 100%
// make a copy that we can return
bytes = Uint8List.fromList(canvasKit.getSkDataBytes(skData));
}

final ByteData data = bytes.buffer.asByteData(0, bytes.length);
return Future<ByteData>.value(data);
}
}

Expand Down
10 changes: 7 additions & 3 deletions lib/web_ui/lib/src/engine/canvaskit/picture.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,13 @@ class CkPicture implements ui.Picture {
}

@override
Future<ui.Image> toImage(int width, int height) {
throw UnsupportedError(
'Picture.toImage not yet implemented for CanvasKit and HTML');
Future<ui.Image> toImage(int width, int height) async {
final SkSurface skSurface = canvasKit.MakeSurface(width, height);
final SkCanvas skCanvas = skSurface.getCanvas();
skCanvas.drawPicture(skiaObject.skiaObject);
final SkImage skImage = skSurface.makeImageSnapshot();
skSurface.dispose();
return CkImage(skImage);
}
}

Expand Down
22 changes: 7 additions & 15 deletions lib/web_ui/lib/src/engine/html_image_codec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,13 @@ class HtmlImage implements ui.Image {
final int height;

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

// Returns absolutely positioned actual image element on first call and
Expand All @@ -149,12 +149,4 @@ class HtmlImage implements ui.Image {
return imgElement;
}
}

// TODO(het): Support this for asset images and images generated from
// `Picture`s.
/// Returns an error message on failure, null on success.
String _toByteData(int format, Callback<Uint8List?> callback) {
callback(null);
return 'Image.toByteData is not supported in Flutter for Web';
}
}
25 changes: 25 additions & 0 deletions lib/web_ui/test/canvaskit/canvaskit_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1188,4 +1188,29 @@ void _canvasTests() {
20,
);
});

test('toImage.toByteData', () async {
final SkPictureRecorder otherRecorder = SkPictureRecorder();
final SkCanvas otherCanvas = otherRecorder.beginRecording(SkRect(
fLeft: 0,
fTop: 0,
fRight: 1,
fBottom: 1,
));
otherCanvas.drawRect(
SkRect(
fLeft: 0,
fTop: 0,
fRight: 1,
fBottom: 1,
),
SkPaint(),
);
final CkPicture picture = CkPicture(otherRecorder.finishRecordingAsPicture(), null);
final CkImage image = await picture.toImage(1, 1);
final ByteData rawData = await image.toByteData(format: ui.ImageByteFormat.rawRgba);
expect(rawData, isNotNull);
final ByteData pngData = await image.toByteData(format: ui.ImageByteFormat.png);
expect(pngData, isNotNull);
});
}
55 changes: 55 additions & 0 deletions lib/web_ui/test/golden_tests/engine/canvas_to_picture_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// @dart = 2.6
import 'dart:html' as html;

import 'package:ui/ui.dart';
import 'package:ui/src/engine.dart';

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';

void main() {
internalBootstrapBrowserTest(() => testMain);
}

void testMain() async {
final Rect region = Rect.fromLTWH(0, 0, 500, 500);

setUp(() async {
debugShowClipLayers = true;
SurfaceSceneBuilder.debugForgetFrameScene();
for (html.Node scene in html.document.querySelectorAll('flt-scene')) {
scene.remove();
}

await webOnlyInitializePlatform();
webOnlyFontCollection.debugRegisterTestFonts();
await webOnlyFontCollection.ensureFontsLoaded();
});

test('Convert Canvas to Picture', () async {
final SurfaceSceneBuilder builder = SurfaceSceneBuilder();
final Picture testPicture = await _drawTestPictureWithCircle(region);
builder.addPicture(Offset.zero, testPicture);

html.document.body.append(builder
.build()
.webOnlyRootElement);

//await matchGoldenFile('canvas_to_picture.png', region: region, write: true);
});
}

Picture _drawTestPictureWithCircle(Rect region) {
final EnginePictureRecorder recorder = PictureRecorder();
final RecordingCanvas canvas = recorder.beginRecording(region);
canvas.drawOval(
region,
Paint()
..style = PaintingStyle.fill
..color = Color(0xFF00FF00));
return recorder.endRecording();
}

0 comments on commit d67bda7

Please sign in to comment.