From eb8204d69b7dab94cb1caf6c26664a16dcc843dc Mon Sep 17 00:00:00 2001 From: Surma Date: Tue, 18 May 2021 19:53:39 +0100 Subject: [PATCH] Add a call-out to Web Codecs API --- src/client/lazy-app/Compress/index.tsx | 7 ++++- src/client/lazy-app/util/web-codecs/index.ts | 26 +++++++++++++++++ .../util/web-codecs/missing-types.d.ts | 28 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/client/lazy-app/util/web-codecs/index.ts create mode 100644 src/client/lazy-app/util/web-codecs/missing-types.d.ts diff --git a/src/client/lazy-app/Compress/index.tsx b/src/client/lazy-app/Compress/index.tsx index 19e30fcb..cb5b0ee8 100644 --- a/src/client/lazy-app/Compress/index.tsx +++ b/src/client/lazy-app/Compress/index.tsx @@ -34,6 +34,7 @@ import { resize } from 'features/processors/resize/client'; import type SnackBarElement from 'shared/custom-els/snack-bar'; import { Arrow, ExpandIcon } from '../icons'; import { generateCliInvocation } from '../util/cli'; +import * as WebCodecs from '../util/web-codecs'; export type OutputType = EncoderType | 'identity'; @@ -109,8 +110,12 @@ async function decodeImage( if (mimeType === 'image/webp2') { return await workerBridge.wp2Decode(signal, blob); } - // If it's not one of those types, fall through and try built-in decoding for a laugh. } + // If none of those work, let’s see if Web Codecs API is available + if (await WebCodecs.isTypeSupported(mimeType)) { + return await abortable(signal, WebCodecs.decode(blob, mimeType)); + } + // Otherwise fall through and try built-in decoding for a laugh. return await abortable(signal, builtinDecode(blob)); } catch (err) { if (err.name === 'AbortError') throw err; diff --git a/src/client/lazy-app/util/web-codecs/index.ts b/src/client/lazy-app/util/web-codecs/index.ts new file mode 100644 index 00000000..1d946a1f --- /dev/null +++ b/src/client/lazy-app/util/web-codecs/index.ts @@ -0,0 +1,26 @@ +import { drawableToImageData } from 'client/lazy-app/util'; + +const hasImageDecoder = typeof ImageDecoder !== 'undefined'; +export async function isTypeSupported(mimeType: string): Promise { + if (!hasImageDecoder) { + return false; + } + return ImageDecoder.isTypeSupported(mimeType); +} +export async function decode( + blob: Blob | File, + mimeType: string, +): Promise { + if (!hasImageDecoder) { + throw Error( + `This browser does not support ImageDecoder. This function should not have been called.`, + ); + } + const decoder = new ImageDecoder({ + type: mimeType, + // Non-obvious way to turn an Blob into a ReadableStream + data: new Response(blob).body!, + }); + const result = await decoder.decode(); + return drawableToImageData(result.image); +} diff --git a/src/client/lazy-app/util/web-codecs/missing-types.d.ts b/src/client/lazy-app/util/web-codecs/missing-types.d.ts new file mode 100644 index 00000000..e5e72cc1 --- /dev/null +++ b/src/client/lazy-app/util/web-codecs/missing-types.d.ts @@ -0,0 +1,28 @@ +interface ImageDecoderInit { + type: string; + data: BufferSource | ReadableStream; + premultiplyAlpha?: PremultiplyAlpha; + colorSpaceConversion?: ColorSpaceConversion; + desiredWidth?: number; + desiredHeight?: number; + preferAnimation?: boolean; +} + +interface ImageDecodeOptions { + frameIndex: number; + completeFramesOnly: boolean; +} + +interface ImageDecodeResult { + image: VideoFrame; + complete: boolean; +} + +// Absolutely not correct, but it’s all we need and I’m lazy. +type VideoFrame = ImageBitmap; + +declare class ImageDecoder { + static isTypeSupported(type: string): Promise; + constructor(desc: ImageDecoderInit); + decode(opts?: Partial): Promise; +}