Integrate WASI module with Squoosh UI

This commit is contained in:
Surma
2021-05-04 13:00:33 +01:00
parent deee258820
commit be3688c8d3
5 changed files with 129 additions and 11 deletions

View File

@@ -13,8 +13,9 @@
import {
EncodeOptions,
MozJpegColorSpace,
MozJPEGModuleExports,
} from 'codecs/mozjpeg/enc/mozjpeg_enc';
export { EncodeOptions, MozJpegColorSpace };
export { EncodeOptions, MozJpegColorSpace, MozJPEGModuleExports };
export const label = 'MozJPEG';
export const mimeType = 'image/jpeg';

View File

@@ -10,23 +10,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import mozjpeg_enc, { MozJPEGModule } from 'codecs/mozjpeg/enc/mozjpeg_enc';
import { EncodeOptions } from '../shared/meta';
import { MozJPEGModuleExports, EncodeOptions } from '../shared/meta';
import wasmUrl from 'url:codecs/mozjpeg/enc/mozjpeg_enc.wasm';
import { initEmscriptenModule } from 'features/worker-utils';
import { instantiateStreaming } from 'features/worker-utils';
import {
makeEverythingElseThrow,
makeWasiEnv,
} from 'features/worker-utils/wasi-utils';
let emscriptenModule: Promise<MozJPEGModule>;
const instancePromise: Promise<WebAssembly.Instance> = instantiateStreaming(
fetch(wasmUrl),
{
wasi_snapshot_preview1: makeEverythingElseThrow(makeWasiEnv()),
},
).then(({ instance }) => instance);
export default async function encode(
data: ImageData,
options: EncodeOptions,
): Promise<ArrayBuffer> {
if (!emscriptenModule) {
emscriptenModule = initEmscriptenModule(mozjpeg_enc, wasmUrl);
}
const instance = await instancePromise;
const exports: MozJPEGModuleExports = (instance.exports as unknown) as MozJPEGModuleExports;
const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height, options);
for (const [opt, value] of Object.entries(options)) {
// @ts-ignore Cant be bothered to make these typings works
exports[`set_opts_${opt}`](value);
}
const inPtr = exports.alloc(data.data.byteLength);
new Uint8ClampedArray(exports.memory.buffer, inPtr, data.data.length).set(
data.data,
);
const resultPtr = exports.encode(inPtr, data.width, data.height);
const dv = new DataView(exports.memory.buffer);
const length = dv.getUint32(resultPtr, true);
const outPtr = dv.getUint32(resultPtr + 4, true);
const result = new Uint8Array(exports.memory.buffer, outPtr, length).slice();
exports.dealloc(inPtr);
exports.dealloc(outPtr);
exports.dealloc(resultPtr);
// wasm cant run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return resultView.buffer as ArrayBuffer;
return result.buffer;
}

View File

@@ -29,3 +29,16 @@ export function initEmscriptenModule<T extends EmscriptenWasm.Module>(
export function blobToArrayBuffer(blob: Blob): Promise<ArrayBuffer> {
return new Response(blob).arrayBuffer();
}
export async function instantiateStreaming(
resp: Response | PromiseLike<Response>,
importObj?: WebAssembly.Imports,
): Promise<WebAssembly.WebAssemblyInstantiatedSource> {
if (WebAssembly.instantiateStreaming) {
return WebAssembly.instantiateStreaming(resp, importObj);
}
return WebAssembly.instantiate(
await Promise.resolve(resp).then((r) => r.arrayBuffer()),
importObj,
);
}

View File

@@ -0,0 +1,32 @@
/**
* Copyright 2021 Google Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function makeEverythingElseThrow(obj: { [x: string]: {} }): {} {
return new Proxy(obj, {
get(target, prop: string) {
if (prop in target) {
return target[prop];
}
return () => {
throw Error(`${prop} not implemented`);
};
},
});
}
export function makeWasiEnv() {
return {
environ_sizes_get: () => 0,
environ_get: () => 0,
};
}