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 a call-out to Web Codecs API during decoding #1018

Merged
merged 10 commits into from
May 24, 2021
Merged
Show file tree
Hide file tree
Changes from 6 commits
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
4 changes: 2 additions & 2 deletions src/client/lazy-app/Compress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ 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.
}
return await abortable(signal, builtinDecode(blob));
// Otherwise fall through and try built-in decoding for a laugh.
return await builtinDecode(signal, blob, mimeType);
} catch (err) {
if (err.name === 'AbortError') throw err;
console.log(err);
Expand Down
47 changes: 41 additions & 6 deletions src/client/lazy-app/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as WebCodecs from '../util/web-codecs';

/**
* Compare two objects, returning a boolean indicating if
* they have the same properties and strictly equal values.
Expand Down Expand Up @@ -192,17 +195,35 @@ interface DrawableToImageDataOptions {
sh?: number;
}

function getWidth(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayWidth' in drawable) {
return drawable.displayWidth;
}
return drawable.width;
}

function getHeight(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayHeight' in drawable) {
return drawable.displayHeight;
}
return drawable.height;
}

export function drawableToImageData(
drawable: ImageBitmap | HTMLImageElement,
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
opts: DrawableToImageDataOptions = {},
): ImageData {
const {
width = drawable.width,
height = drawable.height,
width = getWidth(drawable),
height = getHeight(drawable),
sx = 0,
sy = 0,
sw = drawable.width,
sh = drawable.height,
sw = getWidth(drawable),
sh = getHeight(drawable),
} = opts;

// Make canvas same size as image
Expand All @@ -216,13 +237,27 @@ export function drawableToImageData(
return ctx.getImageData(0, 0, width, height);
}

export async function builtinDecode(blob: Blob): Promise<ImageData> {
export async function builtinDecode(
signal: AbortSignal,
blob: Blob,
mimeType: string,
): Promise<ImageData> {
// If WebCodecs are supported, use that.
if (await WebCodecs.isTypeSupported(mimeType)) {
if (signal.aborted) return Promise.reject('Aborted');
try {
return await WebCodecs.decode(blob, mimeType);
} catch (e) {}
surma marked this conversation as resolved.
Show resolved Hide resolved
}
if (signal.aborted) return Promise.reject('Aborted');

// Prefer createImageBitmap as it's the off-thread option for Firefox.
const drawable =
'createImageBitmap' in self
? await createImageBitmap(blob)
: await blobToImg(blob);

if (signal.aborted) return Promise.reject('Aborted');
surma marked this conversation as resolved.
Show resolved Hide resolved
return drawableToImageData(drawable);
}

Expand Down
26 changes: 26 additions & 0 deletions src/client/lazy-app/util/web-codecs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { drawableToImageData } from 'client/lazy-app/util';

const hasImageDecoder = typeof ImageDecoder !== 'undefined';
export async function isTypeSupported(mimeType: string): Promise<boolean> {
if (!hasImageDecoder) {
return false;
}
return ImageDecoder.isTypeSupported(mimeType);
}
export async function decode(
blob: Blob | File,
mimeType: string,
): Promise<ImageData> {
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!,
jakearchibald marked this conversation as resolved.
Show resolved Hide resolved
});
const { image } = await decoder.decode();
return drawableToImageData(image);
}
60 changes: 60 additions & 0 deletions src/client/lazy-app/util/web-codecs/missing-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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;
}

// I didn’t do all the types because the class is kinda complex.
// I focused on what we need.
// See https://w3c.github.io/webcodecs/#videoframe
declare class VideoFrame {
displayWidth: number;
displayHeight: number;
}

// Add VideoFrame to canvas’ drawImage()
interface CanvasDrawImage {
drawImage(
image: CanvasImageSource | VideoFrame,
dx: number,
dy: number,
): void;
drawImage(
image: CanvasImageSource | VideoFrame,
dx: number,
dy: number,
dw: number,
dh: number,
): void;
drawImage(
image: CanvasImageSource | VideoFrame,
sx: number,
sy: number,
sw: number,
sh: number,
dx: number,
dy: number,
dw: number,
dh: number,
): void;
}

declare class ImageDecoder {
static isTypeSupported(type: string): Promise<boolean>;
constructor(desc: ImageDecoderInit);
decode(opts?: Partial<ImageDecodeOptions>): Promise<ImageDecodeResult>;
}