Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CopyEI2T: Cases To Cover Rotated Images #4111

Merged
merged 13 commits into from
Jan 7, 2025
22 changes: 22 additions & 0 deletions src/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,25 @@ ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4
rm temp.mp4

```

The test jpg files were generated with by exiftool cmds and other image tools in below steps:
```
// Generate four-colors.jpg with no orientation metadata
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.

// Generate jpg picture with 90 cw rotation metadata
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.
exiftool -Orientation#=6 four-colors-hard-rotate-90-ccw.jpg -o four-colors-rotate-90-cw.jpg
rm four-colors-hard-rotate-90-ccw.jpg

// Generate jpg picture with 180 cw rotation metadata
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.
exiftool -Orientation#=3 four-colors-hard-rotate-180-ccw.jpg -o four-colors-rotate-180-cw.jpg
rm four-colors-hard-rotate-180-ccw.jpg

// Generate jpg picture with 270 cw rotation metadata
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.
exiftool -Orientation#=8 four-colors-hard-rotate-270-ccw.jpg -o four-colors-rotate-270-cw.jpg
rm four-colors-hard-rotate-270-ccw.jpg

```
shaoboyan091 marked this conversation as resolved.
Show resolved Hide resolved
Binary file added src/resources/four-colors-rotate-180-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors-rotate-270-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors-rotate-90-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 124 additions & 0 deletions src/webgpu/web_platform/copyToTexture/image_file.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
export const description = `
copyExternalImageToTexture from ImageFiles like *.png, *.jpg source.
`;

import { makeTestGroup } from '../../../common/framework/test_group.js';
import { TextureUploadingUtils } from '../../util/copy_to_texture.js';
import {
convertToUnorm8,
GetSourceFromImageFile,
kImageNames,
kImageInfo,
kImageExpectedColors,
kObjectTypeFromFiles,
} from '../util.js';

export const g = makeTestGroup(TextureUploadingUtils);

g.test('from_orientation_metadata_file')
.desc(
`
Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly.

It creates an ImageBitmap or HTMLImageElement using images in the 'resources' folder.

Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read one pixel out to compare with the manually documented expected color.

If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.

The tests covers:
- Image with rotation metadata
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
`
)
.params(u =>
u //
.combine('imageName', kImageNames)
.combine('objectTypeFromFile', kObjectTypeFromFiles)
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
.combine('srcDoFlipYDuringCopy', [true, false])
)
.fn(async t => {
const { imageName, objectTypeFromFile, srcDoFlipYDuringCopy } = t.params;
const kColorFormat = 'rgba8unorm';

// Load image file.
const source = await GetSourceFromImageFile(t, imageName, objectTypeFromFile);
const width = source.width;
const height = source.height;

const dstTexture = t.createTextureTracked({
size: { width, height },
format: kColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});

t.device.queue.copyExternalImageToTexture(
{
source,
flipY: srcDoFlipYDuringCopy,
},
{
texture: dstTexture,
},
{
width,
height,
}
);

const expect = kImageInfo[imageName].display;
const presentColors = kImageExpectedColors.srgb;

if (srcDoFlipYDuringCopy) {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Flipped top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Flipped top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
// Flipped bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Flipped bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
]);
} else {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
// Bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
]);
}
});
83 changes: 83 additions & 0 deletions src/webgpu/web_platform/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ export const kVideoExpectedColors = makeTable({
},
} as const);

export const kImageExpectedColors = {
srgb: {
red: { R: 1.0, G: 0.0, B: 0.0, A: 1.0 },
green: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
blue: { R: 0.0, G: 0.0, B: 1.0, A: 1.0 },
yellow: { R: 1.0, G: 1.0, B: 0.0, A: 1.0 },
},
} as const;

// MAINTENANCE_TODO: Add BT.2020 video in table.
// Video container and codec defines several transform ops to apply to raw decoded frame to display.
// Our test cases covers 'visible rect' and 'rotation'.
Expand Down Expand Up @@ -350,6 +359,7 @@ type VideoName = keyof typeof kVideoInfo;
export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo);

export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const;

/**
* Starts playing a video and waits for it to be consumable.
* Returns a promise which resolves after `callback` (which may be async) completes.
Expand Down Expand Up @@ -606,3 +616,76 @@ export async function captureCameraFrame(test: GPUTest): Promise<VideoFrame> {

return frame;
}

const kFourColorsInfo = {
display: {
topLeftColor: 'yellow',
topRightColor: 'red',
bottomLeftColor: 'blue',
bottomRightColor: 'green',
},
} as const;

export const kImageInfo = makeTable({
table: {
'four-colors.jpg': kFourColorsInfo,
'four-colors-rotate-90-cw.jpg': kFourColorsInfo,
'four-colors-rotate-180-cw.jpg': kFourColorsInfo,
'four-colors-rotate-270-cw.jpg': kFourColorsInfo,
},
} as const);
shaoboyan091 marked this conversation as resolved.
Show resolved Hide resolved

type ImageName = keyof typeof kImageInfo;
export const kImageNames: readonly ImageName[] = keysOf(kImageInfo);

type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number];
export const kObjectTypeFromFiles = [
'ImageBitmap-from-Blob',
'ImageBitmap-from-Image',
'Image',
] as const;

/**
* Load image file(e.g. *.jpg) from ImageBitmap, blob or HTMLImageElement. And
* convert the result to valid source that GPUCopyExternalImageSource supported.
*/
export async function GetSourceFromImageFile(
test: GPUTest,
imageName: ImageName,
objectTypeFromFile: ObjectTypeFromFile
): Promise<ImageBitmap | HTMLImageElement> {
const imageUrl = getResourcePath(imageName);

switch (objectTypeFromFile) {
case 'ImageBitmap-from-Blob': {
// MAINTENANCE_TODO: resource folder path when using service worker is not correct. Return
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
// the correct path to load resource in correct place.
// The wrong path: /out/webgpu/webworker/web_platform/copyToTexture/resources
if (globalThis.constructor.name === 'ServiceWorkerGlobalScope') {
test.skip('Try to load image resource from serivce worker but the path is not correct.');
}
// Load image file through fetch.
const response = await fetch(imageUrl);
return createImageBitmap(await response.blob());
}
case 'ImageBitmap-from-Image':
case 'Image': {
// Skip test if HTMLImageElement is not available, e.g. in worker.
if (typeof HTMLImageElement === 'undefined') {
test.skip(
'Try to use HTMLImage do image file decoding but HTMLImageElement not available.'
);
}

// Load image file through HTMLImageElement.
const image = new Image();
image.src = imageUrl;
await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout');
if (objectTypeFromFile === 'Image') {
return image;
}

return createImageBitmap(image);
}
}
}