Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Fix CkBrowserImageDecoder conversion of images to ImageByteFormat.rawRgba and rawStraightRgba #52089

Merged
merged 3 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,26 @@ Future<ByteData> 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.
return pixels.asByteData();
}

/// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be inlined too so that the compiler specializes the function for possible values of isBgrx, true and false.

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.
Expand All @@ -122,6 +127,34 @@ 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.
int _premultiply(int value, int alpha) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to inline this function using @pragma('dart2js:tryInline') as images can be pretty big and could take advantage of faster pixel processing logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the inlining annotation

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;
}
}

Expand All @@ -133,7 +166,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<ByteBuffer> readVideoFramePixelsUnmodified(VideoFrame videoFrame) async {
Expand Down
18 changes: 18 additions & 0 deletions lib/web_ui/test/canvaskit/image_golden_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(format: ui.ImageByteFormat.rawRgba);
expect(data!.buffer.asUint8List(),
[0x11, 0x22, 0x33, 0x80, 0x11, 0x22, 0x33, 0x80,
0x11, 0x22, 0x33, 0x80, 0x11, 0x22, 0x33, 0x80]);
});
}

Future<void> expectFrameData(ui.FrameInfo frame, List<int> data) async {
Expand Down
18 changes: 18 additions & 0 deletions lib/web_ui/test/canvaskit/test_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,21 @@ final Uint8List kAnimatedGif = Uint8List.fromList(<int> [
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(<int> [
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
]);