Skip to content

Commit

Permalink
[skwasm] encode PNGs using browser APIs (flutter#45187)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
eyebrowsoffire authored Aug 28, 2023
1 parent 86ec60f commit 45e2b41
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 21 deletions.
5 changes: 5 additions & 0 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,7 @@ class DomImageBitmap {}
extension DomImageBitmapExtension on DomImageBitmap {
external JSNumber get width;
external JSNumber get height;
external void close();
}

@JS()
Expand Down Expand Up @@ -2265,6 +2266,10 @@ class DomBlob {
external factory DomBlob(JSArray parts);
}

extension DomBlobExtension on DomBlob {
external JSPromise arrayBuffer();
}

DomBlob createDomBlob(List<Object?> parts) =>
DomBlob(parts.toJSAnyShallow as JSArray);

Expand Down
28 changes: 26 additions & 2 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -55,8 +56,31 @@ class SkwasmImage extends SkwasmObjectWrapper<RawImage> implements ui.Image {

@override
Future<ByteData?> 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
Expand Down
37 changes: 18 additions & 19 deletions lib/web_ui/skwasm/surface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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;
}
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_async_run_in_main_runtime_thread(
EM_FUNC_SIG_VIII, fOnRasterizeComplete, this, data.release(), callbackId);
Expand Down
4 changes: 4 additions & 0 deletions third_party/canvaskit/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 45e2b41

Please sign in to comment.