Skip to content

Commit

Permalink
[WebCodecs] Ensure orientation is set on YUV image decodes.
Browse files Browse the repository at this point in the history
While the RGB decoding path was always setting the orientation, it
was overlooked on the YUV decoding path. As such, this change adds
code to set it and tests to ensure it doesn't regress.

Fixed: 1342672
Change-Id: I7b8f7f05502a040525cc84fc60f2dd12a42211c0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3751369
Reviewed-by: Thomas Guilbert <tguilbert@chromium.org>
Commit-Queue: Thomas Guilbert <tguilbert@chromium.org>
Auto-Submit: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1021792}
  • Loading branch information
dalecurtis authored and chromium-wpt-export-bot committed Jul 7, 2022
1 parent f15017e commit 8016ebe
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 12 deletions.
5 changes: 4 additions & 1 deletion webcodecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ exiftool -Orientation=1 -n four-colors.jpg

### four-colors-limited-range-420-8bpc.jpg
Used [Sqoosh.app](https://squoosh.app/) with MozJPEG compression and YUV
channels.
channels. exiftool was then used to add an orientation marker.
```
exiftool -Orientation=1 -n four-colors-limited-range-420-8bpc.jpg
```

### four-colors.mp4
Used a [custom tool](https://storage.googleapis.com/dalecurtis/avif2mp4.html) to convert four-colors.avif into a .mp4 file.
Expand Down
Binary file modified webcodecs/four-colors-limited-range-420-8bpc.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions webcodecs/image-decoder-image-orientation-none.https.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!DOCTYPE html>
<title>Test ImageDecoder outputs to a image-orientation: none canvas.</title>
<canvas style="image-orientation: none"></canvas>
<script src="/resources/testharness.js"></script>
Expand Down Expand Up @@ -43,4 +44,45 @@
return testFourColorDecodeWithExifOrientation(
8, document.querySelector('canvas'));
}, 'Test JPEG w/ EXIF orientation left-bottom on canvas w/o orientation.');

// YUV tests
promise_test(t => {
return testFourColorDecodeWithExifOrientation(
1, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation top-left on canvas w/o orientation');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
2, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation top-right on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
3, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation bottom-right on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
4, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation bottom-left on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
5, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation left-top on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
6, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation right-top on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
7, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation right-bottom on canvas w/o orientation.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(
8, document.querySelector('canvas'), /*useYuv=*/true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation left-bottom on canvas w/o orientation.');
</script>
39 changes: 28 additions & 11 deletions webcodecs/image-decoder-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const kRed = 0xFF0000FF;
const kBlue = 0x0000FFFF;
const kGreen = 0x00FF00FF;

function getColorName (color) {
function getColorName(color) {
switch (color) {
case kYellow:
return "Yellow";
Expand All @@ -17,8 +17,20 @@ function getColorName (color) {
return "#" + color.toString(16);
}

function toUInt32(pixelArray) {
function toUInt32(pixelArray, roundForYuv) {
let p = pixelArray.data;

// YUV to RGB conversion introduces some loss, so provide some leeway.
if (roundForYuv) {
const tolerance = 3;
for (var i = 0; i < p.length; ++i) {
if (p[i] >= 0xFF - tolerance)
p[i] = 0xFF;
if (p[i] <= 0x00 + tolerance)
p[i] = 0x00;
}
}

return ((p[0] << 24) + (p[1] << 16) + (p[2] << 8) + p[3]) >>> 0;
}

Expand Down Expand Up @@ -74,17 +86,20 @@ function testFourColorsDecodeBuffer(buffer, mimeType, options = {}) {
});
}

function testFourColorDecodeWithExifOrientation(orientation, canvas) {
function testFourColorDecodeWithExifOrientation(orientation, canvas, useYuv) {
return ImageDecoder.isTypeSupported('image/jpeg').then(support => {
assert_implements_optional(
support, 'Optional codec image/jpeg not supported.');
return fetch('four-colors.jpg')
const testFile =
useYuv ? 'four-colors-limited-range-420-8bpc.jpg' : 'four-colors.jpg';
return fetch(testFile)
.then(response => {
return response.arrayBuffer();
})
.then(buffer => {
let u8buffer = new Uint8Array(buffer);
u8buffer[0x1F] = orientation; // Location derived via diff.
u8buffer[useYuv ? 0x31 : 0x1F] =
orientation; // Location derived via diff.
let decoder = new ImageDecoder({data: u8buffer, type: 'image/jpeg'});
return decoder.decode();
})
Expand Down Expand Up @@ -155,12 +170,13 @@ function testFourColorDecodeWithExifOrientation(orientation, canvas) {
};
}

verifyFourColorsImage(expectedWidth, expectedHeight, ctx, matrix);
verifyFourColorsImage(
expectedWidth, expectedHeight, ctx, matrix, useYuv);
});
});
}

function verifyFourColorsImage(width, height, ctx, matrix) {
function verifyFourColorsImage(width, height, ctx, matrix, isYuv) {
if (!matrix) {
matrix = [
[kYellow, kRed],
Expand All @@ -173,10 +189,11 @@ function verifyFourColorsImage(width, height, ctx, matrix) {
let expectedBottomLeft = matrix[1][0];
let expectedBottomRight = matrix[1][1];

let topLeft = toUInt32(ctx.getImageData(0, 0, 1, 1));
let topRight = toUInt32(ctx.getImageData(width - 1, 0, 1, 1));
let bottomLeft = toUInt32(ctx.getImageData(0, height - 1, 1, 1));
let bottomRight = toUInt32(ctx.getImageData(width - 1, height - 1, 1, 1));
let topLeft = toUInt32(ctx.getImageData(0, 0, 1, 1), isYuv);
let topRight = toUInt32(ctx.getImageData(width - 1, 0, 1, 1), isYuv);
let bottomLeft = toUInt32(ctx.getImageData(0, height - 1, 1, 1), isYuv);
let bottomRight =
toUInt32(ctx.getImageData(width - 1, height - 1, 1, 1), isYuv);

assert_equals(getColorName(topLeft), getColorName(expectedTopLeft),
'top left corner');
Expand Down
33 changes: 33 additions & 0 deletions webcodecs/image-decoder.https.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,39 @@ promise_test(t => {
return testFourColorDecodeWithExifOrientation(8);
}, 'Test JPEG w/ EXIF orientation left-bottom.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(1, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation top-left.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(2, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation top-right.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(3, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation bottom-right.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(4, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation bottom-left.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(5, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation left-top.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(6, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation right-top.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(7, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation right-bottom.');

promise_test(t => {
return testFourColorDecodeWithExifOrientation(8, null, /*useYuv=*/ true);
}, 'Test 4:2:0 JPEG w/ EXIF orientation left-bottom.');


promise_test(t => {
return testFourColorsDecode('four-colors.png', 'image/png');
}, 'Test PNG image decoding.');
Expand Down

0 comments on commit 8016ebe

Please sign in to comment.