From 45e2b41e5ae5563afbfaca45d873451a6962d8a3 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 28 Aug 2023 16:08:46 -0700 Subject: [PATCH] [skwasm] encode PNGs using browser APIs (#45187) This allows us to remove libpng from skia entirely, which saves us about 25kb brotli compressed. Note, this should not change any functionality. The existing functionality is covered by the unit tests here: https://github.com/flutter/engine/blame/bd2132a0814f6df95471e97f7d5efaca515506bd/lib/web_ui/test/ui/image_golden_test.dart#L197-L197 --- lib/web_ui/lib/src/engine/dom.dart | 5 +++ .../src/engine/skwasm/skwasm_impl/image.dart | 28 +++++++++++++- lib/web_ui/skwasm/surface.cpp | 37 +++++++++---------- third_party/canvaskit/BUILD.gn | 4 ++ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 7f8394bc8ffe2..4a5ac6e540dea 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -1430,6 +1430,7 @@ class DomImageBitmap {} extension DomImageBitmapExtension on DomImageBitmap { external JSNumber get width; external JSNumber get height; + external void close(); } @JS() @@ -2265,6 +2266,10 @@ class DomBlob { external factory DomBlob(JSArray parts); } +extension DomBlobExtension on DomBlob { + external JSPromise arrayBuffer(); +} + DomBlob createDomBlob(List parts) => DomBlob(parts.toJSAnyShallow as JSArray); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart index a3940ab788a6c..ee32ffd987350 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:ffi'; +import 'dart:js_interop'; import 'dart:typed_data'; import 'package:ui/src/engine.dart'; @@ -55,8 +56,31 @@ class SkwasmImage extends SkwasmObjectWrapper implements ui.Image { @override Future toByteData( - {ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) { - return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format); + {ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) async { + if (format == ui.ImageByteFormat.png) { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + canvas.drawImage(this, ui.Offset.zero, ui.Paint()); + final DomImageBitmap bitmap = + await (renderer as SkwasmRenderer).surface.renderPicture( + recorder.endRecording() as SkwasmPicture, + ); + final DomOffscreenCanvas offscreenCanvas = + createDomOffscreenCanvas(bitmap.width.toDartInt, bitmap.height.toDartInt); + final DomCanvasRenderingContextBitmapRenderer context = + offscreenCanvas.getContext('bitmaprenderer')! as DomCanvasRenderingContextBitmapRenderer; + context.transferFromImageBitmap(bitmap); + final DomBlob blob = await offscreenCanvas.convertToBlob(); + final JSArrayBuffer arrayBuffer = (await blob.arrayBuffer().toDart)! as JSArrayBuffer; + + // Zero out the contents of the canvas so that resources can be reclaimed + // by the browser. + offscreenCanvas.width = 0; + offscreenCanvas.height = 0; + return ByteData.view(arrayBuffer.toDart); + } else { + return (renderer as SkwasmRenderer).surface.rasterizeImage(this, format); + } } @override diff --git a/lib/web_ui/skwasm/surface.cpp b/lib/web_ui/skwasm/surface.cpp index 6ea9da7be9d7a..160037e4313b3 100644 --- a/lib/web_ui/skwasm/surface.cpp +++ b/lib/web_ui/skwasm/surface.cpp @@ -156,26 +156,25 @@ void Surface::_renderPicture(const SkPicture* picture, uint32_t callbackId) { void Surface::_rasterizeImage(SkImage* image, ImageByteFormat format, uint32_t callbackId) { + // We handle PNG encoding with browser APIs so that we can omit libpng from + // skia to save binary size. + assert(format != ImageByteFormat::png); sk_sp 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(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; - } + 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(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_async_run_in_main_runtime_thread( EM_FUNC_SIG_VIII, fOnRasterizeComplete, this, data.release(), callbackId); diff --git a/third_party/canvaskit/BUILD.gn b/third_party/canvaskit/BUILD.gn index fd2836e349b19..691e09ecbf660 100644 --- a/third_party/canvaskit/BUILD.gn +++ b/third_party/canvaskit/BUILD.gn @@ -81,6 +81,10 @@ wasm_toolchain("skwasm") { skia_use_libpng_decode = false skia_use_libwebp_decode = false + # We use OffscreenCanvas to produce PNG data instead of skia + skia_use_no_png_encode = true + skia_use_libpng_encode = false + # skwasm is multithreaded wasm_use_pthreads = true wasm_prioritize_size = true