Skip to content

Commit 10b66cd

Browse files
CopyEI2T: Cases To Cover Rotated Images (#4111)
Co-authored-by: Kai Ninomiya <[email protected]>
1 parent e99550b commit 10b66cd

File tree

7 files changed

+230
-0
lines changed

7 files changed

+230
-0
lines changed

src/resources/README.md

+22
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,25 @@ ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4
9191
rm temp.mp4
9292
9393
```
94+
95+
The test jpg files were generated with by exiftool cmds and other image tools in below steps:
96+
```
97+
// Generate four-colors.jpg with no orientation metadata
98+
Use a image tool (e.g. "Paint" app on Windows) to create four-colors.jpg from four-colors.png and check with exiftool to ensure no orientation metadata been set.
99+
100+
// Generate jpg picture with 90 cw rotation metadata
101+
Use a image tool (e.g. "Paint" app on Windows) to create four-colors-hard-rotate-90-ccw.jpg and check with exiftool to ensure no orientation metadata been set.
102+
exiftool -Orientation#=6 four-colors-hard-rotate-90-ccw.jpg -o four-colors-rotate-90-cw.jpg
103+
rm four-colors-hard-rotate-90-ccw.jpg
104+
105+
// Generate jpg picture with 180 cw rotation metadata
106+
Use a image tool (e.g. "Paint" app on Windows) to create four-colors-hard-rotate-180-ccw.jpg and check with exiftool to ensure no orientation metadata been set.
107+
exiftool -Orientation#=3 four-colors-hard-rotate-180-ccw.jpg -o four-colors-rotate-180-cw.jpg
108+
rm four-colors-hard-rotate-180-ccw.jpg
109+
110+
// Generate jpg picture with 270 cw rotation metadata
111+
Use a image tool (e.g. "Paint" app on Windows) to create four-colors-hard-rotate-270-ccw.jpg and check with exiftool to ensure no orientation metadata been set.
112+
exiftool -Orientation#=8 four-colors-hard-rotate-270-ccw.jpg -o four-colors-rotate-270-cw.jpg
113+
rm four-colors-hard-rotate-270-ccw.jpg
114+
115+
```
3.44 KB
Loading
3.49 KB
Loading
2.71 KB
Loading

src/resources/four-colors.jpg

2.6 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
export const description = `
2+
copyExternalImageToTexture from ImageFiles like *.png, *.jpg source.
3+
`;
4+
5+
import { makeTestGroup } from '../../../common/framework/test_group.js';
6+
import { TextureUploadingUtils } from '../../util/copy_to_texture.js';
7+
import {
8+
convertToUnorm8,
9+
GetSourceFromImageFile,
10+
kImageNames,
11+
kImageInfo,
12+
kImageExpectedColors,
13+
kObjectTypeFromFiles,
14+
} from '../util.js';
15+
16+
export const g = makeTestGroup(TextureUploadingUtils);
17+
18+
g.test('from_orientation_metadata_file')
19+
.desc(
20+
`
21+
Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly.
22+
23+
It creates an ImageBitmap or HTMLImageElement using images in the 'resources' folder.
24+
25+
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
26+
of dst texture, and read one pixel out to compare with the manually documented expected color.
27+
28+
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
29+
is flipped.
30+
31+
The tests covers:
32+
- Image with rotation metadata
33+
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
34+
- TODO: partial copy tests should be added
35+
- TODO: all valid dstColorFormat tests should be added.
36+
- TODO(#4108): Make this work in service workers (see GetSourceFromImageFile)
37+
`
38+
)
39+
.params(u =>
40+
u //
41+
.combine('imageName', kImageNames)
42+
.combine('objectTypeFromFile', kObjectTypeFromFiles)
43+
.combine('srcDoFlipYDuringCopy', [true, false])
44+
)
45+
.fn(async t => {
46+
const { imageName, objectTypeFromFile, srcDoFlipYDuringCopy } = t.params;
47+
const kColorFormat = 'rgba8unorm';
48+
49+
// Load image file.
50+
const source = await GetSourceFromImageFile(t, imageName, objectTypeFromFile);
51+
const width = source.width;
52+
const height = source.height;
53+
54+
const dstTexture = t.createTextureTracked({
55+
size: { width, height },
56+
format: kColorFormat,
57+
usage:
58+
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
59+
});
60+
61+
t.device.queue.copyExternalImageToTexture(
62+
{
63+
source,
64+
flipY: srcDoFlipYDuringCopy,
65+
},
66+
{
67+
texture: dstTexture,
68+
},
69+
{
70+
width,
71+
height,
72+
}
73+
);
74+
75+
const expect = kImageInfo[imageName].display;
76+
const presentColors = kImageExpectedColors.srgb;
77+
78+
if (srcDoFlipYDuringCopy) {
79+
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
80+
// Flipped top-left.
81+
{
82+
coord: { x: width * 0.25, y: height * 0.25 },
83+
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
84+
},
85+
// Flipped top-right.
86+
{
87+
coord: { x: width * 0.75, y: height * 0.25 },
88+
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
89+
},
90+
// Flipped bottom-left.
91+
{
92+
coord: { x: width * 0.25, y: height * 0.75 },
93+
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
94+
},
95+
// Flipped bottom-right.
96+
{
97+
coord: { x: width * 0.75, y: height * 0.75 },
98+
exp: convertToUnorm8(presentColors[expect.topRightColor]),
99+
},
100+
]);
101+
} else {
102+
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
103+
// Top-left.
104+
{
105+
coord: { x: width * 0.25, y: height * 0.25 },
106+
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
107+
},
108+
// Top-right.
109+
{
110+
coord: { x: width * 0.75, y: height * 0.25 },
111+
exp: convertToUnorm8(presentColors[expect.topRightColor]),
112+
},
113+
// Bottom-left.
114+
{
115+
coord: { x: width * 0.25, y: height * 0.75 },
116+
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
117+
},
118+
// Bottom-right.
119+
{
120+
coord: { x: width * 0.75, y: height * 0.75 },
121+
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
122+
},
123+
]);
124+
}
125+
});

src/webgpu/web_platform/util.ts

+83
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ export const kVideoExpectedColors = makeTable({
101101
},
102102
} as const);
103103

104+
export const kImageExpectedColors = {
105+
srgb: {
106+
red: { R: 1.0, G: 0.0, B: 0.0, A: 1.0 },
107+
green: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
108+
blue: { R: 0.0, G: 0.0, B: 1.0, A: 1.0 },
109+
yellow: { R: 1.0, G: 1.0, B: 0.0, A: 1.0 },
110+
},
111+
} as const;
112+
104113
// MAINTENANCE_TODO: Add BT.2020 video in table.
105114
// Video container and codec defines several transform ops to apply to raw decoded frame to display.
106115
// Our test cases covers 'visible rect' and 'rotation'.
@@ -350,6 +359,7 @@ type VideoName = keyof typeof kVideoInfo;
350359
export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo);
351360

352361
export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const;
362+
353363
/**
354364
* Starts playing a video and waits for it to be consumable.
355365
* Returns a promise which resolves after `callback` (which may be async) completes.
@@ -606,3 +616,76 @@ export async function captureCameraFrame(test: GPUTest): Promise<VideoFrame> {
606616

607617
return frame;
608618
}
619+
620+
const kFourColorsInfo = {
621+
display: {
622+
topLeftColor: 'yellow',
623+
topRightColor: 'red',
624+
bottomLeftColor: 'blue',
625+
bottomRightColor: 'green',
626+
},
627+
} as const;
628+
629+
export const kImageInfo = makeTable({
630+
table: {
631+
'four-colors.jpg': kFourColorsInfo,
632+
'four-colors-rotate-90-cw.jpg': kFourColorsInfo,
633+
'four-colors-rotate-180-cw.jpg': kFourColorsInfo,
634+
'four-colors-rotate-270-cw.jpg': kFourColorsInfo,
635+
},
636+
} as const);
637+
638+
type ImageName = keyof typeof kImageInfo;
639+
export const kImageNames: readonly ImageName[] = keysOf(kImageInfo);
640+
641+
type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number];
642+
export const kObjectTypeFromFiles = [
643+
'ImageBitmap-from-Blob',
644+
'ImageBitmap-from-Image',
645+
'Image',
646+
] as const;
647+
648+
/**
649+
* Load image file(e.g. *.jpg) from ImageBitmap, blob or HTMLImageElement. And
650+
* convert the result to valid source that GPUCopyExternalImageSource supported.
651+
*/
652+
export async function GetSourceFromImageFile(
653+
test: GPUTest,
654+
imageName: ImageName,
655+
objectTypeFromFile: ObjectTypeFromFile
656+
): Promise<ImageBitmap | HTMLImageElement> {
657+
const imageUrl = getResourcePath(imageName);
658+
659+
switch (objectTypeFromFile) {
660+
case 'ImageBitmap-from-Blob': {
661+
// MAINTENANCE_TODO: resource folder path when using service worker is not correct. Return
662+
// the correct path to load resource in correct place.
663+
// The wrong path: /out/webgpu/webworker/web_platform/copyToTexture/resources
664+
if (globalThis.constructor.name === 'ServiceWorkerGlobalScope') {
665+
test.skip('Try to load image resource from serivce worker but the path is not correct.');
666+
}
667+
// Load image file through fetch.
668+
const response = await fetch(imageUrl);
669+
return createImageBitmap(await response.blob());
670+
}
671+
case 'ImageBitmap-from-Image':
672+
case 'Image': {
673+
// Skip test if HTMLImageElement is not available, e.g. in worker.
674+
if (typeof HTMLImageElement === 'undefined') {
675+
test.skip(
676+
'Try to use HTMLImage do image file decoding but HTMLImageElement not available.'
677+
);
678+
}
679+
680+
// Load image file through HTMLImageElement.
681+
const image = new Image();
682+
image.src = imageUrl;
683+
await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout');
684+
if (objectTypeFromFile === 'Image') {
685+
return image;
686+
}
687+
688+
return createImageBitmap(image);
689+
}
690+
}
691+
}

0 commit comments

Comments
 (0)