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

Add multiple format image files for copyToTexture,image_file cases #4135

Merged
merged 2 commits into from
Jan 15, 2025
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
Binary file added src/resources/four-colors.avif
Binary file not shown.
Binary file added src/resources/four-colors.bmp
Binary file not shown.
Binary file added src/resources/four-colors.gif
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.ico
Binary file not shown.
8 changes: 8 additions & 0 deletions src/resources/four-colors.svg
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.webp
Binary file not shown.
119 changes: 115 additions & 4 deletions src/webgpu/web_platform/copyToTexture/image_file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { makeTestGroup } from '../../../common/framework/test_group.js';
import { TextureUploadingUtils } from '../../util/copy_to_texture.js';
import {
convertToUnorm8,
GetSourceFromImageFile,
GetSourceFromEXIFImageFile,
kImageNames,
kImageInfo,
kImageExpectedColors,
kObjectTypeFromFiles,
kEXIFImageNames,
kEXIFImageInfo,
loadImageFileAndRun,
} from '../util.js';

export const g = makeTestGroup(TextureUploadingUtils);
Expand Down Expand Up @@ -38,7 +41,7 @@ g.test('from_orientation_metadata_file')
)
.params(u =>
u //
.combine('imageName', kImageNames)
.combine('imageName', kEXIFImageNames)
.combine('objectTypeFromFile', kObjectTypeFromFiles)
.combine('srcDoFlipYDuringCopy', [true, false])
)
Expand All @@ -47,7 +50,7 @@ g.test('from_orientation_metadata_file')
const kColorFormat = 'rgba8unorm';

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

Expand All @@ -72,7 +75,7 @@ g.test('from_orientation_metadata_file')
}
);

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

if (srcDoFlipYDuringCopy) {
Expand Down Expand Up @@ -123,3 +126,111 @@ g.test('from_orientation_metadata_file')
]);
}
});

g.test('from_multiple_formats')
.desc(
`
Test HTMLImageElements which loaded multiple image file formats. Including
*.jpg, *.png, *.bmp, *.webp, *.avif, *.svg, *.ico and *.gif.

It creates an 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 multiple image file format
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
- TODO(#4108): Make this work in service workers (see GetSourceFromImageFile)
`
)
.params(u =>
u //
.combine('imageName', kImageNames)
.combine('srcDoFlipYDuringCopy', [true, false])
)
.fn(async t => {
const { imageName, srcDoFlipYDuringCopy } = t.params;
const kColorFormat = 'rgba8unorm';
await loadImageFileAndRun(t, imageName, (source: HTMLImageElement) => {
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]),
},
]);
}
});
});
65 changes: 61 additions & 4 deletions src/webgpu/web_platform/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ const kFourColorsInfo = {
},
} as const;

export const kImageInfo = makeTable({
export const kEXIFImageInfo = makeTable({
Copy link
Collaborator

Choose a reason for hiding this comment

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

I suspect that webp and avif also support EXIF. Not sure about any others. Possibly worth testing in a followup?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, webp indeed support EXIF. AVIF support EXIF but browser(chrome) choose to ignore that part and using irot instead. However, I still invesitigate how to modify irot for avif file.

P.S. I ref to this article https://zpl.fi/exif-orientation-in-different-formats/ and did some experiment locally.

table: {
'four-colors.jpg': kFourColorsInfo,
'four-colors-rotate-90-cw.jpg': kFourColorsInfo,
Expand All @@ -635,9 +635,25 @@ export const kImageInfo = makeTable({
},
} as const);

export const kImageInfo = makeTable({
table: {
'four-colors.jpg': kFourColorsInfo,
'four-colors.png': kFourColorsInfo,
'four-colors.bmp': kFourColorsInfo,
'four-colors.webp': kFourColorsInfo,
'four-colors.gif': kFourColorsInfo,
'four-colors.avif': kFourColorsInfo,
Comment on lines +643 to +645
Copy link
Collaborator

Choose a reason for hiding this comment

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

just a note, I checked caniuse and it seems to say firefox and safari support webp and avif, so that's probably safe. Still would be worth checking that firefox and ideally safari will load all of these image files, if you haven't (just as standalone images; don't need to worry about whether their webgpu implementation supports them yet)

Copy link
Contributor Author

@shaoboyan091 shaoboyan091 Jan 13, 2025

Choose a reason for hiding this comment

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

Firefox stable(134.0) : All image files are supported.
Safari Stable (18.2) : All image files are supported.

'four-colors.ico': kFourColorsInfo,
'four-colors.svg': kFourColorsInfo,
},
} as const);

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

type EXIFImageName = keyof typeof kEXIFImageInfo;
export const kEXIFImageNames: readonly EXIFImageName[] = keysOf(kEXIFImageInfo);

type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number];
export const kObjectTypeFromFiles = [
'ImageBitmap-from-Blob',
Expand All @@ -649,12 +665,12 @@ export const kObjectTypeFromFiles = [
* 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(
export async function GetSourceFromEXIFImageFile(
test: GPUTest,
imageName: ImageName,
exifImageName: EXIFImageName,
objectTypeFromFile: ObjectTypeFromFile
): Promise<ImageBitmap | HTMLImageElement> {
const imageUrl = getResourcePath(imageName);
const imageUrl = getResourcePath(exifImageName);

switch (objectTypeFromFile) {
case 'ImageBitmap-from-Blob': {
Expand Down Expand Up @@ -689,3 +705,44 @@ export async function GetSourceFromImageFile(
}
}
}

/**
* Create HTMLImageElement and load image file and waits for it to be loaded.
* Returns a promise which resolves after `callback` (which may be async) completes.
*
* @param imageName An valid imageName in kkImageInfo table .
* @param callback Function to call when HTMLImageElement is loaded.
*
*/
export function loadImageFileAndRun(
test: GPUTest,
imageName: ImageName,
callback: (image: HTMLImageElement) => unknown | Promise<unknown>
kainino0x marked this conversation as resolved.
Show resolved Hide resolved
): Promise<void> {
return raceWithRejectOnTimeout(
new Promise((resolve, reject) => {
const callbackAndResolve = (image: HTMLImageElement) =>
void (async () => {
try {
await callback(image);
resolve();
} catch (ex) {
reject(ex);
}
})();
// 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.'
);
}
const image = new Image();
image.src = getResourcePath(imageName);
image.onload = () => {
callbackAndResolve(image);
};
}),
2000,
'Video never became ready'
);
}
Loading