mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 01:07:18 +00:00
Basic webp integration (#103)
This commit is contained in:
1
codecs/webp_enc/webp_enc.d.ts
vendored
Normal file
1
codecs/webp_enc/webp_enc.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as mozJPEG from './mozjpeg/encoder';
|
|
||||||
import * as identity from './identity/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 browserPNG from './browser-png/encoder';
|
||||||
import * as browserJPEG from './browser-jpeg/encoder';
|
import * as browserJPEG from './browser-jpeg/encoder';
|
||||||
import * as browserWebP from './browser-webp/encoder';
|
import * as browserWebP from './browser-webp/encoder';
|
||||||
@@ -14,12 +15,12 @@ export interface EncoderSupportMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EncoderState =
|
export type EncoderState =
|
||||||
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState |
|
identity.EncoderState | mozJPEG.EncoderState | webP.EncoderState | browserPNG.EncoderState |
|
||||||
browserJPEG.EncoderState | browserWebP.EncoderState | browserGIF.EncoderState |
|
browserJPEG.EncoderState | browserWebP.EncoderState | browserGIF.EncoderState |
|
||||||
browserTIFF.EncoderState | browserJP2.EncoderState | browserBMP.EncoderState |
|
browserTIFF.EncoderState | browserJP2.EncoderState | browserBMP.EncoderState |
|
||||||
browserPDF.EncoderState;
|
browserPDF.EncoderState;
|
||||||
export type EncoderOptions =
|
export type EncoderOptions =
|
||||||
identity.EncodeOptions | mozJPEG.EncodeOptions | browserPNG.EncodeOptions |
|
identity.EncodeOptions | mozJPEG.EncodeOptions | webP.EncodeOptions | browserPNG.EncodeOptions |
|
||||||
browserJPEG.EncodeOptions | browserWebP.EncodeOptions | browserGIF.EncodeOptions |
|
browserJPEG.EncodeOptions | browserWebP.EncodeOptions | browserGIF.EncodeOptions |
|
||||||
browserTIFF.EncodeOptions | browserJP2.EncodeOptions | browserBMP.EncodeOptions |
|
browserTIFF.EncodeOptions | browserJP2.EncodeOptions | browserBMP.EncodeOptions |
|
||||||
browserPDF.EncodeOptions;
|
browserPDF.EncodeOptions;
|
||||||
@@ -28,6 +29,7 @@ export type EncoderType = keyof typeof encoderMap;
|
|||||||
export const encoderMap = {
|
export const encoderMap = {
|
||||||
[identity.type]: identity,
|
[identity.type]: identity,
|
||||||
[mozJPEG.type]: mozJPEG,
|
[mozJPEG.type]: mozJPEG,
|
||||||
|
[webP.type]: webP,
|
||||||
[browserPNG.type]: browserPNG,
|
[browserPNG.type]: browserPNG,
|
||||||
[browserJPEG.type]: browserJPEG,
|
[browserJPEG.type]: browserJPEG,
|
||||||
[browserWebP.type]: browserWebP,
|
[browserWebP.type]: browserWebP,
|
||||||
|
|||||||
80
src/codecs/webp/Encoder.worker.ts
Normal file
80
src/codecs/webp/Encoder.worker.ts
Normal file
@@ -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
src/codecs/webp/encoder.ts
Normal file
16
src/codecs/webp/encoder.ts
Normal 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);
|
||||||
|
}
|
||||||
3
src/codecs/webp/options.tsx
Normal file
3
src/codecs/webp/options.tsx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import qualityOption from '../generic/quality-option';
|
||||||
|
|
||||||
|
export default qualityOption();
|
||||||
@@ -9,6 +9,7 @@ import { FileDropEvent } from './custom-els/FileDrop';
|
|||||||
import './custom-els/FileDrop';
|
import './custom-els/FileDrop';
|
||||||
|
|
||||||
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
|
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
|
||||||
|
import * as webP from '../../codecs/webp/encoder';
|
||||||
import * as identity from '../../codecs/identity/encoder';
|
import * as identity from '../../codecs/identity/encoder';
|
||||||
import * as browserPNG from '../../codecs/browser-png/encoder';
|
import * as browserPNG from '../../codecs/browser-png/encoder';
|
||||||
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
||||||
@@ -64,6 +65,7 @@ async function compressImage(
|
|||||||
const compressedData = await (() => {
|
const compressedData = await (() => {
|
||||||
switch (encodeData.type) {
|
switch (encodeData.type) {
|
||||||
case mozJPEG.type: return mozJPEG.encode(source.data, encodeData.options);
|
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 browserPNG.type: return browserPNG.encode(source.data, encodeData.options);
|
||||||
case browserJPEG.type: return browserJPEG.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);
|
case browserWebP.type: return browserWebP.encode(source.data, encodeData.options);
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import * as style from './style.scss';
|
|||||||
import { bind } from '../../lib/util';
|
import { bind } from '../../lib/util';
|
||||||
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
||||||
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
||||||
|
import WebPEncoderOptions from '../../codecs/webp/options';
|
||||||
import BrowserWebPEncoderOptions from '../../codecs/browser-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 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 browserPNG from '../../codecs/browser-png/encoder';
|
||||||
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
||||||
import * as browserWebP from '../../codecs/browser-webp/encoder';
|
import * as browserWebP from '../../codecs/browser-webp/encoder';
|
||||||
@@ -25,8 +27,9 @@ import {
|
|||||||
} from '../../codecs/encoders';
|
} from '../../codecs/encoders';
|
||||||
|
|
||||||
const encoderOptionsComponentMap = {
|
const encoderOptionsComponentMap = {
|
||||||
[mozJPEG.type]: MozJpegEncoderOptions,
|
|
||||||
[identity.type]: undefined,
|
[identity.type]: undefined,
|
||||||
|
[mozJPEG.type]: MozJpegEncoderOptions,
|
||||||
|
[webP.type]: WebPEncoderOptions,
|
||||||
[browserPNG.type]: undefined,
|
[browserPNG.type]: undefined,
|
||||||
[browserJPEG.type]: BrowserJPEGEncoderOptions,
|
[browserJPEG.type]: BrowserJPEGEncoderOptions,
|
||||||
[browserWebP.type]: BrowserWebPEncoderOptions,
|
[browserWebP.type]: BrowserWebPEncoderOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user