mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make CkPicture resurrectable (#22807)
* Make CkPicture resurrectable * disable goldens on Firefox * add non-recording canvas test; fix Firefox test
This commit is contained in:
parent
e71c6f4d7e
commit
37738353cd
@ -1,2 +1,2 @@
|
||||
repository: https://github.com/flutter/goldens.git
|
||||
revision: 06e0333b8371965dce5dc05e140e6dfb454f33fa
|
||||
revision: ac75f12c6e93461369e1391da6cc20bf8cb08829
|
||||
|
||||
@ -436,35 +436,39 @@ class TestCommand extends Command<bool> with ArgUtils {
|
||||
'test',
|
||||
));
|
||||
|
||||
if (isUnitTestsScreenshotsAvailable) {
|
||||
// Separate screenshot tests from unit-tests. Screenshot tests must run
|
||||
// one at a time. Otherwise, they will end up screenshotting each other.
|
||||
// This is not an issue for unit-tests.
|
||||
final FilePath failureSmokeTestPath = FilePath.fromWebUi(
|
||||
'test/golden_tests/golden_failure_smoke_test.dart',
|
||||
);
|
||||
final List<FilePath> screenshotTestFiles = <FilePath>[];
|
||||
final List<FilePath> unitTestFiles = <FilePath>[];
|
||||
// Separate screenshot tests from unit-tests. Screenshot tests must run
|
||||
// one at a time. Otherwise, they will end up screenshotting each other.
|
||||
// This is not an issue for unit-tests.
|
||||
final FilePath failureSmokeTestPath = FilePath.fromWebUi(
|
||||
'test/golden_tests/golden_failure_smoke_test.dart',
|
||||
);
|
||||
final List<FilePath> screenshotTestFiles = <FilePath>[];
|
||||
final List<FilePath> unitTestFiles = <FilePath>[];
|
||||
|
||||
for (io.File testFile
|
||||
in testDir.listSync(recursive: true).whereType<io.File>()) {
|
||||
final FilePath testFilePath = FilePath.fromCwd(testFile.path);
|
||||
if (!testFilePath.absolute.endsWith('_test.dart')) {
|
||||
// Not a test file at all. Skip.
|
||||
continue;
|
||||
}
|
||||
if (testFilePath == failureSmokeTestPath) {
|
||||
// A smoke test that fails on purpose. Skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.split(testFilePath.relativeToWebUi).contains('golden_tests')) {
|
||||
screenshotTestFiles.add(testFilePath);
|
||||
} else {
|
||||
unitTestFiles.add(testFilePath);
|
||||
}
|
||||
for (io.File testFile
|
||||
in testDir.listSync(recursive: true).whereType<io.File>()) {
|
||||
final FilePath testFilePath = FilePath.fromCwd(testFile.path);
|
||||
if (!testFilePath.absolute.endsWith('_test.dart')) {
|
||||
// Not a test file at all. Skip.
|
||||
continue;
|
||||
}
|
||||
if (testFilePath == failureSmokeTestPath) {
|
||||
// A smoke test that fails on purpose. Skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
// All files under test/golden_tests are considered golden tests.
|
||||
final bool isUnderGoldenTestsDirectory = path.split(testFilePath.relativeToWebUi).contains('golden_tests');
|
||||
// Any file whose name ends with "_golden_test.dart" is run as a golden test.
|
||||
final bool isGoldenTestFile = path.basename(testFilePath.relativeToWebUi).endsWith('_golden_test.dart');
|
||||
if (isUnderGoldenTestsDirectory || isGoldenTestFile) {
|
||||
screenshotTestFiles.add(testFilePath);
|
||||
} else {
|
||||
unitTestFiles.add(testFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (isUnitTestsScreenshotsAvailable) {
|
||||
// This test returns a non-zero exit code on purpose. Run it separately.
|
||||
if (io.Platform.environment['CIRRUS_CI'] != 'true') {
|
||||
await _runTestBatch(
|
||||
@ -474,11 +478,13 @@ class TestCommand extends Command<bool> with ArgUtils {
|
||||
);
|
||||
_checkExitCode();
|
||||
}
|
||||
}
|
||||
|
||||
// Run all unit-tests as a single batch.
|
||||
await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false);
|
||||
_checkExitCode();
|
||||
// Run all unit-tests as a single batch.
|
||||
await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false);
|
||||
_checkExitCode();
|
||||
|
||||
if (isUnitTestsScreenshotsAvailable) {
|
||||
// Run screenshot tests one at a time.
|
||||
for (FilePath testFilePath in screenshotTestFiles) {
|
||||
await _runTestBatch(
|
||||
@ -488,24 +494,6 @@ class TestCommand extends Command<bool> with ArgUtils {
|
||||
);
|
||||
_checkExitCode();
|
||||
}
|
||||
} else {
|
||||
final List<FilePath> unitTestFiles = <FilePath>[];
|
||||
for (io.File testFile
|
||||
in testDir.listSync(recursive: true).whereType<io.File>()) {
|
||||
final FilePath testFilePath = FilePath.fromCwd(testFile.path);
|
||||
if (!testFilePath.absolute.endsWith('_test.dart')) {
|
||||
// Not a test file at all. Skip.
|
||||
continue;
|
||||
}
|
||||
if (!path
|
||||
.split(testFilePath.relativeToWebUi)
|
||||
.contains('golden_tests')) {
|
||||
unitTestFiles.add(testFilePath);
|
||||
}
|
||||
}
|
||||
// Run all unit-tests as a single batch.
|
||||
await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false);
|
||||
_checkExitCode();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,10 @@
|
||||
// @dart = 2.12
|
||||
part of engine;
|
||||
|
||||
/// Memoized value for ClipOp.Intersect, so we don't have to hit JS-interop
|
||||
/// every time we need it.
|
||||
final SkClipOp _clipOpIntersect = canvasKit.ClipOp.Intersect;
|
||||
|
||||
/// A Dart wrapper around Skia's [SkCanvas].
|
||||
///
|
||||
/// This is intentionally not memory-managing the underlying [SkCanvas]. See
|
||||
@ -20,12 +24,9 @@ class CkCanvas {
|
||||
skCanvas.clear(toSharedSkColor1(color));
|
||||
}
|
||||
|
||||
static final SkClipOp _clipOpIntersect = canvasKit.ClipOp.Intersect;
|
||||
|
||||
void clipPath(ui.Path path, bool doAntiAlias) {
|
||||
final CkPath ckPath = path as CkPath;
|
||||
void clipPath(CkPath path, bool doAntiAlias) {
|
||||
skCanvas.clipPath(
|
||||
ckPath.skiaObject,
|
||||
path.skiaObject,
|
||||
_clipOpIntersect,
|
||||
doAntiAlias,
|
||||
);
|
||||
@ -66,15 +67,14 @@ class CkCanvas {
|
||||
|
||||
void drawAtlasRaw(
|
||||
CkPaint paint,
|
||||
ui.Image atlas,
|
||||
CkImage atlas,
|
||||
Float32List rstTransforms,
|
||||
Float32List rects,
|
||||
List<Float32List>? colors,
|
||||
ui.BlendMode blendMode,
|
||||
) {
|
||||
final CkImage skAtlas = atlas as CkImage;
|
||||
skCanvas.drawAtlas(
|
||||
skAtlas.skImage,
|
||||
atlas.skImage,
|
||||
rects,
|
||||
rstTransforms,
|
||||
paint.skiaObject,
|
||||
@ -107,20 +107,18 @@ class CkCanvas {
|
||||
);
|
||||
}
|
||||
|
||||
void drawImage(ui.Image image, ui.Offset offset, CkPaint paint) {
|
||||
final CkImage skImage = image as CkImage;
|
||||
void drawImage(CkImage image, ui.Offset offset, CkPaint paint) {
|
||||
skCanvas.drawImage(
|
||||
skImage.skImage,
|
||||
image.skImage,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
|
||||
void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, CkPaint paint) {
|
||||
final CkImage skImage = image as CkImage;
|
||||
void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) {
|
||||
skCanvas.drawImageRect(
|
||||
skImage.skImage,
|
||||
image.skImage,
|
||||
toSkRect(src),
|
||||
toSkRect(dst),
|
||||
paint.skiaObject,
|
||||
@ -129,10 +127,9 @@ class CkCanvas {
|
||||
}
|
||||
|
||||
void drawImageNine(
|
||||
ui.Image image, ui.Rect center, ui.Rect dst, CkPaint paint) {
|
||||
final CkImage skImage = image as CkImage;
|
||||
CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) {
|
||||
skCanvas.drawImageNine(
|
||||
skImage.skImage,
|
||||
image.skImage,
|
||||
toSkRect(center),
|
||||
toSkRect(dst),
|
||||
paint.skiaObject,
|
||||
@ -173,7 +170,7 @@ class CkCanvas {
|
||||
}
|
||||
|
||||
void drawPicture(CkPicture picture) {
|
||||
skCanvas.drawPicture(picture.skiaObject.skiaObject);
|
||||
skCanvas.drawPicture(picture.skiaObject);
|
||||
}
|
||||
|
||||
void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
|
||||
@ -195,17 +192,16 @@ class CkCanvas {
|
||||
skCanvas.drawRect(toSkRect(rect), paint.skiaObject);
|
||||
}
|
||||
|
||||
void drawShadow(ui.Path path, ui.Color color, double elevation,
|
||||
void drawShadow(CkPath path, ui.Color color, double elevation,
|
||||
bool transparentOccluder) {
|
||||
drawSkShadow(skCanvas, path as CkPath, color, elevation,
|
||||
drawSkShadow(skCanvas, path, color, elevation,
|
||||
transparentOccluder, ui.window.devicePixelRatio);
|
||||
}
|
||||
|
||||
void drawVertices(
|
||||
ui.Vertices vertices, ui.BlendMode blendMode, CkPaint paint) {
|
||||
CkVertices skVertices = vertices as CkVertices;
|
||||
CkVertices vertices, ui.BlendMode blendMode, CkPaint paint) {
|
||||
skCanvas.drawVertices(
|
||||
skVertices.skiaObject,
|
||||
vertices.skiaObject,
|
||||
toSkBlendMode(blendMode),
|
||||
paint.skiaObject,
|
||||
);
|
||||
@ -227,17 +223,17 @@ class CkCanvas {
|
||||
return skCanvas.save();
|
||||
}
|
||||
|
||||
void saveLayer(ui.Rect bounds, CkPaint paint) {
|
||||
void saveLayer(ui.Rect bounds, CkPaint? paint) {
|
||||
skCanvas.saveLayer(
|
||||
paint.skiaObject,
|
||||
paint?.skiaObject,
|
||||
toSkRect(bounds),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
void saveLayerWithoutBounds(CkPaint paint) {
|
||||
skCanvas.saveLayer(paint.skiaObject, null, null, null);
|
||||
void saveLayerWithoutBounds(CkPaint? paint) {
|
||||
skCanvas.saveLayer(paint?.skiaObject, null, null, null);
|
||||
}
|
||||
|
||||
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
|
||||
@ -266,7 +262,811 @@ class CkCanvas {
|
||||
skCanvas.translate(dx, dy);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
skCanvas.flush();
|
||||
CkPictureSnapshot? get pictureSnapshot => null;
|
||||
}
|
||||
|
||||
class RecordingCkCanvas extends CkCanvas {
|
||||
RecordingCkCanvas(SkCanvas skCanvas, ui.Rect bounds)
|
||||
: pictureSnapshot = CkPictureSnapshot(bounds),
|
||||
super(skCanvas);
|
||||
|
||||
@override
|
||||
final CkPictureSnapshot pictureSnapshot;
|
||||
|
||||
void _addCommand(CkPaintCommand command) {
|
||||
pictureSnapshot._commands.add(command);
|
||||
}
|
||||
|
||||
@override
|
||||
void clear(ui.Color color) {
|
||||
super.clear(color);
|
||||
_addCommand(CkClearCommand(color));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipPath(CkPath path, bool doAntiAlias) {
|
||||
super.clipPath(path, doAntiAlias);
|
||||
_addCommand(CkClipPathCommand(path, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRRect(ui.RRect rrect, bool doAntiAlias) {
|
||||
super.clipRRect(rrect, doAntiAlias);
|
||||
_addCommand(CkClipRRectCommand(rrect, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) {
|
||||
super.clipRect(rect, clipOp, doAntiAlias);
|
||||
_addCommand(CkClipRectCommand(rect, clipOp, doAntiAlias));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawArc(
|
||||
ui.Rect oval,
|
||||
double startAngle,
|
||||
double sweepAngle,
|
||||
bool useCenter,
|
||||
CkPaint paint,
|
||||
) {
|
||||
super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
|
||||
_addCommand(CkDrawArcCommand(oval, startAngle, sweepAngle, useCenter, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawAtlasRaw(
|
||||
CkPaint paint,
|
||||
CkImage atlas,
|
||||
Float32List rstTransforms,
|
||||
Float32List rects,
|
||||
List<Float32List>? colors,
|
||||
ui.BlendMode blendMode,
|
||||
) {
|
||||
super.drawAtlasRaw(paint, atlas, rstTransforms, rects, colors, blendMode);
|
||||
_addCommand(CkDrawAtlasCommand(paint, atlas, rstTransforms, rects, colors, blendMode));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawCircle(ui.Offset c, double radius, CkPaint paint) {
|
||||
super.drawCircle(c, radius, paint);
|
||||
_addCommand(CkDrawCircleCommand(c, radius, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawColor(ui.Color color, ui.BlendMode blendMode) {
|
||||
super.drawColor(color, blendMode);
|
||||
_addCommand(CkDrawColorCommand(color, blendMode));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawDRRect(ui.RRect outer, ui.RRect inner, CkPaint paint) {
|
||||
super.drawDRRect(outer, inner, paint);
|
||||
_addCommand(CkDrawDRRectCommand(outer, inner, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImage(CkImage image, ui.Offset offset, CkPaint paint) {
|
||||
super.drawImage(image, offset, paint);
|
||||
_addCommand(CkDrawImageCommand(image, offset, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImageRect(CkImage image, ui.Rect src, ui.Rect dst, CkPaint paint) {
|
||||
super.drawImageRect(image, src, dst, paint);
|
||||
_addCommand(CkDrawImageRectCommand(image, src, dst, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawImageNine(
|
||||
CkImage image, ui.Rect center, ui.Rect dst, CkPaint paint) {
|
||||
super.drawImageNine(image, center, dst, paint);
|
||||
_addCommand(CkDrawImageNineCommand(image, center, dst, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawLine(ui.Offset p1, ui.Offset p2, CkPaint paint) {
|
||||
super.drawLine(p1, p2, paint);
|
||||
_addCommand(CkDrawLineCommand(p1, p2, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawOval(ui.Rect rect, CkPaint paint) {
|
||||
super.drawOval(rect, paint);
|
||||
_addCommand(CkDrawOvalCommand(rect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPaint(CkPaint paint) {
|
||||
super.drawPaint(paint);
|
||||
_addCommand(CkDrawPaintCommand(paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawParagraph(CkParagraph paragraph, ui.Offset offset) {
|
||||
super.drawParagraph(paragraph, offset);
|
||||
_addCommand(CkDrawParagraphCommand(paragraph, offset));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPath(CkPath path, CkPaint paint) {
|
||||
super.drawPath(path, paint);
|
||||
_addCommand(CkDrawPathCommand(path, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPicture(CkPicture picture) {
|
||||
super.drawPicture(picture);
|
||||
_addCommand(CkDrawPictureCommand(picture));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawPoints(CkPaint paint, ui.PointMode pointMode, Float32List points) {
|
||||
super.drawPoints(paint, pointMode, points);
|
||||
_addCommand(CkDrawPointsCommand(pointMode, points, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRRect(ui.RRect rrect, CkPaint paint) {
|
||||
super.drawRRect(rrect, paint);
|
||||
_addCommand(CkDrawRRectCommand(rrect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawRect(ui.Rect rect, CkPaint paint) {
|
||||
super.drawRect(rect, paint);
|
||||
_addCommand(CkDrawRectCommand(rect, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawShadow(CkPath path, ui.Color color, double elevation,
|
||||
bool transparentOccluder) {
|
||||
super.drawShadow(path, color, elevation, transparentOccluder);
|
||||
_addCommand(CkDrawShadowCommand(path, color, elevation, transparentOccluder));
|
||||
}
|
||||
|
||||
@override
|
||||
void drawVertices(
|
||||
CkVertices vertices, ui.BlendMode blendMode, CkPaint paint) {
|
||||
super.drawVertices(vertices, blendMode, paint);
|
||||
_addCommand(CkDrawVerticesCommand(vertices, blendMode, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void restore() {
|
||||
super.restore();
|
||||
_addCommand(const CkRestoreCommand());
|
||||
}
|
||||
|
||||
@override
|
||||
void restoreToCount(int count) {
|
||||
super.restoreToCount(count);
|
||||
_addCommand(CkRestoreToCountCommand(count));
|
||||
}
|
||||
|
||||
@override
|
||||
void rotate(double radians) {
|
||||
super.rotate(radians);
|
||||
_addCommand(CkRotateCommand(radians));
|
||||
}
|
||||
|
||||
@override
|
||||
int save() {
|
||||
_addCommand(const CkSaveCommand());
|
||||
return super.save();
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayer(ui.Rect bounds, CkPaint? paint) {
|
||||
super.saveLayer(bounds, paint);
|
||||
_addCommand(CkSaveLayerCommand(bounds, paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayerWithoutBounds(CkPaint? paint) {
|
||||
super.saveLayerWithoutBounds(paint);
|
||||
_addCommand(CkSaveLayerWithoutBoundsCommand(paint));
|
||||
}
|
||||
|
||||
@override
|
||||
void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) {
|
||||
super.saveLayerWithFilter(bounds, filter);
|
||||
_addCommand(CkSaveLayerWithFilterCommand(bounds, filter));
|
||||
}
|
||||
|
||||
@override
|
||||
void scale(double sx, double sy) {
|
||||
super.scale(sx, sy);
|
||||
_addCommand(CkScaleCommand(sx, sy));
|
||||
}
|
||||
|
||||
@override
|
||||
void skew(double sx, double sy) {
|
||||
super.skew(sx, sy);
|
||||
_addCommand(CkSkewCommand(sx, sy));
|
||||
}
|
||||
|
||||
@override
|
||||
void transform(Float32List matrix4) {
|
||||
super.transform(matrix4);
|
||||
_addCommand(CkTransformCommand(matrix4));
|
||||
}
|
||||
|
||||
@override
|
||||
void translate(double dx, double dy) {
|
||||
super.translate(dx, dy);
|
||||
_addCommand(CkTranslateCommand(dx, dy));
|
||||
}
|
||||
}
|
||||
|
||||
class CkPictureSnapshot {
|
||||
CkPictureSnapshot(this._bounds);
|
||||
|
||||
final ui.Rect _bounds;
|
||||
final List<CkPaintCommand> _commands = <CkPaintCommand>[];
|
||||
|
||||
SkPicture toPicture() {
|
||||
final SkPictureRecorder recorder = SkPictureRecorder();
|
||||
final Float32List skRect = toSkRect(_bounds);
|
||||
final SkCanvas skCanvas = recorder.beginRecording(skRect);
|
||||
for (final CkPaintCommand command in _commands) {
|
||||
command.apply(skCanvas);
|
||||
}
|
||||
final SkPicture skPicture = recorder.finishRecordingAsPicture();
|
||||
recorder.delete();
|
||||
return skPicture;
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
for (final CkPaintCommand command in _commands) {
|
||||
command.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A paint command recorded by [RecordingCkCanvas].
|
||||
///
|
||||
/// # Special rules when drawing images
|
||||
///
|
||||
/// A command painting an image must clone the original image to bump the ref
|
||||
/// count. Otherwise when the framework decides it doesn't need the image any
|
||||
/// more it will bump the ref count down and delete the underlying Skia object,
|
||||
/// leaving the picture that recorded this paint command with a dangling
|
||||
/// pointer. If we attempt to resurrect the picture we'll hit a use-after-free
|
||||
/// error. The command must call [CkImage.dispose] in its [dispose]
|
||||
/// implementation.
|
||||
abstract class CkPaintCommand {
|
||||
const CkPaintCommand();
|
||||
|
||||
/// Applies the command onto the [canvas].
|
||||
void apply(SkCanvas canvas);
|
||||
|
||||
/// Frees resources associated with the command.
|
||||
void dispose() {}
|
||||
}
|
||||
|
||||
class CkClearCommand extends CkPaintCommand {
|
||||
const CkClearCommand(this.color);
|
||||
|
||||
final ui.Color color;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clear(toSharedSkColor1(color));
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveCommand extends CkPaintCommand {
|
||||
const CkSaveCommand();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.save();
|
||||
}
|
||||
}
|
||||
|
||||
class CkRestoreCommand extends CkPaintCommand {
|
||||
const CkRestoreCommand();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
class CkRestoreToCountCommand extends CkPaintCommand {
|
||||
const CkRestoreToCountCommand(this.count);
|
||||
|
||||
final int count;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
}
|
||||
|
||||
class CkTranslateCommand extends CkPaintCommand {
|
||||
final double dx;
|
||||
final double dy;
|
||||
|
||||
CkTranslateCommand(this.dx, this.dy);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.translate(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkScaleCommand extends CkPaintCommand {
|
||||
final double sx;
|
||||
final double sy;
|
||||
|
||||
CkScaleCommand(this.sx, this.sy);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.scale(sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkRotateCommand extends CkPaintCommand {
|
||||
final double radians;
|
||||
|
||||
CkRotateCommand(this.radians);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.rotate(radians * 180.0 / math.pi, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
class CkTransformCommand extends CkPaintCommand {
|
||||
final Float32List matrix4;
|
||||
|
||||
CkTransformCommand(this.matrix4);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.concat(toSkMatrixFromFloat32(matrix4));
|
||||
}
|
||||
}
|
||||
|
||||
class CkSkewCommand extends CkPaintCommand {
|
||||
final double sx;
|
||||
final double sy;
|
||||
|
||||
CkSkewCommand(this.sx, this.sy);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.skew(sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipRectCommand extends CkPaintCommand {
|
||||
final ui.Rect rect;
|
||||
final ui.ClipOp clipOp;
|
||||
final bool doAntiAlias;
|
||||
|
||||
CkClipRectCommand(this.rect, this.clipOp, this.doAntiAlias);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipRect(
|
||||
toSkRect(rect),
|
||||
toSkClipOp(clipOp),
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawArcCommand extends CkPaintCommand {
|
||||
CkDrawArcCommand(this.oval, this.startAngle, this.sweepAngle, this.useCenter, this.paint);
|
||||
|
||||
final ui.Rect oval;
|
||||
final double startAngle;
|
||||
final double sweepAngle;
|
||||
final bool useCenter;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
const double toDegrees = 180 / math.pi;
|
||||
canvas.drawArc(
|
||||
toSkRect(oval),
|
||||
startAngle * toDegrees,
|
||||
sweepAngle * toDegrees,
|
||||
useCenter,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawAtlasCommand extends CkPaintCommand {
|
||||
CkDrawAtlasCommand(this.paint, this.atlas, this.rstTransforms, this.rects, this.colors, this.blendMode);
|
||||
|
||||
final CkPaint paint;
|
||||
final CkImage atlas;
|
||||
final Float32List rstTransforms;
|
||||
final Float32List rects;
|
||||
final List<Float32List>? colors;
|
||||
final ui.BlendMode blendMode;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawAtlas(
|
||||
atlas.skImage,
|
||||
rects,
|
||||
rstTransforms,
|
||||
paint.skiaObject,
|
||||
toSkBlendMode(blendMode),
|
||||
colors,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipRRectCommand extends CkPaintCommand {
|
||||
final ui.RRect rrect;
|
||||
final bool doAntiAlias;
|
||||
|
||||
CkClipRRectCommand(this.rrect, this.doAntiAlias);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipRRect(
|
||||
toSkRRect(rrect),
|
||||
_clipOpIntersect,
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkClipPathCommand extends CkPaintCommand {
|
||||
final CkPath path;
|
||||
final bool doAntiAlias;
|
||||
|
||||
CkClipPathCommand(this.path, this.doAntiAlias);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.clipPath(
|
||||
path.skiaObject,
|
||||
_clipOpIntersect,
|
||||
doAntiAlias,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawColorCommand extends CkPaintCommand {
|
||||
final ui.Color color;
|
||||
final ui.BlendMode blendMode;
|
||||
|
||||
CkDrawColorCommand(this.color, this.blendMode);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawColorInt(
|
||||
color.value,
|
||||
toSkBlendMode(blendMode),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawLineCommand extends CkPaintCommand {
|
||||
final ui.Offset p1;
|
||||
final ui.Offset p2;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawLineCommand(this.p1, this.p2, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawLine(
|
||||
p1.dx,
|
||||
p1.dy,
|
||||
p2.dx,
|
||||
p2.dy,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPaintCommand extends CkPaintCommand {
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawPaintCommand(this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPaint(paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawVerticesCommand extends CkPaintCommand {
|
||||
final CkVertices vertices;
|
||||
final ui.BlendMode blendMode;
|
||||
final CkPaint paint;
|
||||
CkDrawVerticesCommand(this.vertices, this.blendMode, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawVertices(
|
||||
vertices.skiaObject,
|
||||
toSkBlendMode(blendMode),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPointsCommand extends CkPaintCommand {
|
||||
final Float32List points;
|
||||
final ui.PointMode pointMode;
|
||||
final CkPaint paint;
|
||||
CkDrawPointsCommand(this.pointMode, this.points, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPoints(
|
||||
toSkPointMode(pointMode),
|
||||
points,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawRectCommand extends CkPaintCommand {
|
||||
final ui.Rect rect;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawRectCommand(this.rect, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawRect(toSkRect(rect), paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawRRectCommand extends CkPaintCommand {
|
||||
final ui.RRect rrect;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawRRectCommand(this.rrect, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawRRect(
|
||||
toSkRRect(rrect),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawDRRectCommand extends CkPaintCommand {
|
||||
final ui.RRect outer;
|
||||
final ui.RRect inner;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawDRRectCommand(this.outer, this.inner, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawDRRect(
|
||||
toSkRRect(outer),
|
||||
toSkRRect(inner),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawOvalCommand extends CkPaintCommand {
|
||||
final ui.Rect rect;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawOvalCommand(this.rect, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawOval(
|
||||
toSkRect(rect),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawCircleCommand extends CkPaintCommand {
|
||||
final ui.Offset c;
|
||||
final double radius;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawCircleCommand(this.c, this.radius, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawCircle(
|
||||
c.dx,
|
||||
c.dy,
|
||||
radius,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPathCommand extends CkPaintCommand {
|
||||
final CkPath path;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawPathCommand(this.path, this.paint);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPath(path.skiaObject, paint.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawShadowCommand extends CkPaintCommand {
|
||||
CkDrawShadowCommand(
|
||||
this.path, this.color, this.elevation, this.transparentOccluder);
|
||||
|
||||
final CkPath path;
|
||||
final ui.Color color;
|
||||
final double elevation;
|
||||
final bool transparentOccluder;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
drawSkShadow(canvas, path, color, elevation, transparentOccluder,
|
||||
ui.window.devicePixelRatio);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageCommand extends CkPaintCommand {
|
||||
final CkImage image;
|
||||
final ui.Offset offset;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawImageCommand(CkImage image, this.offset, this.paint)
|
||||
: this.image = image.clone();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawImage(
|
||||
image.skImage,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageRectCommand extends CkPaintCommand {
|
||||
final CkImage image;
|
||||
final ui.Rect src;
|
||||
final ui.Rect dst;
|
||||
final CkPaint paint;
|
||||
|
||||
CkDrawImageRectCommand(CkImage image, this.src, this.dst, this.paint)
|
||||
: this.image = image.clone();
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawImageRect(
|
||||
image.skImage,
|
||||
toSkRect(src),
|
||||
toSkRect(dst),
|
||||
paint.skiaObject,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawImageNineCommand extends CkPaintCommand {
|
||||
CkDrawImageNineCommand(CkImage image, this.center, this.dst, this.paint)
|
||||
: this.image = image.clone();
|
||||
|
||||
final CkImage image;
|
||||
final ui.Rect center;
|
||||
final ui.Rect dst;
|
||||
final CkPaint paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawImageNine(
|
||||
image.skImage,
|
||||
toSkRect(center),
|
||||
toSkRect(dst),
|
||||
paint.skiaObject,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
image.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawParagraphCommand extends CkPaintCommand {
|
||||
final CkParagraph paragraph;
|
||||
final ui.Offset offset;
|
||||
|
||||
CkDrawParagraphCommand(this.paragraph, this.offset);
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawParagraph(
|
||||
paragraph.skiaObject,
|
||||
offset.dx,
|
||||
offset.dy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkDrawPictureCommand extends CkPaintCommand {
|
||||
CkDrawPictureCommand(this.picture);
|
||||
|
||||
final CkPicture picture;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.drawPicture(picture.skiaObject);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerCommand extends CkPaintCommand {
|
||||
CkSaveLayerCommand(this.bounds, this.paint);
|
||||
|
||||
final ui.Rect bounds;
|
||||
final CkPaint? paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.saveLayer(
|
||||
paint?.skiaObject,
|
||||
toSkRect(bounds),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerWithoutBoundsCommand extends CkPaintCommand {
|
||||
CkSaveLayerWithoutBoundsCommand(this.paint);
|
||||
|
||||
final CkPaint? paint;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
canvas.saveLayer(
|
||||
paint?.skiaObject,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CkSaveLayerWithFilterCommand extends CkPaintCommand {
|
||||
CkSaveLayerWithFilterCommand(this.bounds, this.filter);
|
||||
|
||||
final ui.Rect bounds;
|
||||
final ui.ImageFilter filter;
|
||||
|
||||
@override
|
||||
void apply(SkCanvas canvas) {
|
||||
final _CkManagedSkImageFilterConvertible convertible = filter as _CkManagedSkImageFilterConvertible;
|
||||
return canvas.saveLayer(
|
||||
null,
|
||||
toSkRect(bounds),
|
||||
convertible._imageFilter.skiaObject,
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
'"recorder" must not already be associated with another Canvas.');
|
||||
}
|
||||
cullRect ??= ui.Rect.largest;
|
||||
final CkPictureRecorder skRecorder = recorder as CkPictureRecorder;
|
||||
return CanvasKitCanvas._(skRecorder.beginRecording(cullRect));
|
||||
final CkPictureRecorder ckRecorder = recorder as CkPictureRecorder;
|
||||
return CanvasKitCanvas._(ckRecorder.beginRecording(cullRect));
|
||||
}
|
||||
|
||||
CanvasKitCanvas._(this._canvas);
|
||||
@ -120,11 +120,7 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
// ignore: unnecessary_null_comparison
|
||||
assert(path != null); // path is checked on the engine side
|
||||
assert(doAntiAlias != null); // ignore: unnecessary_null_comparison
|
||||
_clipPath(path, doAntiAlias);
|
||||
}
|
||||
|
||||
void _clipPath(ui.Path path, bool doAntiAlias) {
|
||||
_canvas.clipPath(path, doAntiAlias);
|
||||
_canvas.clipPath(path as CkPath, doAntiAlias);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -243,11 +239,7 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
assert(image != null); // image is checked on the engine side
|
||||
assert(_offsetIsValid(p));
|
||||
assert(paint != null); // ignore: unnecessary_null_comparison
|
||||
_drawImage(image, p, paint);
|
||||
}
|
||||
|
||||
void _drawImage(ui.Image image, ui.Offset p, ui.Paint paint) {
|
||||
_canvas.drawImage(image, p, paint as CkPaint);
|
||||
_canvas.drawImage(image as CkImage, p, paint as CkPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -257,12 +249,7 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
assert(rectIsValid(src));
|
||||
assert(rectIsValid(dst));
|
||||
assert(paint != null); // ignore: unnecessary_null_comparison
|
||||
_drawImageRect(image, src, dst, paint);
|
||||
}
|
||||
|
||||
void _drawImageRect(
|
||||
ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) {
|
||||
_canvas.drawImageRect(image, src, dst, paint as CkPaint);
|
||||
_canvas.drawImageRect(image as CkImage, src, dst, paint as CkPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -278,7 +265,7 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
|
||||
void _drawImageNine(
|
||||
ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) {
|
||||
_canvas.drawImageNine(image, center, dst, paint as CkPaint);
|
||||
_canvas.drawImageNine(image as CkImage, center, dst, paint as CkPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -337,12 +324,7 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
assert(vertices != null); // vertices is checked on the engine side
|
||||
assert(paint != null); // ignore: unnecessary_null_comparison
|
||||
assert(blendMode != null); // ignore: unnecessary_null_comparison
|
||||
_drawVertices(vertices, blendMode, paint);
|
||||
}
|
||||
|
||||
void _drawVertices(
|
||||
ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) {
|
||||
_canvas.drawVertices(vertices, blendMode, paint as CkPaint);
|
||||
_canvas.drawVertices(vertices as CkVertices, blendMode, paint as CkPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -438,7 +420,14 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
List<Float32List>? colors,
|
||||
ui.BlendMode blendMode,
|
||||
) {
|
||||
_canvas.drawAtlasRaw(paint as CkPaint, atlas, rstTransforms, rects, colors, blendMode);
|
||||
_canvas.drawAtlasRaw(
|
||||
paint as CkPaint,
|
||||
atlas as CkImage,
|
||||
rstTransforms,
|
||||
rects,
|
||||
colors,
|
||||
blendMode,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -453,6 +442,6 @@ class CanvasKitCanvas implements ui.Canvas {
|
||||
|
||||
void _drawShadow(ui.Path path, ui.Color color, double elevation,
|
||||
bool transparentOccluder) {
|
||||
_canvas.drawShadow(path, color, elevation, transparentOccluder);
|
||||
_canvas.drawShadow(path as CkPath, color, elevation, transparentOccluder);
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,7 +336,7 @@ class HtmlViewEmbedder {
|
||||
_overlays[viewId]!.surface.acquireFrame(_frameSize);
|
||||
final CkCanvas canvas = frame.skiaCanvas;
|
||||
canvas.drawPicture(
|
||||
_pictureRecorders[viewId]!.endRecording() as CkPicture,
|
||||
_pictureRecorders[viewId]!.endRecording(),
|
||||
);
|
||||
frame.submit();
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ class CkImage implements ui.Image, StackTraceDebugger {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.Image clone() {
|
||||
CkImage clone() {
|
||||
assert(_debugCheckIsNotDisposed());
|
||||
return CkImage.cloneOf(box);
|
||||
}
|
||||
|
||||
@ -128,7 +128,7 @@ class BackdropFilterLayer extends ContainerLayer {
|
||||
/// A layer that clips its child layers by a given [Path].
|
||||
class ClipPathLayer extends ContainerLayer {
|
||||
/// The path used to clip child layers.
|
||||
final ui.Path _clipPath;
|
||||
final CkPath _clipPath;
|
||||
final ui.Clip _clipBehavior;
|
||||
|
||||
ClipPathLayer(this._clipPath, this._clipBehavior)
|
||||
@ -265,7 +265,7 @@ class OpacityLayer extends ContainerLayer implements ui.OpacityEngineLayer {
|
||||
void paint(PaintContext paintContext) {
|
||||
assert(needsPainting);
|
||||
|
||||
final ui.Paint paint = ui.Paint();
|
||||
final CkPaint paint = CkPaint();
|
||||
paint.color = ui.Color.fromARGB(_alpha, 0, 0, 0);
|
||||
|
||||
paintContext.internalNodesCanvas.save();
|
||||
@ -354,7 +354,7 @@ class ImageFilterLayer extends ContainerLayer implements ui.OpacityEngineLayer {
|
||||
@override
|
||||
void paint(PaintContext paintContext) {
|
||||
assert(needsPainting);
|
||||
final ui.Paint paint = ui.Paint();
|
||||
final CkPaint paint = CkPaint();
|
||||
paint.imageFilter = _filter;
|
||||
paintContext.internalNodesCanvas.saveLayer(paintBounds, paint);
|
||||
paintChildren(paintContext);
|
||||
@ -484,9 +484,9 @@ class PhysicalShapeLayer extends ContainerLayer
|
||||
_color.alpha != 0xff);
|
||||
}
|
||||
|
||||
final ui.Paint paint = ui.Paint()..color = _color;
|
||||
final CkPaint paint = CkPaint()..color = _color;
|
||||
if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) {
|
||||
paintContext.leafNodesCanvas!.drawPath(_path, paint as CkPaint);
|
||||
paintContext.leafNodesCanvas!.drawPath(_path, paint);
|
||||
}
|
||||
|
||||
final int saveCount = paintContext.internalNodesCanvas.save();
|
||||
@ -510,7 +510,7 @@ class PhysicalShapeLayer extends ContainerLayer
|
||||
// (https://github.com/flutter/flutter/issues/18057#issue-328003931)
|
||||
// using saveLayer, we have to call drawPaint instead of drawPath as
|
||||
// anti-aliased drawPath will always have such artifacts.
|
||||
paintContext.leafNodesCanvas!.drawPaint(paint as CkPaint);
|
||||
paintContext.leafNodesCanvas!.drawPaint(paint);
|
||||
}
|
||||
|
||||
paintChildren(paintContext);
|
||||
@ -522,7 +522,7 @@ class PhysicalShapeLayer extends ContainerLayer
|
||||
///
|
||||
/// The blur of the shadow is decided by the [elevation], and the
|
||||
/// shadow is painted with the given [color].
|
||||
static void drawShadow(CkCanvas canvas, ui.Path path, ui.Color color,
|
||||
static void drawShadow(CkCanvas canvas, CkPath path, ui.Color color,
|
||||
double elevation, bool transparentOccluder) {
|
||||
canvas.drawShadow(path, color, elevation, transparentOccluder);
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ class LayerSceneBuilder implements ui.SceneBuilder {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.Scene build() {
|
||||
LayerScene build() {
|
||||
return LayerScene(rootLayer);
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ class LayerSceneBuilder implements ui.SceneBuilder {
|
||||
ui.Clip clipBehavior = ui.Clip.antiAlias,
|
||||
ui.EngineLayer? oldLayer,
|
||||
}) {
|
||||
pushLayer(ClipPathLayer(path, clipBehavior));
|
||||
pushLayer(ClipPathLayer(path as CkPath, clipBehavior));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ part of engine;
|
||||
|
||||
/// A virtual canvas that applies operations to multiple canvases at once.
|
||||
class CkNWayCanvas {
|
||||
// TODO(yjbanov): make this List<CkCanvas>
|
||||
final List<CkCanvas?> _canvases = <CkCanvas?>[];
|
||||
|
||||
void addCanvas(CkCanvas? canvas) {
|
||||
@ -23,9 +24,9 @@ class CkNWayCanvas {
|
||||
}
|
||||
|
||||
/// Calls [saveLayer] on all canvases.
|
||||
void saveLayer(ui.Rect bounds, ui.Paint? paint) {
|
||||
void saveLayer(ui.Rect bounds, CkPaint? paint) {
|
||||
for (int i = 0; i < _canvases.length; i++) {
|
||||
_canvases[i]!.saveLayer(bounds, paint as CkPaint);
|
||||
_canvases[i]!.saveLayer(bounds, paint);
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,9 +66,9 @@ class CkNWayCanvas {
|
||||
}
|
||||
|
||||
/// Calls [clipPath] on all canvases.
|
||||
void clipPath(ui.Path? path, bool doAntiAlias) {
|
||||
void clipPath(CkPath path, bool doAntiAlias) {
|
||||
for (int i = 0; i < _canvases.length; i++) {
|
||||
_canvases[i]!.clipPath(path!, doAntiAlias);
|
||||
_canvases[i]!.clipPath(path, doAntiAlias);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,18 +5,23 @@
|
||||
// @dart = 2.12
|
||||
part of engine;
|
||||
|
||||
class CkPicture implements ui.Picture {
|
||||
final SkiaObject<SkPicture> skiaObject;
|
||||
class CkPicture extends ManagedSkiaObject<SkPicture> implements ui.Picture {
|
||||
final ui.Rect? cullRect;
|
||||
final CkPictureSnapshot? _snapshot;
|
||||
|
||||
CkPicture(SkPicture picture, this.cullRect)
|
||||
: skiaObject = SkPictureSkiaObject(picture);
|
||||
CkPicture(SkPicture picture, this.cullRect, this._snapshot) : super(picture) {
|
||||
assert(
|
||||
browserSupportsFinalizationRegistry && _snapshot == null || _snapshot != null,
|
||||
'If the browser does not support FinalizationRegistry (WeakRef), then we must have a picture snapshot to be able to resurrect it.',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
int get approximateBytesUsed => 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_snapshot?.dispose();
|
||||
skiaObject.delete();
|
||||
}
|
||||
|
||||
@ -24,18 +29,28 @@ class CkPicture implements ui.Picture {
|
||||
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);
|
||||
skCanvas.drawPicture(skiaObject);
|
||||
final SkImage skImage = skSurface.makeImageSnapshot();
|
||||
skSurface.dispose();
|
||||
return CkImage(skImage);
|
||||
}
|
||||
}
|
||||
|
||||
class SkPictureSkiaObject extends OneShotSkiaObject<SkPicture> {
|
||||
SkPictureSkiaObject(SkPicture picture) : super(picture);
|
||||
@override
|
||||
bool get isResurrectionExpensive => true;
|
||||
|
||||
@override
|
||||
SkPicture createDefault() {
|
||||
// The default object is supplied in the constructor.
|
||||
throw StateError('Unreachable code');
|
||||
}
|
||||
|
||||
@override
|
||||
SkPicture resurrect() {
|
||||
return _snapshot!.toPicture();
|
||||
}
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
rawSkiaObject.delete();
|
||||
rawSkiaObject?.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,13 +15,15 @@ class CkPictureRecorder implements ui.PictureRecorder {
|
||||
final SkPictureRecorder recorder = _skRecorder = SkPictureRecorder();
|
||||
final Float32List skRect = toSkRect(bounds);
|
||||
final SkCanvas skCanvas = recorder.beginRecording(skRect);
|
||||
return _recordingCanvas = CkCanvas(skCanvas);
|
||||
return _recordingCanvas = browserSupportsFinalizationRegistry
|
||||
? CkCanvas(skCanvas)
|
||||
: RecordingCkCanvas(skCanvas, bounds);
|
||||
}
|
||||
|
||||
CkCanvas? get recordingCanvas => _recordingCanvas;
|
||||
|
||||
@override
|
||||
ui.Picture endRecording() {
|
||||
CkPicture endRecording() {
|
||||
final SkPictureRecorder? recorder = _skRecorder;
|
||||
|
||||
if (recorder == null) {
|
||||
@ -31,7 +33,7 @@ class CkPictureRecorder implements ui.PictureRecorder {
|
||||
final SkPicture skPicture = recorder.finishRecordingAsPicture();
|
||||
recorder.delete();
|
||||
_skRecorder = null;
|
||||
return CkPicture(skPicture, _cullRect);
|
||||
return CkPicture(skPicture, _cullRect, _recordingCanvas!.pictureSnapshot);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -219,54 +219,6 @@ abstract class ManagedSkiaObject<T extends Object> extends SkiaObject<T> {
|
||||
bool get isResurrectionExpensive => false;
|
||||
}
|
||||
|
||||
// TODO(hterkelsen): [OneShotSkiaObject] is dangerous because it might delete
|
||||
// the underlying Skia object while the associated Dart object is still in
|
||||
// use. This issue discusses ways to address this:
|
||||
// https://github.com/flutter/flutter/issues/60401
|
||||
/// A [SkiaObject] which is deleted once and cannot be used again.
|
||||
///
|
||||
/// In browsers that support weak references we use feedback from the garbage
|
||||
/// collector to determine when it is safe to release the C++ object. Otherwise,
|
||||
/// we use an LRU cache (see [SkiaObjects.manageOneShot]).
|
||||
abstract class OneShotSkiaObject<T extends Object> extends SkiaObject<T> {
|
||||
/// Returns the current skia object as is without attempting to
|
||||
/// resurrect it.
|
||||
///
|
||||
/// If the returned value is `null`, the corresponding C++ object has
|
||||
/// been deleted.
|
||||
///
|
||||
/// Use this field instead of the [skiaObject] getter when implementing
|
||||
/// the [delete] method.
|
||||
T rawSkiaObject;
|
||||
|
||||
bool _isDeleted = false;
|
||||
|
||||
OneShotSkiaObject(T skObject) : this.rawSkiaObject = skObject {
|
||||
if (browserSupportsFinalizationRegistry) {
|
||||
Collector.instance.register(this, skObject as SkDeletable);
|
||||
} else {
|
||||
SkiaObjects.manageOneShot(this);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
T get skiaObject {
|
||||
if (browserSupportsFinalizationRegistry) {
|
||||
return rawSkiaObject;
|
||||
}
|
||||
if (_isDeleted) {
|
||||
throw StateError('Attempting to use a Skia object that has been freed.');
|
||||
}
|
||||
SkiaObjects.oneShotCache.markUsed(this);
|
||||
return rawSkiaObject;
|
||||
}
|
||||
|
||||
@override
|
||||
void didDelete() {
|
||||
_isDeleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface that classes wrapping [SkiaObjectBox] must implement.
|
||||
///
|
||||
/// Used to collect stack traces in debug mode.
|
||||
@ -468,10 +420,7 @@ class SkiaObjects {
|
||||
<ManagedSkiaObject>[];
|
||||
|
||||
@visibleForTesting
|
||||
static int maximumCacheSize = 8192;
|
||||
|
||||
@visibleForTesting
|
||||
static final SkiaObjectCache oneShotCache = SkiaObjectCache(maximumCacheSize);
|
||||
static int maximumCacheSize = 1024;
|
||||
|
||||
@visibleForTesting
|
||||
static final SkiaObjectCache expensiveCache =
|
||||
@ -499,15 +448,6 @@ class SkiaObjects {
|
||||
resurrectableObjects.add(object);
|
||||
}
|
||||
|
||||
/// Starts managing the lifecycle of a one-shot [object].
|
||||
///
|
||||
/// We should avoid deleting these whenever we can, since we won't
|
||||
/// be able to resurrect them.
|
||||
static void manageOneShot(OneShotSkiaObject object) {
|
||||
registerCleanupCallback();
|
||||
oneShotCache.add(object);
|
||||
}
|
||||
|
||||
/// Starts managing the lifecycle of a resurrectable object that is expensive.
|
||||
///
|
||||
/// Since it's expensive to resurrect, we shouldn't just delete it after every
|
||||
|
||||
@ -667,7 +667,7 @@ class CkParagraphBuilder implements ui.ParagraphBuilder {
|
||||
}
|
||||
|
||||
@override
|
||||
ui.Paragraph build() {
|
||||
CkParagraph build() {
|
||||
final builtParagraph = _buildCkParagraph();
|
||||
return CkParagraph(builtParagraph, _style, _commands);
|
||||
}
|
||||
|
||||
402
lib/web_ui/test/canvaskit/canvas_golden_test.dart
Normal file
402
lib/web_ui/test/canvaskit/canvas_golden_test.dart
Normal file
@ -0,0 +1,402 @@
|
||||
// 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.12
|
||||
import 'dart:html' as html;
|
||||
import 'dart:math' as 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.dart';
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
const ui.Rect region = const ui.Rect.fromLTRB(0, 0, 500, 250);
|
||||
|
||||
Future<void> matchPictureGolden(String goldenFile, CkPicture picture, { bool write = false }) async {
|
||||
final EnginePlatformDispatcher dispatcher = ui.window.platformDispatcher as EnginePlatformDispatcher;
|
||||
final LayerSceneBuilder sb = LayerSceneBuilder();
|
||||
sb.pushOffset(0, 0);
|
||||
sb.addPicture(ui.Offset.zero, picture);
|
||||
dispatcher.rasterizer!.draw(sb.build().layerTree);
|
||||
await matchGoldenFile(goldenFile, region: region, write: write);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
group('CkCanvas', () {
|
||||
setUpCanvasKitTest();
|
||||
|
||||
test('renders using non-recording canvas if weak refs are supported', () async {
|
||||
expect(browserSupportsFinalizationRegistry, isTrue,
|
||||
reason: 'This test specifically tests non-recording canvas, which '
|
||||
'only works if FinalizationRegistry is available.');
|
||||
final CkPictureRecorder recorder = CkPictureRecorder();
|
||||
final CkCanvas canvas = recorder.beginRecording(region);
|
||||
expect(canvas.runtimeType, CkCanvas);
|
||||
drawTestPicture(canvas);
|
||||
await matchPictureGolden('canvaskit_picture_original.png', recorder.endRecording());
|
||||
});
|
||||
|
||||
test('renders using a recording canvas if weak refs are not supported', () async {
|
||||
browserSupportsFinalizationRegistry = false;
|
||||
final CkPictureRecorder recorder = CkPictureRecorder();
|
||||
final CkCanvas canvas = recorder.beginRecording(region);
|
||||
expect(canvas, isA<RecordingCkCanvas>());
|
||||
drawTestPicture(canvas);
|
||||
|
||||
final CkPicture originalPicture = recorder.endRecording();
|
||||
await matchPictureGolden('canvaskit_picture_original.png', originalPicture);
|
||||
|
||||
final ByteData originalPixels = await (await originalPicture.toImage(50, 50)).toByteData() as ByteData;
|
||||
|
||||
// Test that a picture restored from a snapshot looks the same.
|
||||
final CkPictureSnapshot? snapshot = canvas.pictureSnapshot;
|
||||
expect(snapshot, isNotNull);
|
||||
final SkPicture restoredSkPicture = snapshot!.toPicture();
|
||||
expect(restoredSkPicture, isNotNull);
|
||||
final CkPicture restoredPicture = CkPicture(restoredSkPicture, ui.Rect.fromLTRB(0, 0, 50, 50), snapshot);
|
||||
final ByteData restoredPixels = await (await restoredPicture.toImage(50, 50)).toByteData() as ByteData;
|
||||
|
||||
await matchPictureGolden('canvaskit_picture_restored.png', restoredPicture);
|
||||
expect(restoredPixels.buffer.asUint8List(), originalPixels.buffer.asUint8List());
|
||||
});
|
||||
// TODO: https://github.com/flutter/flutter/issues/60040
|
||||
// TODO: https://github.com/flutter/flutter/issues/71520
|
||||
}, skip: isIosSafari || isFirefox);
|
||||
}
|
||||
|
||||
void drawTestPicture(CkCanvas canvas) {
|
||||
canvas.clear(ui.Color(0xFFFFFFF));
|
||||
|
||||
canvas.translate(10, 10);
|
||||
|
||||
// Row 1
|
||||
canvas.save();
|
||||
|
||||
canvas.save();
|
||||
canvas.clipRect(
|
||||
ui.Rect.fromLTRB(0, 0, 45, 45),
|
||||
ui.ClipOp.intersect,
|
||||
true,
|
||||
);
|
||||
canvas.clipRRect(
|
||||
ui.RRect.fromLTRBR(5, 5, 50, 50, ui.Radius.circular(8)),
|
||||
true,
|
||||
);
|
||||
canvas.clipPath(
|
||||
CkPath()
|
||||
..moveTo(5, 5)
|
||||
..lineTo(25, 5)
|
||||
..lineTo(45, 45)
|
||||
..lineTo(5, 45)
|
||||
..close(),
|
||||
true,
|
||||
);
|
||||
canvas.drawColor(ui.Color.fromARGB(255, 100, 100, 0), ui.BlendMode.srcOver);
|
||||
canvas.restore(); // remove clips
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawCircle(
|
||||
const ui.Offset(30, 25),
|
||||
15,
|
||||
CkPaint()..color = ui.Color(0xFF0000AA),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawArc(
|
||||
ui.Rect.fromLTRB(10, 20, 50, 40),
|
||||
math.pi / 4,
|
||||
3 * math.pi / 2,
|
||||
true,
|
||||
CkPaint()..color = ui.Color(0xFF00AA00),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawImage(
|
||||
generateTestImage(),
|
||||
const ui.Offset(20, 20),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
final ui.RSTransform transform = ui.RSTransform.fromComponents(
|
||||
rotation: 0,
|
||||
scale: 1,
|
||||
anchorX: 0,
|
||||
anchorY: 0,
|
||||
translateX: 0,
|
||||
translateY: 0,
|
||||
);
|
||||
canvas.drawAtlasRaw(
|
||||
CkPaint(),
|
||||
generateTestImage(),
|
||||
Float32List(4)
|
||||
..[0] = transform.scos
|
||||
..[1] = transform.ssin
|
||||
..[2] = transform.tx + 20
|
||||
..[3] = transform.ty + 20,
|
||||
Float32List(4)
|
||||
..[0] = 0
|
||||
..[1] = 0
|
||||
..[2] = 15
|
||||
..[3] = 15,
|
||||
[Float32List(4)],
|
||||
ui.BlendMode.srcOver,
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawDRRect(
|
||||
ui.RRect.fromLTRBR(0, 0, 40, 30, ui.Radius.elliptical(16, 8)),
|
||||
ui.RRect.fromLTRBR(10, 10, 30, 20, ui.Radius.elliptical(4, 8)),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawImageRect(
|
||||
generateTestImage(),
|
||||
ui.Rect.fromLTRB(0, 0, 15, 15),
|
||||
ui.Rect.fromLTRB(10, 10, 40, 40),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawImageNine(
|
||||
generateTestImage(),
|
||||
ui.Rect.fromLTRB(5, 5, 15, 15),
|
||||
ui.Rect.fromLTRB(10, 10, 50, 40),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
|
||||
// Row 2
|
||||
canvas.translate(0, 60);
|
||||
canvas.save();
|
||||
|
||||
canvas.drawLine(ui.Offset(0, 0), ui.Offset(40, 30), CkPaint());
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawOval(
|
||||
ui.Rect.fromLTRB(0, 0, 40, 30),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.save();
|
||||
canvas.clipRect(ui.Rect.fromLTRB(0, 0, 50, 30), ui.ClipOp.intersect, true);
|
||||
canvas.drawPaint(CkPaint()..color = ui.Color(0xFF6688AA));
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
{
|
||||
final CkPictureRecorder otherRecorder = CkPictureRecorder();
|
||||
final CkCanvas otherCanvas =
|
||||
otherRecorder.beginRecording(ui.Rect.fromLTRB(0, 0, 40, 20));
|
||||
otherCanvas.drawCircle(
|
||||
ui.Offset(30, 15),
|
||||
10,
|
||||
CkPaint()..color = ui.Color(0xFFAABBCC),
|
||||
);
|
||||
canvas.drawPicture(otherRecorder.endRecording());
|
||||
}
|
||||
|
||||
canvas.translate(60, 0);
|
||||
// TODO(yjbanov): CanvasKit.drawPoints is currently broken
|
||||
// https://github.com/flutter/flutter/issues/71489
|
||||
// But keeping this anyway as it's a good test-case that
|
||||
// will ensure it's fixed when we have the fix.
|
||||
canvas.drawPoints(
|
||||
CkPaint()
|
||||
..color = ui.Color(0xFF0000FF)
|
||||
..strokeWidth = 5
|
||||
..strokeCap = ui.StrokeCap.round,
|
||||
ui.PointMode.polygon,
|
||||
offsetListToFloat32List(<ui.Offset>[
|
||||
ui.Offset(10, 10),
|
||||
ui.Offset(20, 10),
|
||||
ui.Offset(30, 20),
|
||||
ui.Offset(40, 20)
|
||||
]),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawRRect(
|
||||
ui.RRect.fromLTRBR(0, 0, 40, 30, ui.Radius.circular(10)),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawRect(
|
||||
ui.Rect.fromLTRB(0, 0, 40, 30),
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawShadow(
|
||||
CkPath()
|
||||
..addRect(ui.Rect.fromLTRB(0, 0, 40, 30)),
|
||||
ui.Color(0xFF00FF00),
|
||||
4,
|
||||
true,
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
|
||||
// Row 3
|
||||
canvas.translate(0, 60);
|
||||
canvas.save();
|
||||
|
||||
canvas.drawVertices(
|
||||
CkVertices(
|
||||
ui.VertexMode.triangleFan,
|
||||
<ui.Offset>[
|
||||
ui.Offset(10, 30),
|
||||
ui.Offset(30, 50),
|
||||
ui.Offset(10, 60),
|
||||
],
|
||||
),
|
||||
ui.BlendMode.srcOver,
|
||||
CkPaint(),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
final int restorePoint = canvas.save();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
canvas.save();
|
||||
canvas.translate(10, 10);
|
||||
canvas.drawCircle(ui.Offset.zero, 5, CkPaint());
|
||||
}
|
||||
canvas.restoreToCount(restorePoint);
|
||||
canvas.drawCircle(ui.Offset.zero, 7, CkPaint()..color = ui.Color(0xFFFF0000));
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawLine(ui.Offset.zero, ui.Offset(30, 30), CkPaint());
|
||||
canvas.save();
|
||||
canvas.rotate(-math.pi / 8);
|
||||
canvas.drawLine(ui.Offset.zero, ui.Offset(30, 30), CkPaint());
|
||||
canvas.drawCircle(ui.Offset(30, 30), 7, CkPaint()..color = ui.Color(0xFF00AA00));
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
final CkPaint thickStroke = CkPaint()
|
||||
..style = ui.PaintingStyle.stroke
|
||||
..strokeWidth = 20;
|
||||
final CkPaint semitransparent = CkPaint()..color = ui.Color(0x66000000);
|
||||
|
||||
canvas.saveLayer(region, semitransparent);
|
||||
canvas.drawLine(ui.Offset(10, 10), ui.Offset(50, 50), thickStroke);
|
||||
canvas.drawLine(ui.Offset(50, 10), ui.Offset(10, 50), thickStroke);
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.saveLayerWithoutBounds(semitransparent);
|
||||
canvas.drawLine(ui.Offset(10, 10), ui.Offset(50, 50), thickStroke);
|
||||
canvas.drawLine(ui.Offset(50, 10), ui.Offset(10, 50), thickStroke);
|
||||
canvas.restore();
|
||||
|
||||
// To test saveLayerWithFilter we draw three circles with only the middle one
|
||||
// blurred using the layer image filter.
|
||||
canvas.translate(60, 0);
|
||||
canvas.saveLayer(region, CkPaint());
|
||||
canvas.drawCircle(ui.Offset(30, 30), 10, CkPaint());
|
||||
{
|
||||
canvas.saveLayerWithFilter(region, ui.ImageFilter.blur(sigmaX: 5, sigmaY: 10));
|
||||
canvas.drawCircle(ui.Offset(10, 10), 10, CkPaint());
|
||||
canvas.drawCircle(ui.Offset(50, 50), 10, CkPaint());
|
||||
canvas.restore();
|
||||
}
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.save();
|
||||
canvas.translate(30, 30);
|
||||
canvas.scale(2, 1.5);
|
||||
canvas.drawCircle(ui.Offset.zero, 10, CkPaint());
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.save();
|
||||
canvas.translate(30, 30);
|
||||
canvas.skew(2, 1.5);
|
||||
canvas.drawRect(ui.Rect.fromLTRB(-10, -10, 10, 10), CkPaint());
|
||||
canvas.restore();
|
||||
|
||||
canvas.restore();
|
||||
|
||||
// Row 4
|
||||
canvas.translate(0, 60);
|
||||
canvas.save();
|
||||
|
||||
canvas.save();
|
||||
final Matrix4 matrix = Matrix4.identity();
|
||||
matrix.translate(30, 30);
|
||||
matrix.scale(2, 1.5);
|
||||
canvas.transform(matrix.storage);
|
||||
canvas.drawCircle(ui.Offset.zero, 10, CkPaint());
|
||||
canvas.restore();
|
||||
|
||||
canvas.translate(60, 0);
|
||||
final CkParagraphBuilder pb = CkParagraphBuilder(CkParagraphStyle(
|
||||
fontFamily: 'Roboto',
|
||||
fontStyle: ui.FontStyle.normal,
|
||||
fontWeight: ui.FontWeight.normal,
|
||||
fontSize: 18,
|
||||
));
|
||||
pb.pushStyle(CkTextStyle(
|
||||
color: ui.Color(0xFF0000AA),
|
||||
));
|
||||
pb.addText('Hello');
|
||||
pb.pop();
|
||||
final CkParagraph p = pb.build();
|
||||
p.layout(ui.ParagraphConstraints(width: 1000));
|
||||
canvas.drawParagraph(
|
||||
p,
|
||||
ui.Offset(10, 20),
|
||||
);
|
||||
|
||||
canvas.translate(60, 0);
|
||||
canvas.drawPath(
|
||||
CkPath()
|
||||
..moveTo(30, 20)
|
||||
..lineTo(50, 50)
|
||||
..lineTo(10, 50)
|
||||
..close(),
|
||||
CkPaint()..color = ui.Color(0xFF0000AA),
|
||||
);
|
||||
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
CkImage generateTestImage() {
|
||||
final html.CanvasElement canvas = html.CanvasElement()
|
||||
..width = 20
|
||||
..height = 20;
|
||||
final html.CanvasRenderingContext2D ctx = canvas.context2D;
|
||||
ctx.fillStyle = '#FF0000';
|
||||
ctx.fillRect(0, 0, 10, 10);
|
||||
ctx.fillStyle = '#00FF00';
|
||||
ctx.fillRect(0, 10, 10, 10);
|
||||
ctx.fillStyle = '#0000FF';
|
||||
ctx.fillRect(10, 0, 10, 10);
|
||||
ctx.fillStyle = '#FF00FF';
|
||||
ctx.fillRect(10, 10, 10, 10);
|
||||
final Uint8List imageData = ctx.getImageData(0, 0, 20, 20).data.buffer.asUint8List();
|
||||
final SkImage skImage = canvasKit.MakeImage(
|
||||
imageData,
|
||||
20,
|
||||
20,
|
||||
canvasKit.AlphaType.Premul,
|
||||
canvasKit.ColorType.RGBA_8888,
|
||||
SkColorSpaceSRGB,
|
||||
);
|
||||
return CkImage(skImage);
|
||||
}
|
||||
@ -1169,6 +1169,10 @@ void _canvasTests() {
|
||||
});
|
||||
|
||||
test('toImage.toByteData', () async {
|
||||
// Pretend that FinalizationRegistry is supported, so we can run this
|
||||
// test in older browsers (the test will use a TestCollector instead of
|
||||
// ProductionCollector)
|
||||
browserSupportsFinalizationRegistry = true;
|
||||
final SkPictureRecorder otherRecorder = SkPictureRecorder();
|
||||
final SkCanvas otherCanvas =
|
||||
otherRecorder.beginRecording(Float32List.fromList([0, 0, 1, 1]));
|
||||
@ -1177,7 +1181,7 @@ void _canvasTests() {
|
||||
SkPaint(),
|
||||
);
|
||||
final CkPicture picture =
|
||||
CkPicture(otherRecorder.finishRecordingAsPicture(), null);
|
||||
CkPicture(otherRecorder.finishRecordingAsPicture(), null, null);
|
||||
final CkImage image = await picture.toImage(1, 1);
|
||||
final ByteData rawData =
|
||||
await image.toByteData(format: ui.ImageByteFormat.rawRgba);
|
||||
|
||||
@ -7,11 +7,14 @@ import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
/// Whether we are running on iOS Safari.
|
||||
/// Whether the current browser is Safari on iOS.
|
||||
// TODO: https://github.com/flutter/flutter/issues/60040
|
||||
bool get isIosSafari => browserEngine == BrowserEngine.webkit &&
|
||||
operatingSystem == OperatingSystem.iOs;
|
||||
|
||||
/// Whether the current browser is Firefox.
|
||||
bool get isFirefox => browserEngine == BrowserEngine.firefox;
|
||||
|
||||
/// Used in tests instead of [ProductionCollector] to control Skia object
|
||||
/// collection explicitly, and to prevent leaks across tests.
|
||||
///
|
||||
@ -24,7 +27,7 @@ void setUpCanvasKitTest() {
|
||||
expect(useCanvasKit, true,
|
||||
reason: 'This test must run in CanvasKit mode.');
|
||||
debugResetBrowserSupportsFinalizationRegistry();
|
||||
await ui.webOnlyInitializePlatform();
|
||||
await ui.webOnlyInitializePlatform(assetManager: WebOnlyMockAssetManager());
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
|
||||
@ -117,37 +117,6 @@ void _tests() {
|
||||
});
|
||||
});
|
||||
|
||||
group(OneShotSkiaObject, () {
|
||||
test('is added to SkiaObjects cache', () {
|
||||
TestOneShotSkiaObject.deleteCount = 0;
|
||||
OneShotSkiaObject object1 = TestOneShotSkiaObject();
|
||||
expect(SkiaObjects.oneShotCache.length, 1);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object1), isTrue);
|
||||
|
||||
OneShotSkiaObject object2 = TestOneShotSkiaObject();
|
||||
expect(SkiaObjects.oneShotCache.length, 2);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object2), isTrue);
|
||||
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(SkiaObjects.oneShotCache.length, 2);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object1), isTrue);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object2), isTrue);
|
||||
|
||||
// Add 3 more objects to the cache to overflow it.
|
||||
TestOneShotSkiaObject();
|
||||
TestOneShotSkiaObject();
|
||||
TestOneShotSkiaObject();
|
||||
expect(SkiaObjects.oneShotCache.length, 5);
|
||||
expect(SkiaObjects.cachesToResize.length, 1);
|
||||
|
||||
SkiaObjects.postFrameCleanUp();
|
||||
expect(TestOneShotSkiaObject.deleteCount, 2);
|
||||
expect(SkiaObjects.oneShotCache.length, 3);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object1), isFalse);
|
||||
expect(SkiaObjects.oneShotCache.debugContains(object2), isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group(SkiaObjectBox, () {
|
||||
test('Records stack traces and respects refcounts', () async {
|
||||
TestSkDeletable.deleteCount = 0;
|
||||
@ -297,27 +266,6 @@ class TestSkDeletable implements SkDeletable {
|
||||
JsConstructor get constructor => TestJsConstructor('TestSkDeletable');
|
||||
}
|
||||
|
||||
class TestOneShotSkiaObject extends OneShotSkiaObject<SkPaint> implements SkDeletable {
|
||||
static int deleteCount = 0;
|
||||
|
||||
TestOneShotSkiaObject() : super(SkPaint());
|
||||
|
||||
@override
|
||||
bool isDeleted() => _isDeleted;
|
||||
bool _isDeleted = false;
|
||||
|
||||
@override
|
||||
void delete() {
|
||||
expect(_isDeleted, isFalse,
|
||||
reason: 'CanvasKit does not allow deleting the same object more than once.');
|
||||
rawSkiaObject?.delete();
|
||||
deleteCount++;
|
||||
}
|
||||
|
||||
@override
|
||||
JsConstructor get constructor => TestJsConstructor('TestOneShotSkiaObject');
|
||||
}
|
||||
|
||||
class TestJsConstructor implements JsConstructor{
|
||||
TestJsConstructor(this.name);
|
||||
|
||||
|
||||
@ -2,10 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// @dart = 2.12
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:html' as html;
|
||||
|
||||
// ignore: implementation_imports
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart';
|
||||
|
||||
@ -18,7 +21,7 @@ Future<dynamic> _callScreenshotServer(dynamic requestData) async {
|
||||
sendData: json.encode(requestData),
|
||||
);
|
||||
|
||||
return json.decode(request.responseText);
|
||||
return json.decode(request.responseText!);
|
||||
}
|
||||
|
||||
/// How to compare pixels within the image.
|
||||
@ -50,8 +53,8 @@ enum PixelComparison {
|
||||
/// [pixelComparison] determines the algorithm used to compare pixels. Uses
|
||||
/// fuzzy comparison by default.
|
||||
Future<void> matchGoldenFile(String filename,
|
||||
{bool write = false, Rect region = null, double maxDiffRatePercent = null, PixelComparison pixelComparison = PixelComparison.fuzzy}) async {
|
||||
Map<String, dynamic> serverParams = <String, dynamic>{
|
||||
{bool write = false, Rect? region, double? maxDiffRatePercent, PixelComparison pixelComparison = PixelComparison.fuzzy}) async {
|
||||
final Map<String, dynamic> serverParams = <String, dynamic>{
|
||||
'filename': filename,
|
||||
'write': write,
|
||||
'region': region == null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user