Basic webp integration (#103)

This commit is contained in:
Jake Archibald
2018-07-17 10:37:42 +01:00
committed by GitHub
parent 526520c399
commit cc9d01a9ab
7 changed files with 112 additions and 5 deletions

1
codecs/webp_enc/webp_enc.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module;

View File

@@ -1,5 +1,6 @@
import * as mozJPEG from './mozjpeg/encoder';
import * as identity from './identity/encoder';
import * as mozJPEG from './mozjpeg/encoder';
import * as webP from './webp/encoder';
import * as browserPNG from './browser-png/encoder';
import * as browserJPEG from './browser-jpeg/encoder';
import * as browserWebP from './browser-webp/encoder';
@@ -14,12 +15,12 @@ export interface EncoderSupportMap {
}
export type EncoderState =
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState |
identity.EncoderState | mozJPEG.EncoderState | webP.EncoderState | browserPNG.EncoderState |
browserJPEG.EncoderState | browserWebP.EncoderState | browserGIF.EncoderState |
browserTIFF.EncoderState | browserJP2.EncoderState | browserBMP.EncoderState |
browserPDF.EncoderState;
export type EncoderOptions =
identity.EncodeOptions | mozJPEG.EncodeOptions | browserPNG.EncodeOptions |
identity.EncodeOptions | mozJPEG.EncodeOptions | webP.EncodeOptions | browserPNG.EncodeOptions |
browserJPEG.EncodeOptions | browserWebP.EncodeOptions | browserGIF.EncodeOptions |
browserTIFF.EncodeOptions | browserJP2.EncodeOptions | browserBMP.EncodeOptions |
browserPDF.EncodeOptions;
@@ -28,6 +29,7 @@ export type EncoderType = keyof typeof encoderMap;
export const encoderMap = {
[identity.type]: identity,
[mozJPEG.type]: mozJPEG,
[webP.type]: webP,
[browserPNG.type]: browserPNG,
[browserJPEG.type]: browserJPEG,
[browserWebP.type]: browserWebP,

View File

@@ -0,0 +1,80 @@
import webp_enc from '../../../codecs/webp_enc/webp_enc';
// Using require() so TypeScript doesnt complain about this not being a module.
import { EncodeOptions } from './encoder';
const wasmBinaryUrl = require('../../../codecs/webp_enc/webp_enc.wasm');
// API exposed by wasm module. Details in the codecs README.
interface ModuleAPI {
version(): number;
create_buffer(width: number, height: number): number;
destroy_buffer(pointer: number): void;
encode(buffer: number, width: number, height: number, quality: number): void;
free_result(): void;
get_result_pointer(): number;
get_result_size(): number;
}
export default class WebPEncoder {
private emscriptenModule: Promise<EmscriptenWasm.Module>;
private api: Promise<ModuleAPI>;
constructor() {
this.emscriptenModule = new Promise((resolve) => {
const m = webp_enc({
// Just to be safe, dont automatically invoke any wasm functions
noInitialRun: false,
locateFile(url: string): string {
// Redirect the request for the wasm binary to whatever webpack gave us.
if (url.endsWith('.wasm')) {
return wasmBinaryUrl;
}
return url;
},
onRuntimeInitialized() {
// An Emscripten is a then-able that, for some reason, `then()`s itself,
// causing an infite loop when you wrap it in a real promise. Deleten the `then`
// prop solves this for now.
// See: https://github.com/kripken/emscripten/blob/incoming/src/postamble.js#L129
// TODO(surma@): File a bug with Emscripten on this.
delete (m as any).then;
resolve(m);
},
});
});
this.api = (async () => {
// Not sure why, but TypeScript complains that I am using
// `emscriptenModule` before its getting assigned, which is clearly not
// true :shrug: Using `any`
const module = await (this as any).emscriptenModule as EmscriptenWasm.Module;
return {
version: module.cwrap('version', 'number', []),
create_buffer: module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: module.cwrap('destroy_buffer', '', ['number']),
encode: module.cwrap('encode', '', ['number', 'number', 'number', 'number']),
free_result: module.cwrap('free_result', '', []),
get_result_pointer: module.cwrap('get_result_pointer', 'number', []),
get_result_size: module.cwrap('get_result_size', 'number', []),
};
})();
}
async encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> {
const m = await this.emscriptenModule;
const api = await this.api;
const p = api.create_buffer(data.width, data.height);
m.HEAP8.set(data.data, p);
api.encode(p, data.width, data.height, options.quality);
const resultPointer = api.get_result_pointer();
const resultSize = api.get_result_size();
const resultView = new Uint8Array(m.HEAP8.buffer, resultPointer, resultSize);
const result = new Uint8Array(resultView);
api.free_result();
api.destroy_buffer(p);
// wasm cant run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return result.buffer as ArrayBuffer;
}
}

View File

@@ -0,0 +1,16 @@
import EncoderWorker from './Encoder.worker';
export interface EncodeOptions { quality: number; }
export interface EncoderState { type: typeof type; options: EncodeOptions; }
export const type = 'webp';
export const label = 'WebP';
export const mimeType = 'image/webp';
export const extension = 'webp';
export const defaultOptions: EncodeOptions = { quality: 7 };
export async function encode(data: ImageData, options: EncodeOptions) {
// We need to await this because it's been comlinked.
const encoder = await new EncoderWorker();
return encoder.encode(data, options);
}

View File

@@ -0,0 +1,3 @@
import qualityOption from '../generic/quality-option';
export default qualityOption();

View File

@@ -9,6 +9,7 @@ import { FileDropEvent } from './custom-els/FileDrop';
import './custom-els/FileDrop';
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
import * as webP from '../../codecs/webp/encoder';
import * as identity from '../../codecs/identity/encoder';
import * as browserPNG from '../../codecs/browser-png/encoder';
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
@@ -64,6 +65,7 @@ async function compressImage(
const compressedData = await (() => {
switch (encodeData.type) {
case mozJPEG.type: return mozJPEG.encode(source.data, encodeData.options);
case webP.type: return webP.encode(source.data, encodeData.options);
case browserPNG.type: return browserPNG.encode(source.data, encodeData.options);
case browserJPEG.type: return browserJPEG.encode(source.data, encodeData.options);
case browserWebP.type: return browserWebP.encode(source.data, encodeData.options);

View File

@@ -3,10 +3,12 @@ import * as style from './style.scss';
import { bind } from '../../lib/util';
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
import WebPEncoderOptions from '../../codecs/webp/options';
import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options';
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
import * as identity from '../../codecs/identity/encoder';
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
import * as webP from '../../codecs/webp/encoder';
import * as browserPNG from '../../codecs/browser-png/encoder';
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
import * as browserWebP from '../../codecs/browser-webp/encoder';
@@ -25,8 +27,9 @@ import {
} from '../../codecs/encoders';
const encoderOptionsComponentMap = {
[mozJPEG.type]: MozJpegEncoderOptions,
[identity.type]: undefined,
[mozJPEG.type]: MozJpegEncoderOptions,
[webP.type]: WebPEncoderOptions,
[browserPNG.type]: undefined,
[browserJPEG.type]: BrowserJPEGEncoderOptions,
[browserWebP.type]: BrowserWebPEncoderOptions,