diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart index 054e7640ef9bf..f2b213c731cd2 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart @@ -96,10 +96,16 @@ Future readPixelsFromVideoFrame(VideoFrame videoFrame, ui.ImageByteFor // At this point we know we want to read unencoded pixels, and that the video // frame is _not_ using the same format as the requested one. - final bool isBgrFrame = videoFrame.format == 'BGRA' || videoFrame.format == 'BGRX'; - if (format == ui.ImageByteFormat.rawRgba && isBgrFrame) { - _bgrToRgba(pixels); - return pixels.asByteData(); + final bool isBgrx = videoFrame.format == 'BGRX'; + final bool isBgrFrame = videoFrame.format == 'BGRA' || isBgrx; + if (isBgrFrame) { + if (format == ui.ImageByteFormat.rawStraightRgba || isBgrx) { + _bgrToStraightRgba(pixels, isBgrx); + return pixels.asByteData(); + } else if (format == ui.ImageByteFormat.rawRgba) { + _bgrToRawRgba(pixels); + return pixels.asByteData(); + } } // Last resort, just return the original pixels. @@ -107,10 +113,9 @@ Future readPixelsFromVideoFrame(VideoFrame videoFrame, ui.ImageByteFor } /// Mutates the [pixels], converting them from BGRX/BGRA to RGBA. -void _bgrToRgba(ByteBuffer pixels) { - final int pixelCount = pixels.lengthInBytes ~/ 4; +void _bgrToStraightRgba(ByteBuffer pixels, bool isBgrx) { final Uint8List pixelBytes = pixels.asUint8List(); - for (int i = 0; i < pixelCount; i += 4) { + for (int i = 0; i < pixelBytes.length; i += 4) { // It seems even in little-endian machines the BGR_ pixels are encoded as // big-endian, i.e. the blue byte is written into the lowest byte in the // memory address space. @@ -122,6 +127,35 @@ void _bgrToRgba(ByteBuffer pixels) { // codecs that do something different. pixelBytes[i] = r; pixelBytes[i + 2] = b; + if (isBgrx) { + pixelBytes[i + 3] = 255; + } + } +} + +/// Based on Chromium's SetRGBAPremultiply. +@pragma('dart2js:tryInline') +int _premultiply(int value, int alpha) { + if (alpha == 255) { + return value; + } + const int kRoundFractionControl = 257 * 128; + return (value * alpha * 257 + kRoundFractionControl) >> 16; +} + +/// Mutates the [pixels], converting them from BGRX/BGRA to RGBA with +/// premultiplied alpha. +void _bgrToRawRgba(ByteBuffer pixels) { + final Uint8List pixelBytes = pixels.asUint8List(); + for (int i = 0; i < pixelBytes.length; i += 4) { + final int a = pixelBytes[i + 3]; + final int r = _premultiply(pixelBytes[i + 2], a); + final int g = _premultiply(pixelBytes[i + 1], a); + final int b = _premultiply(pixelBytes[i], a); + + pixelBytes[i] = r; + pixelBytes[i + 1] = g; + pixelBytes[i + 2] = b; } } @@ -133,7 +167,7 @@ bool _shouldReadPixelsUnmodified(VideoFrame videoFrame, ui.ImageByteFormat forma // Do not convert if the requested format is RGBA and the video frame is // encoded as either RGBA or RGBX. final bool isRgbFrame = videoFrame.format == 'RGBA' || videoFrame.format == 'RGBX'; - return format == ui.ImageByteFormat.rawRgba && isRgbFrame; + return format == ui.ImageByteFormat.rawStraightRgba && isRgbFrame; } Future readVideoFramePixelsUnmodified(VideoFrame videoFrame) async { diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index 824eeca9d7c19..6d3ee7e298859 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -895,6 +895,24 @@ void _testCkBrowserImageDecoder() { debugRestoreWebDecoderExpireDuration(); }); + + test('ImageDecoder toByteData(translucent PNG)', () async { + final CkBrowserImageDecoder image = await CkBrowserImageDecoder.create( + data: kTranslucentPng, + debugSource: 'test', + ); + final ui.FrameInfo frame = await image.getNextFrame(); + + ByteData? data = await frame.image.toByteData(format: ui.ImageByteFormat.rawStraightRgba); + expect(data!.buffer.asUint8List(), + [0x22, 0x44, 0x66, 0x80, 0x22, 0x44, 0x66, 0x80, + 0x22, 0x44, 0x66, 0x80, 0x22, 0x44, 0x66, 0x80]); + + data = await frame.image.toByteData(); + expect(data!.buffer.asUint8List(), + [0x11, 0x22, 0x33, 0x80, 0x11, 0x22, 0x33, 0x80, + 0x11, 0x22, 0x33, 0x80, 0x11, 0x22, 0x33, 0x80]); + }); } Future expectFrameData(ui.FrameInfo frame, List data) async { diff --git a/lib/web_ui/test/canvaskit/test_data.dart b/lib/web_ui/test/canvaskit/test_data.dart index 2e53c7366c16d..8a131cde1dd51 100644 --- a/lib/web_ui/test/canvaskit/test_data.dart +++ b/lib/web_ui/test/canvaskit/test_data.dart @@ -35,3 +35,21 @@ final Uint8List kAnimatedGif = Uint8List.fromList( [ 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b, ]); + +/// A 2x2 translucent PNG. +final Uint8List kTranslucentPng = Uint8List.fromList( [ + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, + 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x01, 0x03, + 0x00, 0x00, 0x00, 0x48, 0x78, 0x9f, 0x67, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, + 0x52, 0x4d, 0x00, 0x00, 0x7a, 0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, + 0x00, 0x00, 0x00, 0x80, 0xe8, 0x00, 0x00, 0x75, 0x30, 0x00, 0x00, 0xea, 0x60, + 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, + 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x22, 0x44, 0x66, 0xff, 0xff, 0xff, + 0x5c, 0x83, 0x6d, 0xb6, 0x00, 0x00, 0x00, 0x01, 0x74, 0x52, 0x4e, 0x53, 0x80, + 0xad, 0x5e, 0x5b, 0x46, 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x01, + 0xff, 0x02, 0x2d, 0xde, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 0x45, 0x07, + 0xe8, 0x04, 0x0c, 0x15, 0x16, 0x21, 0xc3, 0x89, 0xee, 0x25, 0x00, 0x00, 0x00, + 0x0c, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x01, 0x27, 0x34, 0x27, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x49, + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 +]);