Initial support for images in Skwasm (flutter/engine#42019)
This partially implements https://github.com/flutter/flutter/issues/126341 It does not implement image codecs, because they are going to get complicated with transferring video frames to the web worker and so on. I am going to deal with image codecs in a subsequent change.
This commit is contained in:
@@ -2007,6 +2007,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.da
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path.dart + ../../../flutter/LICENSE
|
||||
@@ -2077,6 +2078,7 @@ ORIGIN: ../../../flutter/lib/web_ui/skwasm/data.cpp + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/export.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/fonts.cpp + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/helpers.h + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/image.cpp + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/paint.cpp + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/path.cpp + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/skwasm/picture.cpp + ../../../flutter/LICENSE
|
||||
@@ -4626,6 +4628,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/picture.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_canvas.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_fonts.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_geometry.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_image.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_memory.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_paint.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/raw/raw_path.dart
|
||||
@@ -4696,6 +4699,7 @@ FILE: ../../../flutter/lib/web_ui/skwasm/data.cpp
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/export.h
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/fonts.cpp
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/helpers.h
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/image.cpp
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/paint.cpp
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/path.cpp
|
||||
FILE: ../../../flutter/lib/web_ui/skwasm/picture.cpp
|
||||
|
||||
@@ -398,6 +398,7 @@ class MaskFilter {
|
||||
String toString() => 'MaskFilter.blur($_style, ${_sigma.toStringAsFixed(1)})';
|
||||
}
|
||||
|
||||
// This needs to be kept in sync with the "_FilterQuality" enum in skwasm's canvas.cpp
|
||||
enum FilterQuality {
|
||||
none,
|
||||
low,
|
||||
@@ -438,6 +439,7 @@ enum ColorSpace {
|
||||
extendedSRGB,
|
||||
}
|
||||
|
||||
// This must be kept in sync with the `ImageByteFormat` enum in Skwasm's surface.cpp.
|
||||
enum ImageByteFormat {
|
||||
rawRgba,
|
||||
rawStraightRgba,
|
||||
@@ -445,9 +447,11 @@ enum ImageByteFormat {
|
||||
png,
|
||||
}
|
||||
|
||||
// This must be kept in sync with the `PixelFormat` enum in Skwasm's image.cpp.
|
||||
enum PixelFormat {
|
||||
rgba8888,
|
||||
bgra8888,
|
||||
rgbaFloat32,
|
||||
}
|
||||
|
||||
typedef ImageDecoderCallback = void Function(Image result);
|
||||
@@ -561,6 +565,8 @@ Future<Codec> createBmp(
|
||||
swapRedBlue = true;
|
||||
case PixelFormat.rgba8888:
|
||||
swapRedBlue = false;
|
||||
case PixelFormat.rgbaFloat32:
|
||||
throw UnimplementedError('RGB conversion from rgbaFloat32 data is not implemented');
|
||||
}
|
||||
|
||||
// See https://en.wikipedia.org/wiki/BMP_file_format for format examples.
|
||||
@@ -737,9 +743,19 @@ class Shadow {
|
||||
}
|
||||
|
||||
abstract class ImageShader implements Shader {
|
||||
factory ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4, {
|
||||
factory ImageShader(
|
||||
Image image,
|
||||
TileMode tmx,
|
||||
TileMode tmy,
|
||||
Float64List matrix4, {
|
||||
FilterQuality? filterQuality,
|
||||
}) => engine.renderer.createImageShader(image, tmx, tmy, matrix4, filterQuality);
|
||||
}) => engine.renderer.createImageShader(
|
||||
image,
|
||||
tmx,
|
||||
tmy,
|
||||
matrix4,
|
||||
filterQuality
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose();
|
||||
|
||||
@@ -62,9 +62,7 @@ void skiaDecodeImageFromPixels(
|
||||
}
|
||||
|
||||
if (targetWidth != null || targetHeight != null) {
|
||||
if (!validUpscale(allowUpscaling, targetWidth, targetHeight, width, height)) {
|
||||
domWindow.console.warn('Cannot apply targetWidth/targetHeight when allowUpscaling is false.');
|
||||
} else {
|
||||
if (validUpscale(allowUpscaling, targetWidth, targetHeight, width, height)) {
|
||||
return callback(scaleImage(skImage, targetWidth, targetHeight));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export 'skwasm_impl/picture.dart';
|
||||
export 'skwasm_impl/raw/raw_canvas.dart';
|
||||
export 'skwasm_impl/raw/raw_fonts.dart';
|
||||
export 'skwasm_impl/raw/raw_geometry.dart';
|
||||
export 'skwasm_impl/raw/raw_image.dart';
|
||||
export 'skwasm_impl/raw/raw_memory.dart';
|
||||
export 'skwasm_impl/raw/raw_paint.dart';
|
||||
export 'skwasm_impl/raw/raw_path.dart';
|
||||
|
||||
@@ -183,21 +183,51 @@ class SkwasmCanvas implements ui.Canvas {
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImage(ui.Image uiImage, ui.Offset offset, ui.Paint uiPaint) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) =>
|
||||
canvasDrawImage(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
(paint as SkwasmPaint).handle,
|
||||
paint.filterQuality.index,
|
||||
);
|
||||
|
||||
@override
|
||||
void drawImageRect(
|
||||
ui.Image uiImage, ui.Rect src, ui.Rect dst, ui.Paint uiPaint) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
ui.Image image,
|
||||
ui.Rect src,
|
||||
ui.Rect dst,
|
||||
ui.Paint paint) => withStackScope((StackScope scope) {
|
||||
final Pointer<Float> sourceRect = scope.convertRectToNative(src);
|
||||
final Pointer<Float> destRect = scope.convertRectToNative(dst);
|
||||
canvasDrawImageRect(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
sourceRect,
|
||||
destRect,
|
||||
(paint as SkwasmPaint).handle,
|
||||
paint.filterQuality.index,
|
||||
);
|
||||
});
|
||||
|
||||
@override
|
||||
void drawImageNine(
|
||||
ui.Image uiImage, ui.Rect center, ui.Rect dst, ui.Paint uiPaint) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
ui.Image image,
|
||||
ui.Rect center,
|
||||
ui.Rect dst,
|
||||
ui.Paint paint) => withStackScope((StackScope scope) {
|
||||
final Pointer<Int32> centerRect = scope.convertIRectToNative(center);
|
||||
final Pointer<Float> destRect = scope.convertRectToNative(dst);
|
||||
canvasDrawImageNine(
|
||||
_handle,
|
||||
(image as SkwasmImage).handle,
|
||||
centerRect,
|
||||
destRect,
|
||||
(paint as SkwasmPaint).handle,
|
||||
paint.filterQuality.index,
|
||||
);
|
||||
});
|
||||
|
||||
@override
|
||||
void drawPicture(ui.Picture picture) {
|
||||
|
||||
@@ -2,25 +2,52 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
class SkwasmImage implements ui.Image {
|
||||
@override
|
||||
int get width {
|
||||
throw UnimplementedError();
|
||||
SkwasmImage(this.handle);
|
||||
|
||||
factory SkwasmImage.fromPixels(
|
||||
Uint8List pixels,
|
||||
int width,
|
||||
int height,
|
||||
ui.PixelFormat format, {
|
||||
int? rowBytes,
|
||||
}) {
|
||||
final SkDataHandle dataHandle = skDataCreate(pixels.length);
|
||||
final Pointer<Uint8> dataPointer = skDataGetPointer(dataHandle).cast<Uint8>();
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
dataPointer[i] = pixels[i];
|
||||
}
|
||||
final ImageHandle imageHandle = imageCreateFromPixels(
|
||||
dataHandle,
|
||||
width,
|
||||
height,
|
||||
format.index,
|
||||
rowBytes ?? 4 * width,
|
||||
);
|
||||
skDataDispose(dataHandle);
|
||||
return SkwasmImage(imageHandle);
|
||||
}
|
||||
|
||||
final ImageHandle handle;
|
||||
bool _isDisposed = false;
|
||||
|
||||
@override
|
||||
int get height {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
int get width => imageGetWidth(handle);
|
||||
|
||||
@override
|
||||
int get height => imageGetHeight(handle);
|
||||
|
||||
@override
|
||||
Future<ByteData?> toByteData(
|
||||
{ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
|
||||
throw UnimplementedError();
|
||||
return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -28,13 +55,14 @@ class SkwasmImage implements ui.Image {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
throw UnimplementedError();
|
||||
if (!_isDisposed) {
|
||||
imageDispose(handle);
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get debugDisposed {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
bool get debugDisposed => _isDisposed;
|
||||
|
||||
@override
|
||||
SkwasmImage clone() => this;
|
||||
|
||||
@@ -76,22 +76,6 @@ class SkwasmPaint implements ui.Paint {
|
||||
@override
|
||||
set strokeMiterLimit(double limit) => paintSetMiterLimit(_handle, limit);
|
||||
|
||||
// Unimplemented stuff below
|
||||
@override
|
||||
ui.ColorFilter? colorFilter;
|
||||
|
||||
@override
|
||||
ui.FilterQuality filterQuality = ui.FilterQuality.none;
|
||||
|
||||
@override
|
||||
ui.ImageFilter? imageFilter;
|
||||
|
||||
@override
|
||||
bool invertColors = false;
|
||||
|
||||
@override
|
||||
ui.MaskFilter? maskFilter;
|
||||
|
||||
@override
|
||||
ui.Shader? get shader => _shader;
|
||||
|
||||
@@ -103,4 +87,20 @@ class SkwasmPaint implements ui.Paint {
|
||||
skwasmShader != null ? skwasmShader.handle : nullptr;
|
||||
paintSetShader(_handle, shaderHandle);
|
||||
}
|
||||
|
||||
@override
|
||||
ui.FilterQuality filterQuality = ui.FilterQuality.none;
|
||||
|
||||
// Unimplemented stuff below
|
||||
@override
|
||||
ui.ColorFilter? colorFilter;
|
||||
|
||||
@override
|
||||
ui.ImageFilter? imageFilter;
|
||||
|
||||
@override
|
||||
bool invertColors = false;
|
||||
|
||||
@override
|
||||
ui.MaskFilter? maskFilter;
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@ class SkwasmPicture implements ui.Picture {
|
||||
PictureHandle get handle => _handle;
|
||||
|
||||
@override
|
||||
Future<ui.Image> toImage(int width, int height) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
Future<ui.Image> toImage(int width, int height) async => toImageSync(width, height);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -30,10 +28,8 @@ class SkwasmPicture implements ui.Picture {
|
||||
bool debugDisposed = false;
|
||||
|
||||
@override
|
||||
ui.Image toImageSync(int width, int height) {
|
||||
// TODO(jacksongardner): implement toImageSync
|
||||
throw UnimplementedError();
|
||||
}
|
||||
ui.Image toImageSync(int width, int height) =>
|
||||
SkwasmImage(imageCreateFromPicture(handle, width, height));
|
||||
|
||||
ui.Rect get cullRect {
|
||||
return withStackScope((StackScope s) {
|
||||
|
||||
@@ -9,9 +9,9 @@ import 'dart:ffi';
|
||||
|
||||
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
|
||||
final class CanvasWrapper extends Opaque {}
|
||||
final class RawCanvas extends Opaque {}
|
||||
|
||||
typedef CanvasHandle = Pointer<CanvasWrapper>;
|
||||
typedef CanvasHandle = Pointer<RawCanvas>;
|
||||
|
||||
@Native<Void Function(CanvasHandle)>(symbol: 'canvas_destroy', isLeaf: true)
|
||||
external void canvasDestroy(CanvasHandle canvas);
|
||||
@@ -126,6 +126,57 @@ external void canvasDrawPath(
|
||||
symbol: 'canvas_drawPicture', isLeaf: true)
|
||||
external void canvasDrawPicture(CanvasHandle canvas, PictureHandle picture);
|
||||
|
||||
@Native<Void Function(
|
||||
CanvasHandle,
|
||||
ImageHandle,
|
||||
Float,
|
||||
Float,
|
||||
PaintHandle,
|
||||
Int
|
||||
)>(symbol: 'canvas_drawImage', isLeaf: true)
|
||||
external void canvasDrawImage(
|
||||
CanvasHandle handle,
|
||||
ImageHandle image,
|
||||
double offsetX,
|
||||
double offsetY,
|
||||
PaintHandle paint,
|
||||
int filterQuality,
|
||||
);
|
||||
|
||||
@Native<Void Function(
|
||||
CanvasHandle,
|
||||
ImageHandle,
|
||||
Pointer<Float>,
|
||||
Pointer<Float>,
|
||||
PaintHandle,
|
||||
Int,
|
||||
)>(symbol: 'canvas_drawImageRect', isLeaf: true)
|
||||
external void canvasDrawImageRect(
|
||||
CanvasHandle handle,
|
||||
ImageHandle image,
|
||||
Pointer<Float> sourceRect,
|
||||
Pointer<Float> destRect,
|
||||
PaintHandle paint,
|
||||
int filterQuality,
|
||||
);
|
||||
|
||||
@Native<Void Function(
|
||||
CanvasHandle,
|
||||
ImageHandle,
|
||||
Pointer<Int32>,
|
||||
Pointer<Float>,
|
||||
PaintHandle,
|
||||
Int,
|
||||
)>(symbol: 'canvas_drawImageNine', isLeaf: true)
|
||||
external void canvasDrawImageNine(
|
||||
CanvasHandle handle,
|
||||
ImageHandle image,
|
||||
Pointer<Int32> centerRect,
|
||||
Pointer<Float> destRect,
|
||||
PaintHandle paint,
|
||||
int filterQuality,
|
||||
);
|
||||
|
||||
@Native<Void Function(CanvasHandle, PathHandle, Float, Float, Int32, Bool)>(
|
||||
symbol: 'canvas_drawShadow', isLeaf: true)
|
||||
external void canvasDrawShadow(
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
@DefaultAsset('skwasm')
|
||||
library skwasm_impl;
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
|
||||
final class RawImage extends Opaque {}
|
||||
typedef ImageHandle = Pointer<RawImage>;
|
||||
|
||||
@Native<ImageHandle Function(
|
||||
PictureHandle,
|
||||
Int32,
|
||||
Int32,
|
||||
)>(symbol: 'image_createFromPicture', isLeaf: true)
|
||||
external ImageHandle imageCreateFromPicture(
|
||||
PictureHandle handle,
|
||||
int width,
|
||||
int height,
|
||||
);
|
||||
|
||||
@Native<ImageHandle Function(
|
||||
SkDataHandle,
|
||||
Int,
|
||||
Int,
|
||||
Int,
|
||||
Size
|
||||
)>(symbol: 'image_createFromPixels', isLeaf: true)
|
||||
external ImageHandle imageCreateFromPixels(
|
||||
SkDataHandle pixelData,
|
||||
int width,
|
||||
int height,
|
||||
int pixelFormat,
|
||||
int rowByteCount,
|
||||
);
|
||||
|
||||
@Native<Void Function(ImageHandle)>(symbol: 'image_dispose', isLeaf: true)
|
||||
external void imageDispose(ImageHandle handle);
|
||||
|
||||
@Native<Int Function(ImageHandle)>(symbol: 'image_getWidth', isLeaf: true)
|
||||
external int imageGetWidth(ImageHandle handle);
|
||||
|
||||
@Native<Int Function(ImageHandle)>(symbol: 'image_getHeight', isLeaf: true)
|
||||
external int imageGetHeight(ImageHandle handle);
|
||||
@@ -121,3 +121,18 @@ external ShaderHandle shaderCreateRuntimeEffectShader(
|
||||
Pointer<ShaderHandle> childShaders,
|
||||
int childCount
|
||||
);
|
||||
|
||||
@Native<ShaderHandle Function(
|
||||
ImageHandle,
|
||||
Int,
|
||||
Int,
|
||||
Int,
|
||||
RawMatrix33,
|
||||
)>(symbol: 'shader_createFromImage', isLeaf: true)
|
||||
external ShaderHandle shaderCreateFromImage(
|
||||
ImageHandle handle,
|
||||
int tileModeX,
|
||||
int tileModeY,
|
||||
int quality,
|
||||
RawMatrix33 localMatrix,
|
||||
);
|
||||
|
||||
@@ -16,5 +16,11 @@ external SkDataHandle skDataCreate(int size);
|
||||
@Native<Pointer<Void> Function(SkDataHandle)>(symbol: 'skData_getPointer', isLeaf: true)
|
||||
external Pointer<Void> skDataGetPointer(SkDataHandle handle);
|
||||
|
||||
@Native<Pointer<Void> Function(SkDataHandle)>(symbol: 'skData_getConstPointer', isLeaf: true)
|
||||
external Pointer<Void> skDataGetConstPointer(SkDataHandle handle);
|
||||
|
||||
@Native<Size Function(SkDataHandle)>(symbol: 'skData_getSize', isLeaf: true)
|
||||
external int skDataGetSize(SkDataHandle handle);
|
||||
|
||||
@Native<Void Function(SkDataHandle)>(symbol: 'skData_dispose', isLeaf: true)
|
||||
external void skDataDispose(SkDataHandle handle);
|
||||
|
||||
@@ -22,9 +22,9 @@ external SurfaceHandle surfaceCreateFromCanvas(
|
||||
);
|
||||
|
||||
@Native<Void Function(SurfaceHandle, OnRenderCallbackHandle)>(
|
||||
symbol: 'surface_setOnRenderCallback',
|
||||
symbol: 'surface_setCallbackHandler',
|
||||
isLeaf: true)
|
||||
external void surfaceSetOnRenderCallback(
|
||||
external void surfaceSetCallbackHandler(
|
||||
SurfaceHandle surface,
|
||||
OnRenderCallbackHandle callback,
|
||||
);
|
||||
@@ -47,3 +47,14 @@ external void surfaceSetCanvasSize(
|
||||
symbol: 'surface_renderPicture',
|
||||
isLeaf: true)
|
||||
external int surfaceRenderPicture(SurfaceHandle surface, PictureHandle picture);
|
||||
|
||||
@Native<Int32 Function(
|
||||
SurfaceHandle,
|
||||
ImageHandle,
|
||||
Int
|
||||
)>(symbol: 'surface_rasterizeImage', isLeaf: true)
|
||||
external int surfaceRasterizeImage(
|
||||
SurfaceHandle handle,
|
||||
ImageHandle image,
|
||||
int format,
|
||||
);
|
||||
|
||||
@@ -75,9 +75,19 @@ class SkwasmRenderer implements Renderer {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.ImageShader createImageShader(ui.Image image, ui.TileMode tmx, ui.TileMode tmy, Float64List matrix4, ui.FilterQuality? filterQuality) {
|
||||
throw UnimplementedError('createImageShader not yet implemented');
|
||||
}
|
||||
ui.ImageShader createImageShader(
|
||||
ui.Image image,
|
||||
ui.TileMode tmx,
|
||||
ui.TileMode tmy,
|
||||
Float64List matrix4,
|
||||
ui.FilterQuality? filterQuality
|
||||
) => SkwasmImageShader.imageShader(
|
||||
image as SkwasmImage,
|
||||
tmx,
|
||||
tmy,
|
||||
matrix4,
|
||||
filterQuality
|
||||
);
|
||||
|
||||
@override
|
||||
ui.Gradient createLinearGradient(
|
||||
@@ -285,9 +295,81 @@ class SkwasmRenderer implements Renderer {
|
||||
indices: indices
|
||||
);
|
||||
|
||||
ui.Size? _scaledSize(
|
||||
int width,
|
||||
int height,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
) {
|
||||
if (targetWidth == width && targetHeight == height) {
|
||||
// Not scaled
|
||||
return null;
|
||||
}
|
||||
if (targetWidth == null) {
|
||||
if (targetHeight == null || targetHeight == height) {
|
||||
// Not scaled.
|
||||
return null;
|
||||
}
|
||||
targetWidth = (width * targetHeight / height).round();
|
||||
} else if (targetHeight == null) {
|
||||
if (targetWidth == targetWidth) {
|
||||
// Not scaled.
|
||||
return null;
|
||||
}
|
||||
targetHeight = (height * targetWidth / width).round();
|
||||
}
|
||||
return ui.Size(targetWidth.toDouble(), targetHeight.toDouble());
|
||||
}
|
||||
|
||||
@override
|
||||
void decodeImageFromPixels(Uint8List pixels, int width, int height, ui.PixelFormat format, ui.ImageDecoderCallback callback, {int? rowBytes, int? targetWidth, int? targetHeight, bool allowUpscaling = true}) {
|
||||
throw UnimplementedError('decodeImageFromPixels not yet implemented');
|
||||
void decodeImageFromPixels(
|
||||
Uint8List pixels,
|
||||
int width,
|
||||
int height,
|
||||
ui.PixelFormat format,
|
||||
ui.ImageDecoderCallback callback, {
|
||||
int? rowBytes,
|
||||
int? targetWidth,
|
||||
int? targetHeight,
|
||||
bool allowUpscaling = true
|
||||
}) {
|
||||
ui.Size? scaledSize = _scaledSize(
|
||||
width,
|
||||
height,
|
||||
targetWidth,
|
||||
targetHeight
|
||||
);
|
||||
if (!allowUpscaling && scaledSize != null &&
|
||||
(scaledSize.width > width || scaledSize.height > height)) {
|
||||
scaledSize = null;
|
||||
}
|
||||
final SkwasmImage pixelImage = SkwasmImage.fromPixels(
|
||||
pixels,
|
||||
width,
|
||||
height,
|
||||
format
|
||||
);
|
||||
if (scaledSize == null) {
|
||||
callback(pixelImage);
|
||||
return;
|
||||
}
|
||||
|
||||
final ui.Rect outputRect = ui.Rect.fromLTWH(0, 0, scaledSize.width, scaledSize.height);
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, outputRect);
|
||||
|
||||
canvas.drawImageRect(
|
||||
pixelImage,
|
||||
ui.Rect.fromLTWH(0, 0, width.toDouble(), width.toDouble()),
|
||||
outputRect,
|
||||
ui.Paint(),
|
||||
);
|
||||
final ui.Image finalImage = recorder.endRecording().toImageSync(
|
||||
scaledSize.width.round(),
|
||||
scaledSize.height.round()
|
||||
);
|
||||
pixelImage.dispose();
|
||||
callback(finalImage);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -187,12 +187,10 @@ class SkwasmSceneBuilder implements ui.SceneBuilder {
|
||||
|
||||
@override
|
||||
void setCheckerboardOffscreenLayers(bool checkerboard) {
|
||||
// TODO(jacksongardner): implement setCheckerboardOffscreenLayers
|
||||
}
|
||||
|
||||
@override
|
||||
void setCheckerboardRasterCacheImages(bool checkerboard) {
|
||||
// TODO(jacksongardner): implement setCheckerboardRasterCacheImages
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -205,12 +203,10 @@ class SkwasmSceneBuilder implements ui.SceneBuilder {
|
||||
double insetLeft,
|
||||
bool focusable
|
||||
) {
|
||||
// TODO(jacksongardner): implement setProperties
|
||||
}
|
||||
|
||||
@override
|
||||
void setRasterizerTracingThreshold(int frameInterval) {
|
||||
// TODO(jacksongardner): implement setRasterizerTracingThreshold
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -155,8 +155,55 @@ class SkwasmGradient extends SkwasmShader implements ui.Gradient {
|
||||
}
|
||||
}
|
||||
|
||||
class SkwasmImageShader extends SkwasmShader implements ui.ImageShader {
|
||||
SkwasmImageShader._(this.handle);
|
||||
|
||||
factory SkwasmImageShader.imageShader(
|
||||
SkwasmImage image,
|
||||
ui.TileMode tmx,
|
||||
ui.TileMode tmy,
|
||||
Float64List? matrix4,
|
||||
ui.FilterQuality? filterQuality,
|
||||
) {
|
||||
if (matrix4 != null) {
|
||||
return withStackScope((StackScope scope) {
|
||||
final RawMatrix33 localMatrix = scope.convertMatrix4toSkMatrix(matrix4);
|
||||
return SkwasmImageShader._(shaderCreateFromImage(
|
||||
image.handle,
|
||||
tmx.index,
|
||||
tmy.index,
|
||||
(filterQuality ?? ui.FilterQuality.medium).index,
|
||||
localMatrix,
|
||||
));
|
||||
});
|
||||
} else {
|
||||
return SkwasmImageShader._(shaderCreateFromImage(
|
||||
image.handle,
|
||||
tmx.index,
|
||||
tmy.index,
|
||||
(filterQuality ?? ui.FilterQuality.medium).index,
|
||||
nullptr,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
ShaderHandle handle;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
class SkwasmFragmentProgram implements ui.FragmentProgram {
|
||||
SkwasmFragmentProgram._(this.name, this.handle);
|
||||
SkwasmFragmentProgram._(
|
||||
this.name,
|
||||
this.handle,
|
||||
this.floatUniformCount,
|
||||
this.childShaderCount,
|
||||
);
|
||||
factory SkwasmFragmentProgram.fromBytes(String name, Uint8List bytes) {
|
||||
final ShaderData shaderData = ShaderData.fromBytes(bytes);
|
||||
|
||||
@@ -171,50 +218,58 @@ class SkwasmFragmentProgram implements ui.FragmentProgram {
|
||||
}
|
||||
final RuntimeEffectHandle handle = runtimeEffectCreate(sourceString);
|
||||
skStringFree(sourceString);
|
||||
return SkwasmFragmentProgram._(name, handle);
|
||||
return SkwasmFragmentProgram._(
|
||||
name,
|
||||
handle,
|
||||
shaderData.floatCount,
|
||||
shaderData.textureCount
|
||||
);
|
||||
}
|
||||
|
||||
RuntimeEffectHandle handle;
|
||||
String name;
|
||||
final RuntimeEffectHandle handle;
|
||||
final String name;
|
||||
final int floatUniformCount;
|
||||
final int childShaderCount;
|
||||
bool _isDisposed = false;
|
||||
|
||||
@override
|
||||
ui.FragmentShader fragmentShader() =>
|
||||
SkwasmFragmentShader(this);
|
||||
ui.FragmentShader fragmentShader() => SkwasmFragmentShader(this);
|
||||
|
||||
int get uniformSize => runtimeEffectGetUniformSize(handle);
|
||||
|
||||
void dispose() {
|
||||
runtimeEffectDispose(handle);
|
||||
if (!_isDisposed) {
|
||||
runtimeEffectDispose(handle);
|
||||
_isDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
|
||||
SkwasmFragmentShader(
|
||||
SkwasmFragmentProgram program, {
|
||||
List<SkwasmShader>? childShaders,
|
||||
}) : _program = program,
|
||||
SkwasmFragmentProgram program
|
||||
) : _program = program,
|
||||
_uniformData = skDataCreate(program.uniformSize),
|
||||
_childShaders = childShaders;
|
||||
_floatUniformCount = program.floatUniformCount,
|
||||
_childShaders = List<SkwasmShader?>.filled(program.childShaderCount, null);
|
||||
|
||||
@override
|
||||
ShaderHandle get handle {
|
||||
if (_handle == nullptr) {
|
||||
_handle = withStackScope((StackScope s) {
|
||||
Pointer<ShaderHandle> childShaders = nullptr;
|
||||
final int childCount = _childShaders != null ? _childShaders!.length : 0;
|
||||
if (childCount != 0) {
|
||||
childShaders = s.allocPointerArray(childCount)
|
||||
if (_childShaders.isNotEmpty) {
|
||||
childShaders = s.allocPointerArray(_childShaders.length)
|
||||
.cast<ShaderHandle>();
|
||||
final List<SkwasmShader> shaders = _childShaders!;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
childShaders[i] = shaders[i].handle;
|
||||
for (int i = 0; i < _childShaders.length; i++) {
|
||||
childShaders[i] = _childShaders[i]!.handle;
|
||||
}
|
||||
}
|
||||
return shaderCreateRuntimeEffectShader(
|
||||
_program.handle,
|
||||
_uniformData,
|
||||
childShaders,
|
||||
childCount,
|
||||
_childShaders.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -224,7 +279,8 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
|
||||
ShaderHandle _handle = nullptr;
|
||||
final SkwasmFragmentProgram _program;
|
||||
SkDataHandle _uniformData;
|
||||
final List<SkwasmShader>? _childShaders;
|
||||
final int _floatUniformCount;
|
||||
final List<SkwasmShader?> _childShaders;
|
||||
|
||||
@override
|
||||
void setFloat(int index, double value) {
|
||||
@@ -240,7 +296,20 @@ class SkwasmFragmentShader extends SkwasmShader implements ui.FragmentShader {
|
||||
|
||||
@override
|
||||
void setImageSampler(int index, ui.Image image) {
|
||||
// TODO(jacksongardner): implement this when images are implemented
|
||||
final SkwasmImageShader shader = SkwasmImageShader.imageShader(
|
||||
image as SkwasmImage,
|
||||
ui.TileMode.clamp,
|
||||
ui.TileMode.clamp,
|
||||
null,
|
||||
ui.FilterQuality.none,
|
||||
);
|
||||
final SkwasmShader? oldShader = _childShaders[index];
|
||||
_childShaders[index] = shader;
|
||||
oldShader?.dispose();
|
||||
|
||||
final Pointer<Float> dataPointer = skDataGetPointer(_uniformData).cast<Float>();
|
||||
dataPointer[_floatUniformCount + index * 2] = image.width.toDouble();
|
||||
dataPointer[_floatUniformCount + index * 2 + 1] = image.height.toDouble();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ffi';
|
||||
import 'dart:js_interop';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
class SkwasmSurface {
|
||||
factory SkwasmSurface(String canvasQuerySelector) {
|
||||
@@ -22,33 +24,55 @@ class SkwasmSurface {
|
||||
SkwasmSurface._fromHandle(this._handle);
|
||||
final SurfaceHandle _handle;
|
||||
OnRenderCallbackHandle _callbackHandle = nullptr;
|
||||
final Map<int, Completer<void>> _pendingRenders = <int, Completer<void>>{};
|
||||
final Map<int, Completer<int>> _pendingCallbacks = <int, Completer<int>>{};
|
||||
|
||||
void _initialize() {
|
||||
_callbackHandle =
|
||||
OnRenderCallbackHandle.fromAddress(
|
||||
skwasmInstance.addFunction(
|
||||
_onRender.toJS,
|
||||
'vi'.toJS
|
||||
_callbackHandler.toJS,
|
||||
'vii'.toJS
|
||||
).toDart.toInt()
|
||||
);
|
||||
surfaceSetOnRenderCallback(_handle, _callbackHandle);
|
||||
surfaceSetCallbackHandler(_handle, _callbackHandle);
|
||||
}
|
||||
|
||||
void setSize(int width, int height) =>
|
||||
surfaceSetCanvasSize(_handle, width, height);
|
||||
|
||||
Future<void> renderPicture(SkwasmPicture picture) {
|
||||
final int renderId = surfaceRenderPicture(_handle, picture.handle);
|
||||
final Completer<void> completer = Completer<void>();
|
||||
_pendingRenders[renderId] = completer;
|
||||
final int callbackId = surfaceRenderPicture(_handle, picture.handle);
|
||||
return _registerCallback(callbackId);
|
||||
}
|
||||
|
||||
Future<ByteData> rasterizeImage(SkwasmImage image, ui.ImageByteFormat format) async {
|
||||
final int callbackId = surfaceRasterizeImage(
|
||||
_handle,
|
||||
image.handle,
|
||||
format.index,
|
||||
);
|
||||
final int context = await _registerCallback(callbackId);
|
||||
final SkDataHandle dataHandle = SkDataHandle.fromAddress(context);
|
||||
final int byteCount = skDataGetSize(dataHandle);
|
||||
final Pointer<Uint8> dataPointer = skDataGetConstPointer(dataHandle).cast<Uint8>();
|
||||
final Uint8List output = Uint8List(byteCount);
|
||||
for (int i = 0; i < byteCount; i++) {
|
||||
output[i] = dataPointer[i];
|
||||
}
|
||||
skDataDispose(dataHandle);
|
||||
return ByteData.sublistView(output);
|
||||
}
|
||||
|
||||
Future<int> _registerCallback(int callbackId) {
|
||||
final Completer<int> completer = Completer<int>();
|
||||
_pendingCallbacks[callbackId] = completer;
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
void _onRender(JSNumber jsRenderId) {
|
||||
final int renderId = jsRenderId.toDart.toInt();
|
||||
final Completer<void> completer = _pendingRenders.remove(renderId)!;
|
||||
completer.complete();
|
||||
void _callbackHandler(JSNumber jsCallbackId, JSNumber jsPointer) {
|
||||
final int callbackId = jsCallbackId.toDart.toInt();
|
||||
final Completer<int> completer = _pendingCallbacks.remove(callbackId)!;
|
||||
completer.complete(jsPointer.toDart.toInt());
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
|
||||
@@ -12,6 +12,7 @@ wasm_lib("skwasm") {
|
||||
"export.h",
|
||||
"fonts.cpp",
|
||||
"helpers.h",
|
||||
"image.cpp",
|
||||
"paint.cpp",
|
||||
"path.cpp",
|
||||
"picture.cpp",
|
||||
|
||||
@@ -29,167 +29,144 @@ constexpr SkScalar kShadowLightXOffset = 0;
|
||||
constexpr SkScalar kShadowLightYOffset = -450;
|
||||
} // namespace
|
||||
|
||||
SKWASM_EXPORT void canvas_destroy(CanvasWrapper* wrapper) {
|
||||
delete wrapper;
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_saveLayer(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_saveLayer(SkCanvas* canvas,
|
||||
SkRect* rect,
|
||||
SkPaint* paint) {
|
||||
wrapper->canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, 0));
|
||||
canvas->saveLayer(SkCanvas::SaveLayerRec(rect, paint, 0));
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_save(CanvasWrapper* wrapper) {
|
||||
wrapper->canvas->save();
|
||||
SKWASM_EXPORT void canvas_save(SkCanvas* canvas) {
|
||||
canvas->save();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_restore(CanvasWrapper* wrapper) {
|
||||
wrapper->canvas->restore();
|
||||
SKWASM_EXPORT void canvas_restore(SkCanvas* canvas) {
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_restoreToCount(CanvasWrapper* wrapper, int count) {
|
||||
wrapper->canvas->restoreToCount(count);
|
||||
SKWASM_EXPORT void canvas_restoreToCount(SkCanvas* canvas, int count) {
|
||||
canvas->restoreToCount(count);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT int canvas_getSaveCount(CanvasWrapper* wrapper) {
|
||||
return wrapper->canvas->getSaveCount();
|
||||
SKWASM_EXPORT int canvas_getSaveCount(SkCanvas* canvas) {
|
||||
return canvas->getSaveCount();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_translate(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_translate(SkCanvas* canvas,
|
||||
SkScalar dx,
|
||||
SkScalar dy) {
|
||||
wrapper->canvas->translate(dx, dy);
|
||||
canvas->translate(dx, dy);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_scale(CanvasWrapper* wrapper,
|
||||
SkScalar sx,
|
||||
SkScalar sy) {
|
||||
wrapper->canvas->scale(sx, sy);
|
||||
SKWASM_EXPORT void canvas_scale(SkCanvas* canvas, SkScalar sx, SkScalar sy) {
|
||||
canvas->scale(sx, sy);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_rotate(CanvasWrapper* wrapper, SkScalar degrees) {
|
||||
wrapper->canvas->rotate(degrees);
|
||||
SKWASM_EXPORT void canvas_rotate(SkCanvas* canvas, SkScalar degrees) {
|
||||
canvas->rotate(degrees);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_skew(CanvasWrapper* wrapper,
|
||||
SkScalar sx,
|
||||
SkScalar sy) {
|
||||
wrapper->canvas->skew(sx, sy);
|
||||
SKWASM_EXPORT void canvas_skew(SkCanvas* canvas, SkScalar sx, SkScalar sy) {
|
||||
canvas->skew(sx, sy);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_transform(CanvasWrapper* wrapper,
|
||||
const SkM44* matrix44) {
|
||||
wrapper->canvas->concat(*matrix44);
|
||||
SKWASM_EXPORT void canvas_transform(SkCanvas* canvas, const SkM44* matrix44) {
|
||||
canvas->concat(*matrix44);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_clipRect(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_clipRect(SkCanvas* canvas,
|
||||
const SkRect* rect,
|
||||
SkClipOp op,
|
||||
bool antialias) {
|
||||
wrapper->canvas->clipRect(*rect, op, antialias);
|
||||
canvas->clipRect(*rect, op, antialias);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_clipRRect(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_clipRRect(SkCanvas* canvas,
|
||||
const SkScalar* rrectValues,
|
||||
bool antialias) {
|
||||
wrapper->canvas->clipRRect(createRRect(rrectValues), antialias);
|
||||
canvas->clipRRect(createRRect(rrectValues), antialias);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_clipPath(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_clipPath(SkCanvas* canvas,
|
||||
SkPath* path,
|
||||
bool antialias) {
|
||||
wrapper->canvas->clipPath(*path, antialias);
|
||||
canvas->clipPath(*path, antialias);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawColor(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawColor(SkCanvas* canvas,
|
||||
SkColor color,
|
||||
SkBlendMode blendMode) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawColor(color, blendMode);
|
||||
canvas->drawColor(color, blendMode);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawLine(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawLine(SkCanvas* canvas,
|
||||
SkScalar x1,
|
||||
SkScalar y1,
|
||||
SkScalar x2,
|
||||
SkScalar y2,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawLine(x1, y1, x2, y2, *paint);
|
||||
canvas->drawLine(x1, y1, x2, y2, *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawPaint(CanvasWrapper* wrapper, SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawPaint(*paint);
|
||||
SKWASM_EXPORT void canvas_drawPaint(SkCanvas* canvas, SkPaint* paint) {
|
||||
canvas->drawPaint(*paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawRect(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawRect(SkCanvas* canvas,
|
||||
SkRect* rect,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawRect(*rect, *paint);
|
||||
canvas->drawRect(*rect, *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawRRect(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawRRect(SkCanvas* canvas,
|
||||
const SkScalar* rrectValues,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawRRect(createRRect(rrectValues), *paint);
|
||||
canvas->drawRRect(createRRect(rrectValues), *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawDRRect(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawDRRect(SkCanvas* canvas,
|
||||
const SkScalar* outerRrectValues,
|
||||
const SkScalar* innerRrectValues,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawDRRect(createRRect(outerRrectValues),
|
||||
createRRect(innerRrectValues), *paint);
|
||||
canvas->drawDRRect(createRRect(outerRrectValues),
|
||||
createRRect(innerRrectValues), *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawOval(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawOval(SkCanvas* canvas,
|
||||
const SkRect* rect,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawOval(*rect, *paint);
|
||||
canvas->drawOval(*rect, *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawCircle(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawCircle(SkCanvas* canvas,
|
||||
SkScalar x,
|
||||
SkScalar y,
|
||||
SkScalar radius,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
|
||||
wrapper->canvas->drawCircle(x, y, radius, *paint);
|
||||
canvas->drawCircle(x, y, radius, *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawArc(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawArc(SkCanvas* canvas,
|
||||
const SkRect* rect,
|
||||
SkScalar startAngleDegrees,
|
||||
SkScalar sweepAngleDegrees,
|
||||
bool useCenter,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
wrapper->canvas->drawArc(*rect, startAngleDegrees, sweepAngleDegrees,
|
||||
useCenter, *paint);
|
||||
canvas->drawArc(*rect, startAngleDegrees, sweepAngleDegrees, useCenter,
|
||||
*paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawPath(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawPath(SkCanvas* canvas,
|
||||
SkPath* path,
|
||||
SkPaint* paint) {
|
||||
makeCurrent(wrapper->context);
|
||||
|
||||
wrapper->canvas->drawPath(*path, *paint);
|
||||
canvas->drawPath(*path, *paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawShadow(SkCanvas* canvas,
|
||||
SkPath* path,
|
||||
SkScalar elevation,
|
||||
SkScalar devicePixelRatio,
|
||||
SkColor color,
|
||||
bool transparentOccluder) {
|
||||
makeCurrent(wrapper->context);
|
||||
|
||||
SkColor inAmbient =
|
||||
SkColorSetA(color, kShadowAmbientAlpha * SkColorGetA(color));
|
||||
SkColor inSpot = SkColorSetA(color, kShadowSpotAlpha * SkColorGetA(color));
|
||||
@@ -201,38 +178,64 @@ SKWASM_EXPORT void canvas_drawShadow(CanvasWrapper* wrapper,
|
||||
: SkShadowFlags::kNone_ShadowFlag;
|
||||
flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;
|
||||
SkShadowUtils::DrawShadow(
|
||||
wrapper->canvas, *path,
|
||||
SkPoint3::Make(0.0f, 0.0f, elevation * devicePixelRatio),
|
||||
canvas, *path, SkPoint3::Make(0.0f, 0.0f, elevation * devicePixelRatio),
|
||||
SkPoint3::Make(kShadowLightXOffset, kShadowLightYOffset,
|
||||
kShadowLightHeight * devicePixelRatio),
|
||||
devicePixelRatio * kShadowLightRadius, outAmbient, outSpot, flags);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawParagraph(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawParagraph(SkCanvas* canvas,
|
||||
Paragraph* paragraph,
|
||||
SkScalar x,
|
||||
SkScalar y) {
|
||||
paragraph->paint(wrapper->canvas, x, y);
|
||||
paragraph->paint(canvas, x, y);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawPicture(CanvasWrapper* wrapper,
|
||||
SkPicture* picture) {
|
||||
makeCurrent(wrapper->context);
|
||||
|
||||
wrapper->canvas->drawPicture(picture);
|
||||
SKWASM_EXPORT void canvas_drawPicture(SkCanvas* canvas, SkPicture* picture) {
|
||||
canvas->drawPicture(picture);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_getTransform(CanvasWrapper* wrapper,
|
||||
SkM44* outTransform) {
|
||||
*outTransform = wrapper->canvas->getLocalToDevice();
|
||||
SKWASM_EXPORT void canvas_drawImage(SkCanvas* canvas,
|
||||
SkImage* image,
|
||||
SkScalar offsetX,
|
||||
SkScalar offsetY,
|
||||
SkPaint* paint,
|
||||
FilterQuality quality) {
|
||||
canvas->drawImage(image, offsetX, offsetY, samplingOptionsForQuality(quality),
|
||||
paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_getLocalClipBounds(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_drawImageRect(SkCanvas* canvas,
|
||||
SkImage* image,
|
||||
SkRect* sourceRect,
|
||||
SkRect* destRect,
|
||||
SkPaint* paint,
|
||||
FilterQuality quality) {
|
||||
canvas->drawImageRect(image, *sourceRect, *destRect,
|
||||
samplingOptionsForQuality(quality), paint,
|
||||
SkCanvas::kStrict_SrcRectConstraint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_drawImageNine(SkCanvas* canvas,
|
||||
SkImage* image,
|
||||
SkIRect* centerRect,
|
||||
SkRect* destinationRect,
|
||||
SkPaint* paint,
|
||||
FilterQuality quality) {
|
||||
canvas->drawImageNine(image, *centerRect, *destinationRect,
|
||||
filterModeForQuality(quality), paint);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_getTransform(SkCanvas* canvas, SkM44* outTransform) {
|
||||
*outTransform = canvas->getLocalToDevice();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_getLocalClipBounds(SkCanvas* canvas,
|
||||
SkRect* outRect) {
|
||||
*outRect = wrapper->canvas->getLocalClipBounds();
|
||||
*outRect = canvas->getLocalClipBounds();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void canvas_getDeviceClipBounds(CanvasWrapper* wrapper,
|
||||
SKWASM_EXPORT void canvas_getDeviceClipBounds(SkCanvas* canvas,
|
||||
SkIRect* outRect) {
|
||||
*outRect = wrapper->canvas->getDeviceClipBounds();
|
||||
*outRect = canvas->getDeviceClipBounds();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,14 @@ SKWASM_EXPORT void* skData_getPointer(SkData* data) {
|
||||
return data->writable_data();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT const void* skData_getConstPointer(SkData* data) {
|
||||
return data->data();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT size_t skData_getSize(SkData* data) {
|
||||
return data->size();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void skData_dispose(SkData* data) {
|
||||
return data->unref();
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "third_party/skia/include/core/SkMatrix.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "third_party/skia/include/core/SkSamplingOptions.h"
|
||||
|
||||
namespace Skwasm {
|
||||
|
||||
@@ -23,4 +24,37 @@ inline SkRRect createRRect(const SkScalar* f) {
|
||||
return rr;
|
||||
}
|
||||
|
||||
// This needs to be kept in sync with the "FilterQuality" enum in dart:ui
|
||||
enum class FilterQuality {
|
||||
none,
|
||||
low,
|
||||
medium,
|
||||
high,
|
||||
};
|
||||
|
||||
inline SkFilterMode filterModeForQuality(FilterQuality quality) {
|
||||
switch (quality) {
|
||||
case FilterQuality::none:
|
||||
case FilterQuality::low:
|
||||
return SkFilterMode::kNearest;
|
||||
case FilterQuality::medium:
|
||||
case FilterQuality::high:
|
||||
return SkFilterMode::kLinear;
|
||||
}
|
||||
}
|
||||
|
||||
inline SkSamplingOptions samplingOptionsForQuality(FilterQuality quality) {
|
||||
switch (quality) {
|
||||
case FilterQuality::none:
|
||||
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
|
||||
case FilterQuality::low:
|
||||
return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNearest);
|
||||
case FilterQuality::medium:
|
||||
return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
|
||||
case FilterQuality::high:
|
||||
// Cubic equation coefficients recommended by Mitchell & Netravali
|
||||
// in their paper on cubic interpolation.
|
||||
return SkSamplingOptions(SkCubicResampler::Mitchell());
|
||||
}
|
||||
}
|
||||
} // namespace Skwasm
|
||||
|
||||
77
engine/src/flutter/lib/web_ui/skwasm/image.cpp
Normal file
77
engine/src/flutter/lib/web_ui/skwasm/image.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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.
|
||||
|
||||
#include "export.h"
|
||||
|
||||
#include "third_party/skia/include/core/SkColorSpace.h"
|
||||
#include "third_party/skia/include/core/SkData.h"
|
||||
#include "third_party/skia/include/core/SkImage.h"
|
||||
#include "third_party/skia/include/core/SkImageInfo.h"
|
||||
#include "third_party/skia/include/core/SkPicture.h"
|
||||
|
||||
using namespace SkImages;
|
||||
|
||||
enum class PixelFormat {
|
||||
rgba8888,
|
||||
bgra8888,
|
||||
rgbaFloat32,
|
||||
};
|
||||
|
||||
SkColorType colorTypeForPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::rgba8888:
|
||||
return SkColorType::kRGBA_8888_SkColorType;
|
||||
case PixelFormat::bgra8888:
|
||||
return SkColorType::kBGRA_8888_SkColorType;
|
||||
case PixelFormat::rgbaFloat32:
|
||||
return SkColorType::kRGBA_F32_SkColorType;
|
||||
}
|
||||
}
|
||||
|
||||
SkAlphaType alphaTypeForPixelFormat(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::rgba8888:
|
||||
case PixelFormat::bgra8888:
|
||||
return SkAlphaType::kPremul_SkAlphaType;
|
||||
case PixelFormat::rgbaFloat32:
|
||||
return SkAlphaType::kUnpremul_SkAlphaType;
|
||||
}
|
||||
}
|
||||
|
||||
SKWASM_EXPORT SkImage* image_createFromPicture(SkPicture* picture,
|
||||
int32_t width,
|
||||
int32_t height) {
|
||||
picture->ref();
|
||||
return DeferredFromPicture(sk_sp<SkPicture>(picture), {width, height},
|
||||
nullptr, nullptr, BitDepth::kU8,
|
||||
SkColorSpace::MakeSRGB())
|
||||
.release();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT SkImage* image_createFromPixels(SkData* data,
|
||||
int width,
|
||||
int height,
|
||||
PixelFormat pixelFormat,
|
||||
size_t rowByteCount) {
|
||||
data->ref();
|
||||
return SkImages::RasterFromData(
|
||||
SkImageInfo::Make(width, height,
|
||||
colorTypeForPixelFormat(pixelFormat),
|
||||
alphaTypeForPixelFormat(pixelFormat),
|
||||
SkColorSpace::MakeSRGB()),
|
||||
sk_sp(data), rowByteCount)
|
||||
.release();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void image_dispose(SkImage* image) {
|
||||
image->unref();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT int image_getWidth(SkImage* image) {
|
||||
return image->width();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT int image_getHeight(SkImage* image) {
|
||||
return image->height();
|
||||
}
|
||||
@@ -19,10 +19,10 @@ SKWASM_EXPORT void pictureRecorder_dispose(SkPictureRecorder* recorder) {
|
||||
delete recorder;
|
||||
}
|
||||
|
||||
SKWASM_EXPORT CanvasWrapper* pictureRecorder_beginRecording(
|
||||
SKWASM_EXPORT SkCanvas* pictureRecorder_beginRecording(
|
||||
SkPictureRecorder* recorder,
|
||||
const SkRect* cullRect) {
|
||||
return new CanvasWrapper{0, recorder->beginRecording(*cullRect, &bbhFactory)};
|
||||
return recorder->beginRecording(*cullRect, &bbhFactory);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT SkPicture* pictureRecorder_endRecording(
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "export.h"
|
||||
#include "helpers.h"
|
||||
#include "third_party/skia/include/core/SkImage.h"
|
||||
#include "third_party/skia/include/effects/SkGradientShader.h"
|
||||
#include "third_party/skia/include/effects/SkRuntimeEffect.h"
|
||||
#include "wrappers.h"
|
||||
@@ -136,3 +137,21 @@ SKWASM_EXPORT SkShader* shader_createRuntimeEffectShader(
|
||||
childPointers.data(), childCount, nullptr)
|
||||
.release();
|
||||
}
|
||||
|
||||
SKWASM_EXPORT SkShader* shader_createFromImage(SkImage* image,
|
||||
SkTileMode tileModeX,
|
||||
SkTileMode tileModeY,
|
||||
FilterQuality quality,
|
||||
SkScalar* matrix33) {
|
||||
if (matrix33) {
|
||||
SkMatrix localMatrix = createMatrix(matrix33);
|
||||
return image
|
||||
->makeShader(tileModeX, tileModeY, samplingOptionsForQuality(quality),
|
||||
&localMatrix)
|
||||
.release();
|
||||
} else {
|
||||
return image
|
||||
->makeShader(tileModeX, tileModeY, samplingOptionsForQuality(quality))
|
||||
.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
#include <emscripten/html5_webgl.h>
|
||||
#include <emscripten/threading.h>
|
||||
#include <webgl/webgl1.h>
|
||||
#include <cassert>
|
||||
#include "export.h"
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkColorSpace.h"
|
||||
#include "third_party/skia/include/core/SkPicture.h"
|
||||
#include "third_party/skia/include/core/SkSurface.h"
|
||||
#include "third_party/skia/include/encode/SkPngEncoder.h"
|
||||
#include "third_party/skia/include/gpu/GrDirectContext.h"
|
||||
#include "third_party/skia/include/gpu/ganesh/SkSurfaceGanesh.h"
|
||||
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
|
||||
@@ -21,20 +23,38 @@
|
||||
|
||||
using namespace Skwasm;
|
||||
|
||||
using OnRenderCompleteCallback = void(uint32_t);
|
||||
|
||||
namespace {
|
||||
|
||||
// This must be kept in sync with the `ImageByteFormat` enum in dart:ui.
|
||||
enum class ImageByteFormat {
|
||||
rawRgba,
|
||||
rawStraightRgba,
|
||||
rawUnmodified,
|
||||
png,
|
||||
};
|
||||
|
||||
class Surface;
|
||||
void fDispose(Surface* surface);
|
||||
void fSetCanvasSize(Surface* surface, int width, int height);
|
||||
void fRenderPicture(Surface* surface, SkPicture* picture);
|
||||
void fNotifyRenderComplete(Surface* surface, uint32_t renderId);
|
||||
void fOnRenderComplete(Surface* surface, uint32_t renderId);
|
||||
void fNotifyRenderComplete(Surface* surface, uint32_t callbackId);
|
||||
void fOnRenderComplete(Surface* surface, uint32_t callbackId);
|
||||
void fRasterizeImage(Surface* surface,
|
||||
SkImage* image,
|
||||
ImageByteFormat format,
|
||||
uint32_t callbackId);
|
||||
void fOnRasterizeComplete(Surface* surface,
|
||||
SkData* imageData,
|
||||
uint32_t callbackId);
|
||||
|
||||
class Surface {
|
||||
public:
|
||||
using CallbackHandler = void(uint32_t, void*);
|
||||
|
||||
// Main thread only
|
||||
Surface(const char* canvasID) : _canvasID(canvasID) {
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
@@ -51,6 +71,7 @@ class Surface {
|
||||
|
||||
// Main thread only
|
||||
void dispose() {
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VI,
|
||||
reinterpret_cast<void*>(fDispose), nullptr,
|
||||
this);
|
||||
@@ -58,6 +79,7 @@ class Surface {
|
||||
|
||||
// Main thread only
|
||||
void setCanvasSize(int width, int height) {
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIII,
|
||||
reinterpret_cast<void*>(fSetCanvasSize),
|
||||
nullptr, this, width, height);
|
||||
@@ -65,7 +87,8 @@ class Surface {
|
||||
|
||||
// Main thread only
|
||||
uint32_t renderPicture(SkPicture* picture) {
|
||||
uint32_t renderId = ++_currentRenderId;
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
uint32_t callbackId = ++_currentCallbackId;
|
||||
picture->ref();
|
||||
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VII,
|
||||
reinterpret_cast<void*>(fRenderPicture),
|
||||
@@ -78,13 +101,24 @@ class Surface {
|
||||
emscripten_dispatch_to_thread(
|
||||
_thread, EM_FUNC_SIG_VII,
|
||||
reinterpret_cast<void*>(fNotifyRenderComplete), nullptr, this,
|
||||
renderId);
|
||||
return renderId;
|
||||
callbackId);
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
uint32_t rasterizeImage(SkImage* image, ImageByteFormat format) {
|
||||
uint32_t callbackId = ++_currentCallbackId;
|
||||
image->ref();
|
||||
|
||||
emscripten_dispatch_to_thread(_thread, EM_FUNC_SIG_VIIII,
|
||||
reinterpret_cast<void*>(fRasterizeImage),
|
||||
nullptr, this, image, format, callbackId);
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
// Main thread only
|
||||
void setOnRenderCallback(OnRenderCompleteCallback* callback) {
|
||||
_onRenderCompleteCallback = callback;
|
||||
void setCallbackHandler(CallbackHandler* callbackHandler) {
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
_callbackHandler = callbackHandler;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -177,20 +211,54 @@ class Surface {
|
||||
_surface->flush();
|
||||
}
|
||||
|
||||
void _rasterizeImage(SkImage* image,
|
||||
ImageByteFormat format,
|
||||
uint32_t callbackId) {
|
||||
sk_sp<SkData> data;
|
||||
if (format == ImageByteFormat::png) {
|
||||
data = SkPngEncoder::Encode(_grContext.get(), image, {});
|
||||
} else {
|
||||
SkAlphaType alphaType = format == ImageByteFormat::rawStraightRgba
|
||||
? SkAlphaType::kUnpremul_SkAlphaType
|
||||
: SkAlphaType::kPremul_SkAlphaType;
|
||||
SkImageInfo info = SkImageInfo::Make(image->width(), image->height(),
|
||||
SkColorType::kRGBA_8888_SkColorType,
|
||||
alphaType, SkColorSpace::MakeSRGB());
|
||||
size_t bytesPerRow = 4 * image->width();
|
||||
size_t byteSize = info.computeByteSize(bytesPerRow);
|
||||
data = SkData::MakeUninitialized(byteSize);
|
||||
uint8_t* pixels = reinterpret_cast<uint8_t*>(data->writable_data());
|
||||
bool success = image->readPixels(_grContext.get(), image->imageInfo(),
|
||||
pixels, bytesPerRow, 0, 0);
|
||||
if (!success) {
|
||||
printf("Failed to read pixels from image!\n");
|
||||
data = nullptr;
|
||||
}
|
||||
}
|
||||
emscripten_sync_run_in_main_runtime_thread(EM_FUNC_SIG_VIII,
|
||||
fOnRasterizeComplete, this,
|
||||
data.release(), callbackId);
|
||||
}
|
||||
|
||||
void _onRasterizeComplete(SkData* data, uint32_t callbackId) {
|
||||
_callbackHandler(callbackId, data);
|
||||
}
|
||||
|
||||
// Worker thread only
|
||||
void _notifyRenderComplete(uint32_t renderId) {
|
||||
void _notifyRenderComplete(uint32_t callbackId) {
|
||||
emscripten_sync_run_in_main_runtime_thread(
|
||||
EM_FUNC_SIG_VII, fOnRenderComplete, this, renderId);
|
||||
EM_FUNC_SIG_VII, fOnRenderComplete, this, callbackId);
|
||||
}
|
||||
|
||||
// Main thread only
|
||||
void _onRenderComplete(uint32_t renderId) {
|
||||
_onRenderCompleteCallback(renderId);
|
||||
void _onRenderComplete(uint32_t callbackId) {
|
||||
assert(emscripten_is_main_browser_thread());
|
||||
_callbackHandler(callbackId, nullptr);
|
||||
}
|
||||
|
||||
std::string _canvasID;
|
||||
OnRenderCompleteCallback* _onRenderCompleteCallback = nullptr;
|
||||
uint32_t _currentRenderId = 0;
|
||||
CallbackHandler* _callbackHandler = nullptr;
|
||||
uint32_t _currentCallbackId = 0;
|
||||
|
||||
int _canvasWidth = 0;
|
||||
int _canvasHeight = 0;
|
||||
@@ -207,8 +275,15 @@ class Surface {
|
||||
friend void fDispose(Surface* surface);
|
||||
friend void fSetCanvasSize(Surface* surface, int width, int height);
|
||||
friend void fRenderPicture(Surface* surface, SkPicture* picture);
|
||||
friend void fNotifyRenderComplete(Surface* surface, uint32_t renderId);
|
||||
friend void fOnRenderComplete(Surface* surface, uint32_t renderId);
|
||||
friend void fNotifyRenderComplete(Surface* surface, uint32_t callbackId);
|
||||
friend void fOnRenderComplete(Surface* surface, uint32_t callbackId);
|
||||
friend void fRasterizeImage(Surface* surface,
|
||||
SkImage* image,
|
||||
ImageByteFormat format,
|
||||
uint32_t callbackId);
|
||||
friend void fOnRasterizeComplete(Surface* surface,
|
||||
SkData* imageData,
|
||||
uint32_t callbackId);
|
||||
};
|
||||
|
||||
void fDispose(Surface* surface) {
|
||||
@@ -224,12 +299,26 @@ void fRenderPicture(Surface* surface, SkPicture* picture) {
|
||||
picture->unref();
|
||||
}
|
||||
|
||||
void fNotifyRenderComplete(Surface* surface, uint32_t renderId) {
|
||||
surface->_notifyRenderComplete(renderId);
|
||||
void fNotifyRenderComplete(Surface* surface, uint32_t callbackId) {
|
||||
surface->_notifyRenderComplete(callbackId);
|
||||
}
|
||||
|
||||
void fOnRenderComplete(Surface* surface, uint32_t renderId) {
|
||||
surface->_onRenderComplete(renderId);
|
||||
void fOnRenderComplete(Surface* surface, uint32_t callbackId) {
|
||||
surface->_onRenderComplete(callbackId);
|
||||
}
|
||||
|
||||
void fOnRasterizeComplete(Surface* surface,
|
||||
SkData* imageData,
|
||||
uint32_t callbackId) {
|
||||
surface->_onRasterizeComplete(imageData, callbackId);
|
||||
}
|
||||
|
||||
void fRasterizeImage(Surface* surface,
|
||||
SkImage* image,
|
||||
ImageByteFormat format,
|
||||
uint32_t callbackId) {
|
||||
surface->_rasterizeImage(image, format, callbackId);
|
||||
image->unref();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -237,10 +326,10 @@ SKWASM_EXPORT Surface* surface_createFromCanvas(const char* canvasID) {
|
||||
return new Surface(canvasID);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void surface_setOnRenderCallback(
|
||||
SKWASM_EXPORT void surface_setCallbackHandler(
|
||||
Surface* surface,
|
||||
OnRenderCompleteCallback* callback) {
|
||||
surface->setOnRenderCallback(callback);
|
||||
Surface::CallbackHandler* callbackHandler) {
|
||||
surface->setCallbackHandler(callbackHandler);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT void surface_destroy(Surface* surface) {
|
||||
@@ -257,3 +346,9 @@ SKWASM_EXPORT uint32_t surface_renderPicture(Surface* surface,
|
||||
SkPicture* picture) {
|
||||
return surface->renderPicture(picture);
|
||||
}
|
||||
|
||||
SKWASM_EXPORT uint32_t surface_rasterizeImage(Surface* surface,
|
||||
SkImage* image,
|
||||
ImageByteFormat format) {
|
||||
return surface->rasterizeImage(image, format);
|
||||
}
|
||||
|
||||
@@ -18,11 +18,6 @@ struct SurfaceWrapper {
|
||||
sk_sp<SkSurface> surface;
|
||||
};
|
||||
|
||||
struct CanvasWrapper {
|
||||
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context;
|
||||
SkCanvas* canvas;
|
||||
};
|
||||
|
||||
inline void makeCurrent(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle) {
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
@@ -18,11 +18,11 @@ class FakeAssetManager implements AssetManager {
|
||||
|
||||
@override
|
||||
Future<ByteData> load(String assetKey) async {
|
||||
final ByteData? data = _assetMap[assetKey];
|
||||
if (data == null) {
|
||||
final ByteData? assetData = await _currentScope?.getAssetData(assetKey);
|
||||
if (assetData == null) {
|
||||
throw HttpFetchNoPayloadError(assetKey, status: 404);
|
||||
}
|
||||
return data;
|
||||
return assetData;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,12 +55,7 @@ class FakeAssetManager implements AssetManager {
|
||||
_currentScope = scope._parent;
|
||||
}
|
||||
|
||||
void setAsset(String assetKey, ByteData assetData) {
|
||||
_assetMap[assetKey] = assetData;
|
||||
}
|
||||
|
||||
FakeAssetScope? _currentScope;
|
||||
final Map<String, ByteData> _assetMap = <String, ByteData>{};
|
||||
}
|
||||
|
||||
class FakeAssetScope {
|
||||
|
||||
@@ -48,11 +48,20 @@ Future<void> testMain() async {
|
||||
|
||||
const ui.Rect region = ui.Rect.fromLTWH(0, 0, 300, 300);
|
||||
|
||||
test('fragment shader', () async {
|
||||
fakeAssetManager.setAsset(
|
||||
late FakeAssetScope assetScope;
|
||||
setUp(() {
|
||||
assetScope = fakeAssetManager.pushAssetScope();
|
||||
assetScope.setAsset(
|
||||
'voronoi_shader',
|
||||
Uint8List.fromList(utf8.encode(kVoronoiShaderSksl)).buffer.asByteData()
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
fakeAssetManager.popAssetScope(assetScope);
|
||||
});
|
||||
|
||||
test('fragment shader', () async {
|
||||
final ui.FragmentProgram program = await renderer.createFragmentProgram('voronoi_shader');
|
||||
final ui.FragmentShader shader = program.fragmentShader();
|
||||
|
||||
|
||||
291
engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart
Normal file
291
engine/src/flutter/lib/web_ui/test/ui/image_golden_test.dart
Normal file
@@ -0,0 +1,291 @@
|
||||
// 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.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
import 'package:web_engine_tester/golden_tester.dart';
|
||||
|
||||
import '../common/fake_asset_manager.dart';
|
||||
import '../common/test_initialization.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
const String kGlitchShaderSksl = r'''
|
||||
{
|
||||
"sksl": "// This SkSL shader is autogenerated by spirv-cross.\n\nfloat4 flutter_FragCoord;\n\nuniform vec2 uResolution;\nuniform float uTime;\nuniform shader uTex;\nuniform half2 uTex_size;\n\nvec4 oColor;\n\nvec2 FLT_flutter_local_FlutterFragCoord()\n{\n return flutter_FragCoord.xy;\n}\n\nfloat FLT_flutter_local_cubicPulse(float c, float w, inout float x)\n{\n x = abs(x - c);\n if (x > w)\n {\n return 0.0;\n }\n x /= w;\n return 1.0 - ((x * x) * (3.0 - (2.0 * x)));\n}\n\nfloat FLT_flutter_local_twoSin(inout float x)\n{\n x = (6.4899997711181640625 * x) - 0.64999997615814208984375;\n float t = ((-0.699999988079071044921875) * sin(6.80000019073486328125 * x)) + (1.39999997615814208984375 * sin(2.900000095367431640625 * x));\n t = (t / 4.099999904632568359375) + 0.5;\n return t;\n}\n\nfloat FLT_flutter_local_hash_1d(float v)\n{\n float u = 50.0 * sin(v * 3000.0);\n return (2.0 * fract((2.0 * u) * u)) - 1.0;\n}\n\nvoid FLT_main()\n{\n vec2 uv = vec2(FLT_flutter_local_FlutterFragCoord()) / uResolution;\n float param = 0.5;\n float param_1 = 0.0500000007450580596923828125;\n float param_2 = fract(uTime / 4.0);\n float _127 = FLT_flutter_local_cubicPulse(param, param_1, param_2);\n float t_2 = _127;\n float param_3 = fract(uTime / 5.0);\n float _134 = FLT_flutter_local_twoSin(param_3);\n float t_1 = _134;\n float glitchScale = mix(0.0, 8.0, t_1 + t_2);\n float aberrationSize = mix(0.0, 5.0, t_1 + t_2);\n float param_4 = uv.y;\n float h = FLT_flutter_local_hash_1d(param_4);\n float hs = sign(h);\n h = max(h, 0.0);\n h *= h;\n h = floor(h + float(0.5)) * hs;\n uv += (vec2(h * glitchScale, 0.0) / uResolution);\n vec2 redOffset = vec2(aberrationSize, 0.0) / uResolution;\n vec2 greenOffset = vec2(0.0) / uResolution;\n vec2 blueOffset = vec2(-aberrationSize, 0.0) / uResolution;\n vec2 redUv = uv + redOffset;\n vec2 greenUv = uv + greenOffset;\n vec2 blueUv = uv + blueOffset;\n vec2 ra = uTex.eval(uTex_size * redUv).xw;\n vec2 ga = uTex.eval(uTex_size * greenUv).yw;\n vec2 ba = uTex.eval(uTex_size * blueUv).zw;\n ra.x /= ra.y;\n ga.x /= ga.y;\n ba.x /= ba.y;\n float alpha = max(ra.y, max(ga.y, ba.y));\n oColor = vec4(ra.x, ga.x, ba.x, 1.0) * alpha;\n}\n\nhalf4 main(float2 iFragCoord)\n{\n flutter_FragCoord = float4(iFragCoord, 0, 0);\n FLT_main();\n return oColor;\n}\n",
|
||||
"stage": 1,
|
||||
"target_platform": 2,
|
||||
"uniforms": [
|
||||
{
|
||||
"array_elements": 0,
|
||||
"bit_width": 32,
|
||||
"columns": 1,
|
||||
"location": 0,
|
||||
"name": "uResolution",
|
||||
"rows": 2,
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"array_elements": 0,
|
||||
"bit_width": 32,
|
||||
"columns": 1,
|
||||
"location": 1,
|
||||
"name": "uTime",
|
||||
"rows": 1,
|
||||
"type": 10
|
||||
},
|
||||
{
|
||||
"array_elements": 0,
|
||||
"bit_width": 0,
|
||||
"columns": 1,
|
||||
"location": 2,
|
||||
"name": "uTex",
|
||||
"rows": 1,
|
||||
"type": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
''';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
Future<void> testMain() async {
|
||||
setUpUnitTests(
|
||||
setUpTestViewDimensions: false,
|
||||
);
|
||||
|
||||
late FakeAssetScope assetScope;
|
||||
setUp(() {
|
||||
assetScope = fakeAssetManager.pushAssetScope();
|
||||
assetScope.setAsset(
|
||||
'glitch_shader',
|
||||
Uint8List.fromList(utf8.encode(kGlitchShaderSksl)).buffer.asByteData()
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
fakeAssetManager.popAssetScope(assetScope);
|
||||
});
|
||||
|
||||
const ui.Rect drawRegion = ui.Rect.fromLTWH(0, 0, 300, 300);
|
||||
const ui.Rect imageRegion = ui.Rect.fromLTWH(0, 0, 150, 150);
|
||||
|
||||
// Emits a set of rendering tests for an image
|
||||
// `imageGenerator` should produce an image that is 150x150 pixels.
|
||||
void emitImageTests(String name, Future<ui.Image> Function() imageGenerator) {
|
||||
group(name, () {
|
||||
test('drawImage', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
|
||||
canvas.drawImage(image, const ui.Offset(100, 100), ui.Paint());
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_canvas_drawImage.png', region: drawRegion);
|
||||
});
|
||||
|
||||
test('drawImageRect', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
const ui.Rect.fromLTRB(50, 50, 100, 100),
|
||||
const ui.Rect.fromLTRB(100, 100, 150, 150),
|
||||
ui.Paint()
|
||||
);
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_canvas_drawImageRect.png', region: drawRegion);
|
||||
});
|
||||
|
||||
test('drawImageNine', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
|
||||
canvas.drawImageNine(
|
||||
image,
|
||||
const ui.Rect.fromLTRB(50, 50, 100, 100),
|
||||
drawRegion,
|
||||
ui.Paint()
|
||||
);
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_canvas_drawImageNine.png', region: drawRegion);
|
||||
});
|
||||
|
||||
test('image_shader_cubic_rotated', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final Float64List matrix = Matrix4.rotationZ(pi / 6).toFloat64();
|
||||
final ui.ImageShader shader = ui.ImageShader(
|
||||
image,
|
||||
ui.TileMode.repeated,
|
||||
ui.TileMode.repeated,
|
||||
matrix,
|
||||
filterQuality: ui.FilterQuality.high,
|
||||
);
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
|
||||
canvas.drawOval(
|
||||
const ui.Rect.fromLTRB(0, 50, 300, 250),
|
||||
ui.Paint()..shader = shader
|
||||
);
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
await matchGoldenFile('${name}_image_shader_cubic_rotated.png', region: drawRegion);
|
||||
});
|
||||
|
||||
test('fragment_shader_sampler', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ui.FragmentProgram program = await renderer.createFragmentProgram('glitch_shader');
|
||||
final ui.FragmentShader shader = program.fragmentShader();
|
||||
|
||||
// Resolution
|
||||
shader.setFloat(0, 300);
|
||||
shader.setFloat(1, 300);
|
||||
|
||||
// Time
|
||||
shader.setFloat(2, 2);
|
||||
|
||||
// Image
|
||||
shader.setImageSampler(0, image);
|
||||
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, drawRegion);
|
||||
canvas.drawCircle(const ui.Offset(150, 150), 100, ui.Paint()..shader = shader);
|
||||
|
||||
await drawPictureUsingCurrentRenderer(recorder.endRecording());
|
||||
|
||||
await matchGoldenFile('${name}_fragment_shader_sampler.png', region: drawRegion);
|
||||
}, skip: isHtml); // HTML doesn't support fragment shaders
|
||||
|
||||
test('toByteData_rgba', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ByteData? rgbaData = await image.toByteData();
|
||||
expect(rgbaData, isNotNull);
|
||||
expect(rgbaData!.lengthInBytes, isNonZero);
|
||||
});
|
||||
|
||||
test('toByteData_rgba', () async {
|
||||
final ui.Image image = await imageGenerator();
|
||||
expect(image.width, 150);
|
||||
expect(image.height, 150);
|
||||
|
||||
final ByteData? pngData = await image.toByteData(format: ui.ImageByteFormat.png);
|
||||
expect(pngData, isNotNull);
|
||||
expect(pngData!.lengthInBytes, isNonZero);
|
||||
}, skip: isHtml); // https://github.com/flutter/flutter/issues/126611
|
||||
});
|
||||
}
|
||||
|
||||
emitImageTests('picture_toImage', () {
|
||||
final ui.PictureRecorder recorder = ui.PictureRecorder();
|
||||
final ui.Canvas canvas = ui.Canvas(recorder, imageRegion);
|
||||
for (int y = 0; y < 15; y++) {
|
||||
for (int x = 0; x < 15; x++) {
|
||||
final ui.Offset center = ui.Offset(x * 10 + 5, y * 10 + 5);
|
||||
final ui.Color color = ui.Color.fromRGBO(
|
||||
(center.dx * 256 / 150).round(),
|
||||
(center.dy * 256 / 150).round(), 0, 1);
|
||||
canvas.drawCircle(center, 5, ui.Paint()..color = color);
|
||||
}
|
||||
}
|
||||
return recorder.endRecording().toImage(150, 150);
|
||||
});
|
||||
|
||||
Uint8List generatePixelData(
|
||||
int width,
|
||||
int height,
|
||||
ui.Color Function(double, double) generator
|
||||
) {
|
||||
final Uint8List data = Uint8List(width * height * 4);
|
||||
int outputIndex = 0;
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
final ui.Color pixelColor = generator(
|
||||
(2.0 * x / width) - 1.0,
|
||||
(2.0 * y / height) - 1.0,
|
||||
);
|
||||
data[outputIndex++] = pixelColor.red;
|
||||
data[outputIndex++] = pixelColor.green;
|
||||
data[outputIndex++] = pixelColor.blue;
|
||||
data[outputIndex++] = pixelColor.alpha;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
emitImageTests('decodeImageFromPixels_unscaled', () {
|
||||
final Uint8List pixels = generatePixelData(150, 150, (double x, double y) {
|
||||
final double r = sqrt(x * x + y * y);
|
||||
final double theta = atan2(x, y);
|
||||
return ui.Color.fromRGBO(
|
||||
(255 * (sin(r * 10.0) + 1.0) / 2.0).round(),
|
||||
(255 * (sin(theta * 10.0) + 1.0) / 2.0).round(),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
});
|
||||
final Completer<ui.Image> completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(pixels, 150, 150, ui.PixelFormat.rgba8888, completer.complete);
|
||||
return completer.future;
|
||||
});
|
||||
|
||||
// https://github.com/flutter/flutter/issues/126603
|
||||
if (!isHtml) {
|
||||
emitImageTests('decodeImageFromPixels_scaled', () {
|
||||
final Uint8List pixels = generatePixelData(50, 50, (double x, double y) {
|
||||
final double r = sqrt(x * x + y * y);
|
||||
final double theta = atan2(x, y);
|
||||
return ui.Color.fromRGBO(
|
||||
(255 * (sin(r * 10.0) + 1.0) / 2.0).round(),
|
||||
(255 * (sin(theta * 10.0) + 1.0) / 2.0).round(),
|
||||
0,
|
||||
1,
|
||||
);
|
||||
});
|
||||
final Completer<ui.Image> completer = Completer<ui.Image>();
|
||||
ui.decodeImageFromPixels(
|
||||
pixels,
|
||||
50,
|
||||
50,
|
||||
ui.PixelFormat.rgba8888,
|
||||
completer.complete,
|
||||
targetWidth: 150,
|
||||
targetHeight: 150,
|
||||
);
|
||||
return completer.future;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user