From 3981e931ad423a0d34be3e5cd42b3bfc210f82b2 Mon Sep 17 00:00:00 2001 From: shaoboyan Date: Thu, 19 Dec 2024 16:15:34 +0800 Subject: [PATCH 1/9] CopyEI2T: Cases To Cover Rotated Images This PR created images with rotation metadata. It tests using CopyEI2T to upload image and imageBitmap with 'from-image' orientation flag. Issue:#4180 --- src/resources/README.md | 13 ++ src/resources/four-colors-rotate-180-cw.jpg | Bin 0 -> 2677 bytes src/resources/four-colors-rotate-270-cw.jpg | Bin 0 -> 2677 bytes src/resources/four-colors-rotate-90-cw.jpg | Bin 0 -> 2677 bytes src/resources/four-colors.jpg | Bin 0 -> 2665 bytes .../copyToTexture/ImageBitmap.spec.ts | 126 ++++++++++++++++++ .../web_platform/copyToTexture/image.spec.ts | 117 ++++++++++++++++ src/webgpu/web_platform/util.ts | 50 +++++++ 8 files changed, 306 insertions(+) create mode 100644 src/resources/four-colors-rotate-180-cw.jpg create mode 100644 src/resources/four-colors-rotate-270-cw.jpg create mode 100644 src/resources/four-colors-rotate-90-cw.jpg create mode 100644 src/resources/four-colors.jpg diff --git a/src/resources/README.md b/src/resources/README.md index a1ed0604170c..28e522e0239d 100644 --- a/src/resources/README.md +++ b/src/resources/README.md @@ -91,3 +91,16 @@ ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4 rm temp.mp4 ``` + +The test video files were generated with by exiftool cmds below: +``` +// Generate jpg picture with 90 cw rotation metadata +exiftool -Orientation#=6 four-colors.jpg -o .\four-colors-rotate-90-cw.jpg + +// Generate jpg picture with 180 cw rotation metadata +exiftool -Orientation#=3 four-colors.jpg -o .\four-colors-rotate-180-cw.jpg + +// Generate jpg picture with 270 cw rotation metadata +exiftool -Orientation#=8 four-colors.jpg -o .\four-colors-rotate-270-cw.jpg + +``` diff --git a/src/resources/four-colors-rotate-180-cw.jpg b/src/resources/four-colors-rotate-180-cw.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ca8d69aa4823fba4458d37fd92bf8d6f6821560c GIT binary patch literal 2677 zcmeH{ZBSHI7{{Nxci&lD?y_r^)9eT$tMmoXD8JlvSx6>FmK4gSNHA2IhS4y|nY?Mm z%blqm5=)#W+}$X$Y=;spre;Gyw6P%+0&yl{C4#jkc42W@?%vZm!Xi^2kiN#V_v}3P zoadhV|Novd7xkiFAtozhQwC5J1!edLXaI^VWqXPMHgASC0H6dpr2`sg6#1tqklo1v za%mdRk$D`}nCYC{SQ(H`&+AI9y1O~41FHNaK%))a05eD^id-{6%V-jEsg#x}!9fsZ=W!3U#zv9i_pcP%Y6$YnG6lY(gF2hB)jE0X294OYdDy|HmAhn4z`)%L~~ReWmBsG(qA zr+VqiwF3!0V(pHzdoyH zWFsh^uavB7M+$Jc|8oR}^NYLBCALUrE{(Z4AtSCIc6nVvyDgmCKlwI-zs3X_L1it1 z?EXm>!EUagCePPnG5XBL&}sMBas>SY2$)pS?)G!yPhy+Nb@L0J^^~)LXSks>1nvuB z+^(>h8$=Mchk0v$`?GqkO~ZN5BDhk5fU6MFJ3{3ME+d#SPw5@*cbZKI_+<#XUqW!a zUqqk{R@DWf5gbJ@Q8BUF!aZ1OMW9bcaOwtvWOrCJ`eIox7L$nJPwgK$#D`Nf6(ZPn zPAuf61lF^P>q-;1Am}sp)*FQVotZ**yZBj&a1p`nAlLc?f`$^I4#63=F)Jx}%VC=X zVRRt~LViAMa=2ed;7N$QvkuEOG+?{Xe!<}Vvig9@}eQV&_ zidio{4p(dl%IZ#x=Z#qOTx(W@S(7$+Ix}o_Pgdg-6Fc@C0bmPO7XZM|X z_I;my_xt@k?=0#;ze21vV?zc|6a}UD0@M$MmeSpY02?>LDgaP|oYDe~GYaQGQy{yO z1LV*&o+I-(Y_Zchd9X1cou1d0*tGX@QU_Go34jJWx&da8P!zdmf|k)FQQ}bsyKC{KWO? zsV~1e(|GpW*WWaqztGZpvF+03AFf{O?E1O8=X!6y_vXNFgTD_AkNC#?%4G)E<*W{>Lg z_jafjFJIlC=p)wdIJ-Y%hyRnayTIF4Em=cC&fQ4{{lQ)T6dzXYao|tQB20!AB z?m!lTqPa@Rx@I^ZmydmpK!1K=_u0f2$jqiOJ11nswL|U^chF%E=k!g!gWxZ}KqIKA zL6Frq$s*Xr2PW(x1HM(zp!L!~nHt;Mrn1;Y} zL5$xSHgf|A!j3R+%WHd1$F-`tk+TS{6eHluh4l7N8G_3Qrp!}1r{~=!BLaR2g07bl z9P1MiXo8isffxiw5KNR$th8_sm)H>Kk`bJ`fgsrv77e~Qb_9z_Lhz^Nk8I+jPBa!E z*m_PZ;HCuDyMpUX6E`8~HT2Z$g?$~FLROpjS+Q^t!R;W|@)QDBu~3WP4BKEe1#da+ zvmlJl2SI3z4;!7HHxPIeBj2pWa`i4O7XwLQ!`_20b>!9S@FZiJ2G_}Y8x4UB1cR9f zzTNcW0n@k>Q@p!DxsX#Qy847mSZ(E`+j6klmMNsWBi!(Bf$!wHFn}6^=wsImxv%mV zV#U!&---;rZT3(d=TAWJOVZ$X`j8FxQ0s{>Gm9pyW=pZSgPUp*x}6@Yu}+N7o#~s4 zYb|0&@Nu|eM^IXOd^~s9qT^bu5oV2=;OWe;%`;hrPfVQuc>=&5tjvq_%rQUr%}to! ttsy~ebn-CQ2oI3I=p*f8AI?=t)`g{{`kE&2!u?b)?EP>4{|_eU`rnV2H6Z{1 literal 0 HcmV?d00001 diff --git a/src/resources/four-colors-rotate-90-cw.jpg b/src/resources/four-colors-rotate-90-cw.jpg new file mode 100644 index 0000000000000000000000000000000000000000..15840c2269693b097a885d70f494cdb7d99ba1f7 GIT binary patch literal 2677 zcmeH{e^3-<7{{Nzy7XZM|X z_I;my_xt@k?=0#;ze21vV?zc|6a}UD0@M$MmeSpY02?>LDgaP|oYDe~GYaGYGzGFd zIY16g<2f>q!xlT8lLs3E(&>3^iA{SiCv`xTod9UCqZ?ob2}O~6CTJN=LN1lkGKE~B zAVV1y9i>#NlnR9^Mx}~Y<4{B`(!{72k(_KoUMFwiTdhv$ccj7+;Md7V7Ywp|gw zN0)M_%ArhH-_Wej?igL3n!opOlxpz?-`3o(r7uzmf{^9Di&aR)kd#?BPdv6Z>Hu(F{@Q82BKOUI49h?ji zUleX(B&Lo1!xw{nNpKIO3gU~B?8k#@CR-TEp=LvvI@YWAo; ze{Y9s@$%LEi9TZOjN8##;MN-+YiTu5&Zl_9u{V9GqDb9&xwG9uuYAn1A- z!LdFOfhJg48;C)01i?i4#7YbIaET3pE*Zh88wip;VbS1=V@I%Uai>O^A! zg01Jo0&YrRy(_rRG;tGxUPDj4Uf9=>DP*;YpA`!i5!?=PEl(kE6$`Zp&ae$uQ}CA4 zJ`2L=d=P}j_^{FGc>{qrG4jn?ELZQsaxst;Htaq4Qb%6B4o@gsax6u&DKronz z;M+|<9x#nNF~z$ZlnXg^qN`81gw<9~x-AE*ZJ9#4JHieB7WhuC3j?Szh(31Bkozi+ zAyyoX^sUI?+hz~dasC7Zza$NQrw`e154D~MGqY&IYPJ-MJGiM9q1)-P8tcUP+?l?) zxYicbE literal 0 HcmV?d00001 diff --git a/src/resources/four-colors.jpg b/src/resources/four-colors.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c04c1f6cd30175507927001ffb759bc83694c8e6 GIT binary patch literal 2665 zcmeH{e@t6d6vxl&YiTKEk6F^Yq^9~eV6nZqUS zxvBhki8eTtZXW7Gze1cjYf~0b6b0q@185KmOyzra0&Lz4YXCq2GMWMzt|**ln!a-< zHCW=NYx2dyfE3?w8DD0Jzgv?&pejxRG+EINFoJ}l$TbtRj3yB!mC`bKlw3|y5gik) zP$(60xiVI%j8Wl`M=w#ws+N$NY(gFX!UBB{i~`1~$7m`MBn(9}6lwz< zRw~0J2#lGaB(zi(g(WFta6@w(mQT|XELJAPcB$9!I!GB={EE~KQ3;mq^2EKGw8J$v zMbgHmR&8$A_{#L6eMh2|OYd8D{{zXZQuM3WtX=nT#v_leH)fi$vLD}Mesb%!y!@x0 zE-2pd!p>d0_msR`w*Qp_<*y!mj^cV2JplfS$HB z>+B!C80<@eXCRdmUzFqkPMVR*R-{J7Z?MR>?@dTdJFL)btg$t(e&oAyim)Ihixise1gp9a;#5v{+*sS5af#BN+{_+Vlf~q;YuHwedBDhkDfU6WTJ3|!+E+d#SPHF6}cUtra_+6;Ua?D0j}){1dXLaJ%TfAli3isWw*|OFuo83 zAuk`++g-0Ca3@FpvmVRUHe$ILNE#bm=*Ka?*WG?SKAWx)=BUH~;?!6LkG=(gQT< literal 0 HcmV?d00001 diff --git a/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts b/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts index 5036c8ef20bc..34997b01f86d 100644 --- a/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts @@ -10,9 +10,17 @@ TODO: Test ImageBitmap generated from all possible ImageBitmapSource, relevant I TODO: Test zero-sized copies from all sources (just make sure params cover it) (e.g. 0x0, 0x4, 4x0). `; +import { getResourcePath } from '../../../common/framework/resources.js'; import { makeTestGroup } from '../../../common/framework/test_group.js'; +import { raceWithRejectOnTimeout } from '../../../common/util/util.js'; import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js'; import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js'; +import { + convertToUnorm8, + kImageNames, + kImageInfo, + kImageExpectedColors, +} from '../../web_platform/util.js'; import { kTestColorsAll, kTestColorsOpaque, makeTestColorsTexelView } from './util.js'; @@ -541,3 +549,121 @@ g.test('copy_subrect_from_2D_Canvas') { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 } ); }); + +g.test('from_rotated_image') + .desc( + ` + Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly. + + It creates ImageBitmap with images under Resource folder, with orientation flag set to + 'from-image'. + + 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. + - TODO: all valid imageOrientation flags tests should be added + TODO: all valid dstColorFormat tests should be added. + ` + ) + .params(u => + u // + .combine('imageName', kImageNames) + .combine('srcDoFlipYDuringCopy', [true, false]) + ) + .fn(async t => { + const { imageName, srcDoFlipYDuringCopy } = t.params; + const kColorFormat = 'rgba8unorm'; + + // Load image. + const image = new Image(); + const imageUrl = getResourcePath(imageName); + image.src = imageUrl; + await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); + + const imageBitmap = await createImageBitmap(image, { + imageOrientation: 'from-image', + }); + + const width = imageBitmap.width; + const height = imageBitmap.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: imageBitmap, + 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]), + }, + ]); + } + }); diff --git a/src/webgpu/web_platform/copyToTexture/image.spec.ts b/src/webgpu/web_platform/copyToTexture/image.spec.ts index 7bb577ae5d07..9d90f8d0e412 100644 --- a/src/webgpu/web_platform/copyToTexture/image.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image.spec.ts @@ -2,10 +2,17 @@ export const description = ` copyExternalImageToTexture from HTMLImageElement source. `; +import { getResourcePath } from '../../../common/framework/resources.js'; import { makeTestGroup } from '../../../common/framework/test_group.js'; import { raceWithRejectOnTimeout } from '../../../common/util/util.js'; import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js'; import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js'; +import { + convertToUnorm8, + kImageNames, + kImageInfo, + kImageExpectedColors, +} from '../../web_platform/util.js'; import { kTestColorsOpaque, makeTestColorsTexelView } from './util.js'; @@ -339,3 +346,113 @@ g.test('copy_subrect_from_2D_Canvas') { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 } ); }); + +g.test('from_rotated_image') + .desc( + ` + Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly. + + It creates Images with images under Resource 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. + ` + ) + .params(u => + u // + .combine('imageName', kImageNames) + .combine('srcDoFlipYDuringCopy', [true, false]) + ) + .fn(async t => { + const { imageName, srcDoFlipYDuringCopy } = t.params; + const kColorFormat = 'rgba8unorm'; + + // Load image. + const image = new Image(); + const imageUrl = getResourcePath(imageName); + image.src = imageUrl; + await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); + const width = image.width; + const height = image.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: image, + 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]), + }, + ]); + } + }); diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts index f000d80e56b9..dd7c0ed0085c 100644 --- a/src/webgpu/web_platform/util.ts +++ b/src/webgpu/web_platform/util.ts @@ -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'. @@ -350,6 +359,47 @@ type VideoName = keyof typeof kVideoInfo; export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo); export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const; + +export const kImageInfo = makeTable({ + table: { + 'four-colors.jpg': { + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + }, + 'four-colors-rotate-90-cw.jpg': { + display: { + topLeftColor: 'blue', + topRightColor: 'yellow', + bottomLeftColor: 'green', + bottomRightColor: 'red', + }, + }, + 'four-colors-rotate-180-cw.jpg': { + display: { + topLeftColor: 'green', + topRightColor: 'blue', + bottomLeftColor: 'red', + bottomRightColor: 'yellow', + }, + }, + 'four-colors-rotate-270-cw.jpg': { + display: { + topLeftColor: 'red', + topRightColor: 'green', + bottomLeftColor: 'yellow', + bottomRightColor: 'blue', + }, + }, + }, +} as const); + +type ImageName = keyof typeof kImageInfo; +export const kImageNames: readonly ImageName[] = keysOf(kImageInfo); + /** * Starts playing a video and waits for it to be consumable. * Returns a promise which resolves after `callback` (which may be async) completes. From 91159f44ec09a2ab8b12c98da7344c1487c60441 Mon Sep 17 00:00:00 2001 From: shaoboyan Date: Thu, 19 Dec 2024 16:31:46 +0800 Subject: [PATCH 2/9] video -> jpg --- src/resources/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/README.md b/src/resources/README.md index 28e522e0239d..74cedb6bac96 100644 --- a/src/resources/README.md +++ b/src/resources/README.md @@ -92,7 +92,7 @@ rm temp.mp4 ``` -The test video files were generated with by exiftool cmds below: +The test jpg files were generated with by exiftool cmds below: ``` // Generate jpg picture with 90 cw rotation metadata exiftool -Orientation#=6 four-colors.jpg -o .\four-colors-rotate-90-cw.jpg From 6df131951c762c3c703d3e2fdc8053e9d5eeddd0 Mon Sep 17 00:00:00 2001 From: shaoboyan Date: Mon, 23 Dec 2024 13:24:32 +0800 Subject: [PATCH 3/9] Address comments --- src/resources/README.md | 11 +- src/resources/four-colors-rotate-180-cw.jpg | Bin 2677 -> 3519 bytes src/resources/four-colors-rotate-270-cw.jpg | Bin 2677 -> 3576 bytes src/resources/four-colors-rotate-90-cw.jpg | Bin 2677 -> 2780 bytes .../copyToTexture/ImageBitmap.spec.ts | 126 -------------- .../web_platform/copyToTexture/image.spec.ts | 117 ------------- .../copyToTexture/image_file.spec.ts | 124 ++++++++++++++ src/webgpu/web_platform/util.ts | 161 +++++++++++++----- 8 files changed, 255 insertions(+), 284 deletions(-) create mode 100644 src/webgpu/web_platform/copyToTexture/image_file.spec.ts diff --git a/src/resources/README.md b/src/resources/README.md index 74cedb6bac96..8b8f38ba2969 100644 --- a/src/resources/README.md +++ b/src/resources/README.md @@ -92,15 +92,24 @@ rm temp.mp4 ``` -The test jpg files were generated with by exiftool cmds below: +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.jpg -o .\four-colors-rotate-90-cw.jpg +rm four-clors-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.jpg -o .\four-colors-rotate-180-cw.jpg +rm four-clors-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.jpg -o .\four-colors-rotate-270-cw.jpg +rm four-colors-hard-rotate-270-ccw.jpg ``` diff --git a/src/resources/four-colors-rotate-180-cw.jpg b/src/resources/four-colors-rotate-180-cw.jpg index ca8d69aa4823fba4458d37fd92bf8d6f6821560c..5be3efab39b10a38ca9917d19c7206f1b52ed7bb 100644 GIT binary patch literal 3519 zcmeHJdsGuw8vjB938)Q^U|HO57C{Zr7OY@*>8_))0r9aI-sQ9#fvtktwIW>~U6`sB zGzGI@E7~3nFU!Mf7KIjHC$hX0Z7G6+(uzDqs8&cK$uKi_?*vOdd-jCg{ku2w&G~Zg zH~0R2zwf(u26du);J7j*EChHw9_+wBKs~Ttz9VHlz?wDS0{|kh<2eH#dpz#W=Yg%o z2Z-VGaUa)@o5FF@=l&?1Krq?woThO8$>-vLmstQ%sYYE83^qI-cRg)gS8c4y1aBe4gx1z zXOD%;>|7KH!ns=|uk6k(6nQSMtaXiOeeAU;F?El`^uoU(CS+Pi<Raamk6#O3TVC&YZ2P{^HA;bLanASKrXsbmeOEjkfliw>mm+-|6Y?>mL}rKlEUD zbWCqBJ{dQeDUKJ;;v}spX8*>^3G=eSIS>drUOb!a*!WHYTaSfy&dU_Sgsm=fU)e2^ zEYB^hthM)C6!F+KF}2lUwpT#Uyityth1q{cY|sD2Y>L=VyzanET$naad?z5lsFD@w zn)P&~gRJO%t2U>wD067jmoFmt+X+_2CU=PGI7)k+mR>@jzDjlzUl*7Ak$q#z@fLmA z2zxzMH||69sQcB%kk@`)r1GsPC{oG5it)$uR5GpUEYbXql_<*$h3DO6f62U%+A`j( z>{Souyn~=YT|u|hPIQ~-d`3?X2jz{k#5{}@LC`_n_f5VCQj1wPb_If{q%)Z41(j8{ zSPf#QtVo66!$oxIbfFGEi({pFeip~NII4%&h;DCEMSK!ihhX-SlX^n@c-2%% zBRKm@Ez_(vC7C@L#lb7MJRiyM4Xhc{U!+_7swUFhdXdt6t?Tz>J*vndPXu^#JXGIB z(3>r0Ra&|V!K3>Re(!3o?Xq!3jYgf+w{I zBCH?qy|{5e!uzxE9Vn$~e@j{M54S-SqJLp>%LE zf^S0ERS3F-v|=}c)@THWHO#lnf@f)~O_{9-3e- zjZBCu&7T)~<*oGgI({*#hUysK=hwBeo*%e!-B-6QXHg;T;M8(0s`%g4L7Ji=Nw zjdsY%#3NLz`jnE6eV#4%qc0-(0UwI{@dDupIGAkEkoNmXY5YQCG3)ywJ_YmHKat}# zWPKn4oB|7B@`be6C*JB)ZWLC_wE;oJDg=JS#J5DRI|7_ii+;qx^S%?3rd!$RSbSA4 zf}6OM@-@sOEFSp)fh8N#rZ-&~Rnkbvk}9%cD>;mFeyxQa7n?G51_V#5ukUpBdR^t} z^yH>nYSU>yCPHc6iQsMzu9OZ|g8)}vCH?L_%H2PG)>DGg(KnP-iEd1f;8Gpyp=Ah5 z;U$?yY7GJFD0j926O_giy)`;x7&&ks!FySBu#zGt%MMGmcPP2m5I8Ni!Q=R4G!V(q z)1Lil#7OM0chwMiOWPi?-c5Tiyp;_#6el3~l|R`L$VR^;f5s;d$7%Y)Z?YRqwZwsa z5=C5|L?H)jMg|xZamtwT|Nx^iX-jgssYb!($uTz8_N!0Bo zLe0_}Ia&0MS_E%&n4_v)pYhq(6UiJR)~x9hu=6pW#1#s;1gsd3ZfljhvK7*YORYp% zW{8{wv8FVbthNK{DILbVMO{J>VgitmRoTp*~zLiKWWOYHLxJu}K7rkBO<9lxlUID{W5^SJ;m> z6McmS~mAeY2=;cc|k){05d G?SBD=u^Hb0 literal 2677 zcmeH{ZBSHI7{{Nxci&lD?y_r^)9eT$tMmoXD8JlvSx6>FmK4gSNHA2IhS4y|nY?Mm z%blqm5=)#W+}$X$Y=;spre;Gyw6P%+0&yl{C4#jkc42W@?%vZm!Xi^2kiN#V_v}3P zoadhV|Novd7xkiFAtozhQwC5J1!edLXaI^VWqXPMHgASC0H6dpr2`sg6#1tqklo1v za%mdRk$D`}nCYC{SQ(H`&+AI9y1O~41FHNaK%))a05eD^id-{6%V-jEsg#x}!9fsZ=W!3U#zv9i_pcP%Y6$YnG6lY(gF2hB)jE0X294OYdDy|HmAhn4z`)%L~~ReWmBsG(qA zr+VqiwF3!0V(pHzdoyH zWFsh^uavB7M+$Jc|8oR}^NYLBCALUrE{(Z4AtSCIc6nVvyDgmCKlwI-zs3X_L1it1 z?EXm>!EUagCePPnG5XBL&}sMBas>SY2$)pS?)G!yPhy+Nb@L0J^^~)LXSks>1nvuB z+^(>h8$=Mchk0v$`?GqkO~ZN5BDhk5fU6MFJ3{3ME+d#SPw5@*cbZKI_+<#XUqW!a zUqqk{R@DWf5gbJ@Q8BUF!aZ1OMW9bcaOwtvWOrCJ`eIox7L$nJPwgK$#D`Nf6(ZPn zPAuf61lF^P>q-;1Am}sp)*FQVotZ**yZBj&a1p`nAlLc?f`$^I4#63=F)Jx}%VC=X zVRRt~LViAMa=2ed;7N$QvkuEOG+?{Xe!<}Vvig9@}eQV&_ zidio{4p(dl%IZ#x=Z#qOTx(W@S(7$+Ix}o_Pgdg-6Fc@C0bmPOD!3esh`73L6szLeiac7tBivP2 zsIf=ZC$)Bo1QnIlEG?8rT`q{IwQ33$ML`h+0^yNJB(Ix$XXhsAcDtLLv%9C=Jv+I7 zGWqA9JKy*J|I9zI1wIC2q83Fi0vLvY4Dt8mt9k zAvO;fxY#7X5o2622Co1qLdr*8VIAXY7>CQ_dm%`oQOKZt41&+)au8TP57EW4&^X|U z`Tj4zI^S!oY`tJovgEa02MdLh7nIfoEN?VS2}?-X?Jat4-1rI42Tq+9G(C9c>$7Ih znH#?F?TAH-mqbRzu83P1zv@@3H@vfP)8@o2zfDbh|AX|5%pH4vzc(lM!+nZFhmRaR zmjChbPgF&xPM;}0d+z*apO=+?QBhe{eYyTh!_{lozq-+M=iBDH_gd~h=;(aX)!n1* z?bAISHa;6MnJrcu%L_vx{H{5P{R1yC;>AHR;0ah>7$+SGSIpzT{HmA#e3@W<^4LkQ z?Gj2B94suY^PU{G+z^nE(kOav%FK?yrz|xGvww})?*E9{--vzB>jCgViOCUj#Q+EP zRFxuxw&Nk9Al2{O311gNNnQxC^#4XhmdRtQ2N7;L925i*1%rkSG%I_XaQo?JxKDLa1jvIh&WeXG-l4tfTGsS|=*fe@rB zsf*d?I@ER&f(pX)7(upU_zHJnV0HL`YQ$P^VB$Slh*-BV;r;AtKOrBEBNGl({}+Pf zOo)p=dFr{6{Qo9EhX-N_&2&P4cyY@a2nv=%&_0#ae@VsiA;60u*cEETk7nklga^lz zMnmw%X$Wefr3ojP%%u?Q`~)wbAvfA^i$4T2de&a=J@;0_Z2NFNW1X3$NV0Z^(!+_& zw{?W37lOx2?e)jgC#z0Tl6K<_In~u@noequkmj$7{(d#zjco@M?oj0o{&Jk1$zyor z&AC4!9%o|wzo2>?CV7t(hywD@T^ z$ReZF+%guWmaReFLu9PmAwsW{$mr7_L7@B$f?B0D_iiu*VN^Z4x`f!W0oM+YhH1?D z5e0dZF{p2yIl4!ByxMlJ@0IL#@pgPQ{fqjW5vv>3gYR^2TicpNDTg)>Y>F!BRBqL; z(Vgf=%&7fZ+7PRI zDCu?{sx6n=|2RwWb7@?megDwHOIpU5otCMgd+dE>vURo#eqHjE7k=>W{etF1ViNTS zy>sr1sSsq6;qOUSy;ZW%I<76(F}c^V>YYSY-Nb0(cavSj-x{!(*Kd zF|KE<$hU90d>db%lq*JC0@b@~R53MvcBh&;0zq3RLTt-qbbBSTc_@DWB+}CoSp@Nl z(r^*|0(TN%{WCgl=&jER$XQao@A;^7N>b$#* VVs*}~^aXLb(gSPoV{IjP|F3eCRlooM literal 2677 zcmeH{e^3-<7{{Nzy7XZM|X z_I;my_xt@k?=0#;ze21vV?zc|6a}UD0@M$MmeSpY02?>LDgaP|oYDe~GYaQGQy{yO z1LV*&o+I-(Y_Zchd9X1cou1d0*tGX@QU_Go34jJWx&da8P!zdmf|k)FQQ}bsyKC{KWO? zsV~1e(|GpW*WWaqztGZpvF+03AFf{O?E1O8=X!6y_vXNFgTD_AkNC#?%4G)E<*W{>Lg z_jafjFJIlC=p)wdIJ-Y%hyRnayTIF4Em=cC&fQ4{{lQ)T6dzXYao|tQB20!AB z?m!lTqPa@Rx@I^ZmydmpK!1K=_u0f2$jqiOJ11nswL|U^chF%E=k!g!gWxZ}KqIKA zL6Frq$s*Xr2PW(x1HM(zp!L!~nHt;Mrn1;Y} zL5$xSHgf|A!j3R+%WHd1$F-`tk+TS{6eHluh4l7N8G_3Qrp!}1r{~=!BLaR2g07bl z9P1MiXo8isffxiw5KNR$th8_sm)H>Kk`bJ`fgsrv77e~Qb_9z_Lhz^Nk8I+jPBa!E z*m_PZ;HCuDyMpUX6E`8~HT2Z$g?$~FLROpjS+Q^t!R;W|@)QDBu~3WP4BKEe1#da+ zvmlJl2SI3z4;!7HHxPIeBj2pWa`i4O7XwLQ!`_20b>!9S@FZiJ2G_}Y8x4UB1cR9f zzTNcW0n@k>Q@p!DxsX#Qy847mSZ(E`+j6klmMNsWBi!(Bf$!wHFn}6^=wsImxv%mV zV#U!&---;rZT3(d=TAWJOVZ$X`j8FxQ0s{>Gm9pyW=pZSgPUp*x}6@Yu}+N7o#~s4 zYb|0&@Nu|eM^IXOd^~s9qT^bu5oV2=;OWe;%`;hrPfVQuc>=&5tjvq_%rQUr%}to! ttsy~ebn-CQ2oI3I=p*f8AI?=t)`g{{`kE&2!u?b)?EP>4{|_eU`rnV2H6Z{1 diff --git a/src/resources/four-colors-rotate-90-cw.jpg b/src/resources/four-colors-rotate-90-cw.jpg index 15840c2269693b097a885d70f494cdb7d99ba1f7..2e2beed8e491e6b2c5f6898dfef88b64f273f239 100644 GIT binary patch literal 2780 zcmex=kRWME@p zU}j`s1+y6#!kC$XVjyuK2o!*@L7YHA1{R38P)VQ=f(=r~z}U8cfnj6+|Jw}C3`~rS zAOr=>tjr+5#=^qP%Ff2l4#FIq+?*U7TpaA|T)bRd+&nxo=LsQGd)Xdz%(#qMz)y>_*(Y^YsE>`O44~(BS|6xR+oBN8z%2xsyT_xof0Rr>M*hBpVI5!G~7XZM|X z_I;my_xt@k?=0#;ze21vV?zc|6a}UD0@M$MmeSpY02?>LDgaP|oYDe~GYaGYGzGFd zIY16g<2f>q!xlT8lLs3E(&>3^iA{SiCv`xTod9UCqZ?ob2}O~6CTJN=LN1lkGKE~B zAVV1y9i>#NlnR9^Mx}~Y<4{B`(!{72k(_KoUMFwiTdhv$ccj7+;Md7V7Ywp|gw zN0)M_%ArhH-_Wej?igL3n!opOlxpz?-`3o(r7uzmf{^9Di&aR)kd#?BPdv6Z>Hu(F{@Q82BKOUI49h?ji zUleX(B&Lo1!xw{nNpKIO3gU~B?8k#@CR-TEp=LvvI@YWAo; ze{Y9s@$%LEi9TZOjN8##;MN-+YiTu5&Zl_9u{V9GqDb9&xwG9uuYAn1A- z!LdFOfhJg48;C)01i?i4#7YbIaET3pE*Zh88wip;VbS1=V@I%Uai>O^A! zg01Jo0&YrRy(_rRG;tGxUPDj4Uf9=>DP*;YpA`!i5!?=PEl(kE6$`Zp&ae$uQ}CA4 zJ`2L=d=P}j_^{FGc>{qrG4jn?ELZQsaxst;Htaq4Qb%6B4o@gsax6u&DKronz z;M+|<9x#nNF~z$ZlnXg^qN`81gw<9~x-AE*ZJ9#4JHieB7WhuC3j?Szh(31Bkozi+ zAyyoX^sUI?+hz~dasC7Zza$NQrw`e154D~MGqY&IYPJ-MJGiM9q1)-P8tcUP+?l?) zxYicbE diff --git a/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts b/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts index 34997b01f86d..5036c8ef20bc 100644 --- a/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts @@ -10,17 +10,9 @@ TODO: Test ImageBitmap generated from all possible ImageBitmapSource, relevant I TODO: Test zero-sized copies from all sources (just make sure params cover it) (e.g. 0x0, 0x4, 4x0). `; -import { getResourcePath } from '../../../common/framework/resources.js'; import { makeTestGroup } from '../../../common/framework/test_group.js'; -import { raceWithRejectOnTimeout } from '../../../common/util/util.js'; import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js'; import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js'; -import { - convertToUnorm8, - kImageNames, - kImageInfo, - kImageExpectedColors, -} from '../../web_platform/util.js'; import { kTestColorsAll, kTestColorsOpaque, makeTestColorsTexelView } from './util.js'; @@ -549,121 +541,3 @@ g.test('copy_subrect_from_2D_Canvas') { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 } ); }); - -g.test('from_rotated_image') - .desc( - ` - Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly. - - It creates ImageBitmap with images under Resource folder, with orientation flag set to - 'from-image'. - - 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. - - TODO: all valid imageOrientation flags tests should be added - TODO: all valid dstColorFormat tests should be added. - ` - ) - .params(u => - u // - .combine('imageName', kImageNames) - .combine('srcDoFlipYDuringCopy', [true, false]) - ) - .fn(async t => { - const { imageName, srcDoFlipYDuringCopy } = t.params; - const kColorFormat = 'rgba8unorm'; - - // Load image. - const image = new Image(); - const imageUrl = getResourcePath(imageName); - image.src = imageUrl; - await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); - - const imageBitmap = await createImageBitmap(image, { - imageOrientation: 'from-image', - }); - - const width = imageBitmap.width; - const height = imageBitmap.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: imageBitmap, - 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]), - }, - ]); - } - }); diff --git a/src/webgpu/web_platform/copyToTexture/image.spec.ts b/src/webgpu/web_platform/copyToTexture/image.spec.ts index 9d90f8d0e412..7bb577ae5d07 100644 --- a/src/webgpu/web_platform/copyToTexture/image.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image.spec.ts @@ -2,17 +2,10 @@ export const description = ` copyExternalImageToTexture from HTMLImageElement source. `; -import { getResourcePath } from '../../../common/framework/resources.js'; import { makeTestGroup } from '../../../common/framework/test_group.js'; import { raceWithRejectOnTimeout } from '../../../common/util/util.js'; import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js'; import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js'; -import { - convertToUnorm8, - kImageNames, - kImageInfo, - kImageExpectedColors, -} from '../../web_platform/util.js'; import { kTestColorsOpaque, makeTestColorsTexelView } from './util.js'; @@ -346,113 +339,3 @@ g.test('copy_subrect_from_2D_Canvas') { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 } ); }); - -g.test('from_rotated_image') - .desc( - ` - Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly. - - It creates Images with images under Resource 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. - ` - ) - .params(u => - u // - .combine('imageName', kImageNames) - .combine('srcDoFlipYDuringCopy', [true, false]) - ) - .fn(async t => { - const { imageName, srcDoFlipYDuringCopy } = t.params; - const kColorFormat = 'rgba8unorm'; - - // Load image. - const image = new Image(); - const imageUrl = getResourcePath(imageName); - image.src = imageUrl; - await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); - const width = image.width; - const height = image.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: image, - 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]), - }, - ]); - } - }); diff --git a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts new file mode 100644 index 000000000000..0bb922100777 --- /dev/null +++ b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts @@ -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 Images with images under Resource 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. + ` + ) + .params(u => + u // + .combine('imageName', kImageNames) + .combine('objectTypeFromFile', kObjectTypeFromFiles) + .combine('srcDoFlipYDuringCopy', [true, false]) + ) + .fn(async t => { + const { imageName, objectTypeFromFile, srcDoFlipYDuringCopy } = t.params; + const kColorFormat = 'rgba8unorm'; + + // Load image file. + const source = await GetSourceFromImageFile(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]), + }, + ]); + } + }); diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts index dd7c0ed0085c..420f5357c126 100644 --- a/src/webgpu/web_platform/util.ts +++ b/src/webgpu/web_platform/util.ts @@ -360,46 +360,6 @@ export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo); export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const; -export const kImageInfo = makeTable({ - table: { - 'four-colors.jpg': { - display: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - }, - 'four-colors-rotate-90-cw.jpg': { - display: { - topLeftColor: 'blue', - topRightColor: 'yellow', - bottomLeftColor: 'green', - bottomRightColor: 'red', - }, - }, - 'four-colors-rotate-180-cw.jpg': { - display: { - topLeftColor: 'green', - topRightColor: 'blue', - bottomLeftColor: 'red', - bottomRightColor: 'yellow', - }, - }, - 'four-colors-rotate-270-cw.jpg': { - display: { - topLeftColor: 'red', - topRightColor: 'green', - bottomLeftColor: 'yellow', - bottomRightColor: 'blue', - }, - }, - }, -} as const); - -type ImageName = keyof typeof kImageInfo; -export const kImageNames: readonly ImageName[] = keysOf(kImageInfo); - /** * Starts playing a video and waits for it to be consumable. * Returns a promise which resolves after `callback` (which may be async) completes. @@ -656,3 +616,124 @@ export async function captureCameraFrame(test: GPUTest): Promise { return frame; } + +export const kImageInfo = makeTable({ + table: { + 'four-colors.jpg': { + coded: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + }, + 'four-colors-rotate-90-cw.jpg': { + coded: { + topLeftColor: 'red', + topRightColor: 'green', + bottomLeftColor: 'yellow', + bottomRightColor: 'blue', + }, + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + }, + 'four-colors-rotate-180-cw.jpg': { + coded: { + topLeftColor: 'green', + topRightColor: 'blue', + bottomLeftColor: 'red', + bottomRightColor: 'yellow', + }, + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + }, + 'four-colors-rotate-270-cw.jpg': { + coded: { + topLeftColor: 'blue', + topRightColor: 'yellow', + bottomLeftColor: 'green', + bottomRightColor: 'red', + }, + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, + }, + }, +} as const); + +type ImageName = keyof typeof kImageInfo; +export const kImageNames: readonly ImageName[] = keysOf(kImageInfo); + +type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number]; +export const kObjectTypeFromFiles = ['imageBitmap', 'image', 'blob'] as const; + +/** + * Load image file(e.g. *.jpg) from ImageBitmap, blob or HTMLImageElement. And + * convert the result to valid source that GPUCopyExternalImageSource supported. + * + * @param imageName: Required image name + * @param objectTypeFromFile: The object to load image file, e.g. ImageBitmap, blob + * + */ +export async function GetSourceFromImageFile( + imageName: ImageName, + objectTypeFromFile: ObjectTypeFromFile +): Promise { + const image = new Image(); + const imageUrl = getResourcePath(imageName); + let imageBitmap: ImageBitmap; + + if (objectTypeFromFile === 'blob') { + // Load image file through fetch. + const blob = await loadXHR(imageUrl); + imageBitmap = await createImageBitmap(blob, { imageOrientation: 'from-image' }); + return imageBitmap; + } else { + // Load image file through HTMLImageElement. + image.src = imageUrl; + await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); + if (objectTypeFromFile === 'image') { + return image; + } + + imageBitmap = await createImageBitmap(image, { imageOrientation: 'from-image' }); + return imageBitmap; + } +} + +// Use fetch() to load image file as blob +function loadXHR(url: string): Promise { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', url); + xhr.responseType = 'blob'; + xhr.onerror = function () { + reject(new Error('Network error.')); + }; + xhr.onload = function () { + if (xhr.status === 200) { + resolve(xhr.response); + } else { + reject(new Error('Loading error:' + xhr.statusText)); + } + }; + xhr.send(); + }); +} From 61a3b1ac41057b58edbe4a45f4b28a4c2ba7c30a Mon Sep 17 00:00:00 2001 From: shaoboyan091 Date: Mon, 6 Jan 2025 09:31:35 +0800 Subject: [PATCH 4/9] Update src/resources/README.md Co-authored-by: Kai Ninomiya --- src/resources/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resources/README.md b/src/resources/README.md index 8b8f38ba2969..2bc89869a501 100644 --- a/src/resources/README.md +++ b/src/resources/README.md @@ -99,17 +99,17 @@ Use a image tool (e.g. "Paint" app on Windows) to create four-colors.jpg from fo // 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.jpg -o .\four-colors-rotate-90-cw.jpg -rm four-clors-hard-rotate-90-ccw.jpg +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.jpg -o .\four-colors-rotate-180-cw.jpg -rm four-clors-hard-rotate-180-ccw.jpg +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.jpg -o .\four-colors-rotate-270-cw.jpg +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 ``` From be7679a388b9f45cd868cb7c85a593c8602336f7 Mon Sep 17 00:00:00 2001 From: shaoboyan091 Date: Mon, 6 Jan 2025 09:33:16 +0800 Subject: [PATCH 5/9] Update src/webgpu/web_platform/util.ts Co-authored-by: Kai Ninomiya --- src/webgpu/web_platform/util.ts | 69 +++++++-------------------------- 1 file changed, 13 insertions(+), 56 deletions(-) diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts index 420f5357c126..ee4a7f1bbc19 100644 --- a/src/webgpu/web_platform/util.ts +++ b/src/webgpu/web_platform/util.ts @@ -617,64 +617,21 @@ export async function captureCameraFrame(test: GPUTest): Promise { return frame; } +const kFourColorsInfo = { + display: { + topLeftColor: 'yellow', + topRightColor: 'red', + bottomLeftColor: 'blue', + bottomRightColor: 'green', + }, +} as const; + export const kImageInfo = makeTable({ table: { - 'four-colors.jpg': { - coded: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - display: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - }, - 'four-colors-rotate-90-cw.jpg': { - coded: { - topLeftColor: 'red', - topRightColor: 'green', - bottomLeftColor: 'yellow', - bottomRightColor: 'blue', - }, - display: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - }, - 'four-colors-rotate-180-cw.jpg': { - coded: { - topLeftColor: 'green', - topRightColor: 'blue', - bottomLeftColor: 'red', - bottomRightColor: 'yellow', - }, - display: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - }, - 'four-colors-rotate-270-cw.jpg': { - coded: { - topLeftColor: 'blue', - topRightColor: 'yellow', - bottomLeftColor: 'green', - bottomRightColor: 'red', - }, - display: { - topLeftColor: 'yellow', - topRightColor: 'red', - bottomLeftColor: 'blue', - bottomRightColor: 'green', - }, - }, + '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); From 34d415037647a430a77890433735340d174ba67a Mon Sep 17 00:00:00 2001 From: shaoboyan091 Date: Mon, 6 Jan 2025 09:33:41 +0800 Subject: [PATCH 6/9] Update src/webgpu/web_platform/util.ts Co-authored-by: Kai Ninomiya --- src/webgpu/web_platform/util.ts | 61 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts index ee4a7f1bbc19..c4755dcf02a2 100644 --- a/src/webgpu/web_platform/util.ts +++ b/src/webgpu/web_platform/util.ts @@ -639,58 +639,39 @@ type ImageName = keyof typeof kImageInfo; export const kImageNames: readonly ImageName[] = keysOf(kImageInfo); type ObjectTypeFromFile = (typeof kObjectTypeFromFiles)[number]; -export const kObjectTypeFromFiles = ['imageBitmap', 'image', 'blob'] as const; +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. - * - * @param imageName: Required image name - * @param objectTypeFromFile: The object to load image file, e.g. ImageBitmap, blob - * */ export async function GetSourceFromImageFile( imageName: ImageName, objectTypeFromFile: ObjectTypeFromFile ): Promise { - const image = new Image(); const imageUrl = getResourcePath(imageName); - let imageBitmap: ImageBitmap; - if (objectTypeFromFile === 'blob') { - // Load image file through fetch. - const blob = await loadXHR(imageUrl); - imageBitmap = await createImageBitmap(blob, { imageOrientation: 'from-image' }); - return imageBitmap; - } else { - // Load image file through HTMLImageElement. - image.src = imageUrl; - await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); - if (objectTypeFromFile === 'image') { - return image; + switch (objectTypeFromFile) { + case 'ImageBitmap-from-Blob': { + // Load image file through fetch. + const response = await fetch(imageUrl); + return createImageBitmap(await response.blob()); } + case 'ImageBitmap-from-Image': + case 'Image': { + // Load image file through HTMLImageElement. + const image = new Image(); + image.src = imageUrl; + if (objectTypeFromFile === 'Image') { + await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); + return image; + } - imageBitmap = await createImageBitmap(image, { imageOrientation: 'from-image' }); - return imageBitmap; + return createImageBitmap(image); + } } } - -// Use fetch() to load image file as blob -function loadXHR(url: string): Promise { - return new Promise((resolve, reject) => { - const xhr = new XMLHttpRequest(); - xhr.open('GET', url); - xhr.responseType = 'blob'; - xhr.onerror = function () { - reject(new Error('Network error.')); - }; - xhr.onload = function () { - if (xhr.status === 200) { - resolve(xhr.response); - } else { - reject(new Error('Loading error:' + xhr.statusText)); - } - }; - xhr.send(); - }); -} From 00a07f5885f5af815535f68b85d337ea5bc30afa Mon Sep 17 00:00:00 2001 From: shaoboyan Date: Mon, 6 Jan 2025 13:54:56 +0800 Subject: [PATCH 7/9] fix worker issue and createImageBitmap with 0 width image issue --- .../copyToTexture/image_file.spec.ts | 2 +- src/webgpu/web_platform/util.ts | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts index 0bb922100777..8a8e83358252 100644 --- a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts @@ -46,7 +46,7 @@ g.test('from_orientation_metadata_file') const kColorFormat = 'rgba8unorm'; // Load image file. - const source = await GetSourceFromImageFile(imageName, objectTypeFromFile); + const source = await GetSourceFromImageFile(t, imageName, objectTypeFromFile); const width = source.width; const height = source.height; diff --git a/src/webgpu/web_platform/util.ts b/src/webgpu/web_platform/util.ts index c4755dcf02a2..66a2163b7388 100644 --- a/src/webgpu/web_platform/util.ts +++ b/src/webgpu/web_platform/util.ts @@ -650,6 +650,7 @@ export const kObjectTypeFromFiles = [ * convert the result to valid source that GPUCopyExternalImageSource supported. */ export async function GetSourceFromImageFile( + test: GPUTest, imageName: ImageName, objectTypeFromFile: ObjectTypeFromFile ): Promise { @@ -657,17 +658,30 @@ export async function GetSourceFromImageFile( switch (objectTypeFromFile) { case 'ImageBitmap-from-Blob': { + // MAINTENANCE_TODO: resource folder path when using service worker is not correct. Return + // 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') { - await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout'); return image; } From d3169aea3db459a18310451460f90c57531addb9 Mon Sep 17 00:00:00 2001 From: shaoboyan091 Date: Mon, 6 Jan 2025 14:27:18 +0800 Subject: [PATCH 8/9] Update src/webgpu/web_platform/copyToTexture/image_file.spec.ts Co-authored-by: Kai Ninomiya --- src/webgpu/web_platform/copyToTexture/image_file.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts index 8a8e83358252..fc5e3eda0f51 100644 --- a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts @@ -20,7 +20,7 @@ g.test('from_orientation_metadata_file') ` Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly. - It creates Images with images under Resource folder. + 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. From 4bddbb250937fcf539712c10ecb91fc02c5fde44 Mon Sep 17 00:00:00 2001 From: Kai Ninomiya Date: Mon, 6 Jan 2025 17:54:36 -0800 Subject: [PATCH 9/9] Apply suggestions from code review --- src/webgpu/web_platform/copyToTexture/image_file.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts index fc5e3eda0f51..aa9a16e16dce 100644 --- a/src/webgpu/web_platform/copyToTexture/image_file.spec.ts +++ b/src/webgpu/web_platform/copyToTexture/image_file.spec.ts @@ -33,6 +33,7 @@ g.test('from_orientation_metadata_file') - 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 =>