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

Basic webp encoding integration #103

Merged
merged 1 commit into from
Jul 17, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions codecs/webp_enc/webp_enc.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module;
8 changes: 5 additions & 3 deletions src/codecs/encoders.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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,
Expand Down
80 changes: 80 additions & 0 deletions src/codecs/webp/Encoder.worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import webp_enc from '../../../codecs/webp_enc/webp_enc';
// Using require() so TypeScript doesn’t 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 codec’s 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, don’t 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 it’s 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 can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return result.buffer as ArrayBuffer;
}
}
16 changes: 16 additions & 0 deletions src/codecs/webp/encoder.ts
Original file line number Diff line number Diff line change
@@ -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);
}
3 changes: 3 additions & 0 deletions src/codecs/webp/options.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import qualityOption from '../generic/quality-option';

export default qualityOption();
2 changes: 2 additions & 0 deletions src/components/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions src/components/Options/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand Down