diff --git a/codecs/mozjpeg_enc/mozjpeg_enc.d.ts b/codecs/mozjpeg_enc/mozjpeg_enc.d.ts new file mode 100644 index 00000000..5e680ea9 --- /dev/null +++ b/codecs/mozjpeg_enc/mozjpeg_enc.d.ts @@ -0,0 +1 @@ +export default function(): EmscriptenWasm.Module; diff --git a/emscripten-wasm.d.ts b/emscripten-wasm.d.ts new file mode 100644 index 00000000..f8988008 --- /dev/null +++ b/emscripten-wasm.d.ts @@ -0,0 +1,99 @@ +// These types roughly model the object that the JS files generated by Emscripten define. Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts and turned into a type definition rather than a global to support our way of using Emscripten. +// TODO(surma@): Upstream this? +declare namespace EmscriptenWasm { + type EnvironmentType = "WEB" | "NODE" | "SHELL" | "WORKER"; + + interface Module { + print(str: string): void; + printErr(str: string): void; + arguments: string[]; + environment: EnvironmentType; + preInit: { (): void }[]; + preRun: { (): void }[]; + postRun: { (): void }[]; + preinitializedWebGLContext: WebGLRenderingContext; + noInitialRun: boolean; + noExitRuntime: boolean; + logReadFiles: boolean; + filePackagePrefixURL: string; + wasmBinary: ArrayBuffer; + + destroy(object: object): void; + getPreloadedPackage(remotePackageName: string, remotePackageSize: number): ArrayBuffer; + instantiateWasm( + imports: WebAssembly.Imports, + successCallback: (module: WebAssembly.Module) => void + ): WebAssembly.Exports; + locateFile(url: string): string; + onCustomMessage(event: MessageEvent): void; + + Runtime: any; + + ccall(ident: string, returnType: string | null, argTypes: string[], args: any[]): any; + cwrap(ident: string, returnType: string | null, argTypes: string[]): any; + + setValue(ptr: number, value: any, type: string, noSafe?: boolean): void; + getValue(ptr: number, type: string, noSafe?: boolean): number; + + ALLOC_NORMAL: number; + ALLOC_STACK: number; + ALLOC_STATIC: number; + ALLOC_DYNAMIC: number; + ALLOC_NONE: number; + + allocate(slab: any, types: string, allocator: number, ptr: number): number; + allocate(slab: any, types: string[], allocator: number, ptr: number): number; + + Pointer_stringify(ptr: number, length?: number): string; + UTF16ToString(ptr: number): string; + stringToUTF16(str: string, outPtr: number): void; + UTF32ToString(ptr: number): string; + stringToUTF32(str: string, outPtr: number): void; + + // USE_TYPED_ARRAYS == 1 + HEAP: Int32Array; + IHEAP: Int32Array; + FHEAP: Float64Array; + + // USE_TYPED_ARRAYS == 2 + HEAP8: Int8Array; + HEAP16: Int16Array; + HEAP32: Int32Array; + HEAPU8: Uint8Array; + HEAPU16: Uint16Array; + HEAPU32: Uint32Array; + HEAPF32: Float32Array; + HEAPF64: Float64Array; + + TOTAL_STACK: number; + TOTAL_MEMORY: number; + FAST_MEMORY: number; + + addOnPreRun(cb: () => any): void; + addOnInit(cb: () => any): void; + addOnPreMain(cb: () => any): void; + addOnExit(cb: () => any): void; + addOnPostRun(cb: () => any): void; + + // Tools + intArrayFromString(stringy: string, dontAddNull?: boolean, length?: number): number[]; + intArrayToString(array: number[]): string; + writeStringToMemory(str: string, buffer: number, dontAddNull: boolean): void; + writeArrayToMemory(array: number[], buffer: number): void; + writeAsciiToMemory(str: string, buffer: number, dontAddNull: boolean): void; + + addRunDependency(id: any): void; + removeRunDependency(id: any): void; + + + preloadedImages: any; + preloadedAudios: any; + + _malloc(size: number): number; + _free(ptr: number): void; + + // Augmentations below by surma@ + onRuntimeInitialized: () => void | null; + } +} + diff --git a/package-lock.json b/package-lock.json index 15be382d..84533abe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -385,6 +385,12 @@ "integrity": "sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw==", "dev": true }, + "@types/webassembly-js-api": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/webassembly-js-api/-/webassembly-js-api-0.0.1.tgz", + "integrity": "sha1-YtULIBB319TMEJuxytoi/f1FI/s=", + "dev": true + }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -4529,6 +4535,24 @@ "homedir-polyfill": "^1.0.1" } }, + "exports-loader": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/exports-loader/-/exports-loader-0.7.0.tgz", + "integrity": "sha512-RKwCrO4A6IiKm0pG3c9V46JxIHcDplwwGJn6+JJ1RcVnh/WSGJa0xkmk5cRVtgOPzCAtTMGj2F7nluh9L0vpSA==", + "dev": true, + "requires": { + "loader-utils": "^1.1.0", + "source-map": "0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.0.tgz", + "integrity": "sha1-D+llA6yGpa213mP05BKuSHLNvoY=", + "dev": true + } + } + }, "express": { "version": "4.16.2", "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", diff --git a/package.json b/package.json index c0a6a53b..372a8749 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "version": "0.0.0", "license": "apache-2.0", "scripts": { + "build:mozjpeg_enc": "cd codecs/mozjpeg_enc && npm run build", + "build:codecs": "npm run build:mozjpeg_enc", "start": "webpack serve --hot", "build": "webpack -p", "lint": "eslint src" @@ -30,6 +32,7 @@ ], "devDependencies": { "@types/node": "^9.4.7", + "@types/webassembly-js-api": "0.0.1", "babel-loader": "^7.1.4", "babel-plugin-jsx-pragmatic": "^1.0.2", "babel-plugin-syntax-dynamic-import": "^6.18.0", @@ -52,6 +55,7 @@ "eslint-plugin-promise": "^3.7.0", "eslint-plugin-react": "^7.7.0", "eslint-plugin-standard": "^3.0.1", + "exports-loader": "^0.7.0", "html-webpack-plugin": "^3.0.6", "if-env": "^1.0.4", "loader-utils": "^1.1.0", diff --git a/src/components/app/index.tsx b/src/components/app/index.tsx index a41b8e20..8b3abae7 100644 --- a/src/components/app/index.tsx +++ b/src/components/app/index.tsx @@ -3,6 +3,8 @@ import { bind } from '../../lib/util'; import * as style from './style.scss'; import Output from '../output'; +import {MozJpegEncoder} from '../../lib/codec-wrappers/mozjpeg-enc'; + type Props = {}; type State = { @@ -29,7 +31,11 @@ export default class App extends Component { if (!fileInput.files || !fileInput.files[0]) return; // TODO: handle decode error const img = await createImageBitmap(fileInput.files[0]); - this.setState({ img }); + const encoder = new MozJpegEncoder(); + const compressedData = await encoder.encode(img); + const blob = new Blob([compressedData], {type: 'image/jpeg'}); + const compressedImage = await createImageBitmap(blob); + this.setState({ img: compressedImage }); } render({ }: Props, { img }: State) { diff --git a/src/lib/codec-wrappers/codec.ts b/src/lib/codec-wrappers/codec.ts new file mode 100644 index 00000000..88483854 --- /dev/null +++ b/src/lib/codec-wrappers/codec.ts @@ -0,0 +1,7 @@ +export interface Encoder { + encode(image: ImageBitmap): Promise; +} + +export interface Decoder { + decode(data: ArrayBuffer): Promise; +} diff --git a/src/lib/codec-wrappers/mozjpeg-enc.ts b/src/lib/codec-wrappers/mozjpeg-enc.ts new file mode 100644 index 00000000..47a3420d --- /dev/null +++ b/src/lib/codec-wrappers/mozjpeg-enc.ts @@ -0,0 +1,21 @@ +import {Encoder} from './codec'; + +import mozjpeg_enc from '../../../codecs/mozjpeg_enc/mozjpeg_enc'; + +export class MozJpegEncoder implements Encoder { + private emscriptenModule: Promise; + constructor() { + this.emscriptenModule = new Promise(resolve => { + console.log(mozjpeg_enc); + const m = mozjpeg_enc(); + m.onRuntimeInitialized = () => resolve(m); + }); + } + + async encode(bitmap: ImageBitmap): Promise { + console.log('encoding!'); + const m = await this.emscriptenModule; + console.log(m); + return Promise.resolve(new Uint8Array([1,2,3]).buffer); + } +} diff --git a/webpack.config.js b/webpack.config.js index a7de24f4..312166cf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -107,10 +107,17 @@ module.exports = function (_, env) { loader: 'babel-loader', // Don't respect any Babel RC files found on the filesystem: options: Object.assign(readJson('.babelrc'), { babelrc: false }) + }, + { + // All the codec files define a global with the same name as their file name. `exports-loader` attaches those to `module.exports`. + test: /\/codec\/.*\.js$/, + loader: 'exports-loader', } ] }, plugins: [ + // Ignore some of the native Node modules for any of the codecs. These files are generated by Emscripten and are supposed to also work in Node, which we don‘t care about. + new webpack.IgnorePlugin(/(fs)/, /\/codecs\//), // Pretty progressbar showing build progress: new ProgressBarPlugin({ format: '\u001b[90m\u001b[44mBuild\u001b[49m\u001b[39m [:bar] \u001b[32m\u001b[1m:percent\u001b[22m\u001b[39m (:elapseds) \u001b[2m:msg\u001b[22m\r',