mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Image.toByteData and Picture.toImage implementations (#3) (flutter/engine#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**
This commit is contained in:
parent
12b8249404
commit
70cb5eec9e
@ -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
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -128,6 +137,7 @@ class SkSurface {
|
||||
external int width();
|
||||
external int height();
|
||||
external void dispose();
|
||||
external SkImage makeImageSnapshot();
|
||||
}
|
||||
|
||||
@JS()
|
||||
@ -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 {
|
||||
@ -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.
|
||||
///
|
||||
@ -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()
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user