diff --git a/codecs/avif/dec/avif_dec.d.ts b/codecs/avif/dec/avif_dec.d.ts index ff52bcff..b2e56d3b 100644 --- a/codecs/avif/dec/avif_dec.d.ts +++ b/codecs/avif/dec/avif_dec.d.ts @@ -1,6 +1,7 @@ -interface AVIFModule extends EmscriptenWasm.Module { +export interface AVIFModule extends EmscriptenWasm.Module { decode(data: BufferSource): ImageData | null; } -export default function(opts: EmscriptenWasm.ModuleOpts): AVIFModule; +declare var moduleFactory: EmscriptenWasm.ModuleFactory; +export default moduleFactory; diff --git a/codecs/avif/enc/avif_enc.d.ts b/codecs/avif/enc/avif_enc.d.ts index c00e18e7..2f1436fe 100644 --- a/codecs/avif/enc/avif_enc.d.ts +++ b/codecs/avif/enc/avif_enc.d.ts @@ -1,7 +1,14 @@ -import { EncodeOptions } from '../../../src/codecs/avif/encoder-meta'; +import { EncodeOptions } from 'image-worker/avifEncode'; -interface AVIFModule extends EmscriptenWasm.Module { - encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array | null; +export interface AVIFModule extends EmscriptenWasm.Module { + encode( + data: BufferSource, + width: number, + height: number, + options: EncodeOptions, + ): Uint8Array | null; } -export default function(opts: EmscriptenWasm.ModuleOpts): AVIFModule; +declare var moduleFactory: EmscriptenWasm.ModuleFactory; + +export default moduleFactory; diff --git a/src/image-worker/avifDecode/index.ts b/src/image-worker/avifDecode/index.ts new file mode 100644 index 00000000..cdf04b11 --- /dev/null +++ b/src/image-worker/avifDecode/index.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2020 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. + */ +import avifDecoder, { AVIFModule } from 'codecs/avif/dec/avif_dec'; +import wasmUrl from 'url:codecs/avif/dec/avif_dec.wasm'; +import { initEmscriptenModule } from '../util'; + +let emscriptenModule: Promise; + +export default async function decode(data: ArrayBuffer): Promise { + if (!emscriptenModule) { + emscriptenModule = initEmscriptenModule(avifDecoder, wasmUrl); + } + + const module = await emscriptenModule; + const result = module.decode(data); + if (!result) throw new Error('Decoding error'); + return result; +} diff --git a/src/image-worker/avifEncode/index.ts b/src/image-worker/avifEncode/index.ts new file mode 100644 index 00000000..66c707a1 --- /dev/null +++ b/src/image-worker/avifEncode/index.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2020 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. + */ +import avifEncoder, { AVIFModule } from 'codecs/avif/enc/avif_enc'; +import wasmUrl from 'url:codecs/avif/enc/avif_enc.wasm'; +import { initEmscriptenModule } from '../util'; + +export interface EncodeOptions { + minQuantizer: number; + maxQuantizer: number; + minQuantizerAlpha: number; + maxQuantizerAlpha: number; + tileRowsLog2: number; + tileColsLog2: number; + speed: number; + subsample: number; +} + +let emscriptenModule: Promise; + +export default async function encode( + data: ImageData, + options: EncodeOptions, +): Promise { + if (!emscriptenModule) { + emscriptenModule = initEmscriptenModule(avifEncoder, wasmUrl); + } + + const module = await emscriptenModule; + const result = module.encode(data.data, data.width, data.height, options); + + if (!result) throw new Error('Encoding error'); + + // wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer. + return result.buffer as ArrayBuffer; +} diff --git a/src/codecs/avif/decoder-meta.ts b/src_old/codecs/avif/decoder-meta.ts similarity index 100% rename from src/codecs/avif/decoder-meta.ts rename to src_old/codecs/avif/decoder-meta.ts diff --git a/src/codecs/avif/decoder.ts b/src_old/codecs/avif/decoder.ts similarity index 83% rename from src/codecs/avif/decoder.ts rename to src_old/codecs/avif/decoder.ts index 0dc55b37..234d5268 100644 --- a/src/codecs/avif/decoder.ts +++ b/src_old/codecs/avif/decoder.ts @@ -5,7 +5,8 @@ import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; export async function decode(data: ArrayBuffer): Promise { - if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_dec, wasmUrl); + if (!emscriptenModule) + emscriptenModule = initEmscriptenModule(avif_dec, wasmUrl); const module = await emscriptenModule; const result = module.decode(data); diff --git a/src/codecs/avif/encoder-meta.ts b/src_old/codecs/avif/encoder-meta.ts similarity index 87% rename from src/codecs/avif/encoder-meta.ts rename to src_old/codecs/avif/encoder-meta.ts index c051064e..7df64b40 100644 --- a/src/codecs/avif/encoder-meta.ts +++ b/src_old/codecs/avif/encoder-meta.ts @@ -24,4 +24,7 @@ export const defaultOptions: EncodeOptions = { subsample: 1, }; -export interface EncoderState { type: typeof type; options: EncodeOptions; } +export interface EncoderState { + type: typeof type; + options: EncodeOptions; +} diff --git a/src/codecs/avif/encoder.ts b/src_old/codecs/avif/encoder.ts similarity index 75% rename from src/codecs/avif/encoder.ts rename to src_old/codecs/avif/encoder.ts index 50b33ab1..8c4088d5 100644 --- a/src/codecs/avif/encoder.ts +++ b/src_old/codecs/avif/encoder.ts @@ -5,8 +5,12 @@ import { initEmscriptenModule } from '../util'; let emscriptenModule: Promise; -export async function encode(data: ImageData, options: EncodeOptions): Promise { - if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl); +export async function encode( + data: ImageData, + options: EncodeOptions, +): Promise { + if (!emscriptenModule) + emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl); const module = await emscriptenModule; const result = module.encode(data.data, data.width, data.height, options); diff --git a/src/codecs/avif/options.tsx b/src_old/codecs/avif/options.tsx similarity index 79% rename from src/codecs/avif/options.tsx rename to src_old/codecs/avif/options.tsx index ac957d3c..dd85f60c 100644 --- a/src/codecs/avif/options.tsx +++ b/src_old/codecs/avif/options.tsx @@ -34,18 +34,28 @@ const maxQuant = 63; const maxSpeed = 10; export default class AVIFEncoderOptions extends Component { - static getDerivedStateFromProps(props: Props, state: State): Partial | undefined { + static getDerivedStateFromProps( + props: Props, + state: State, + ): Partial | undefined { if (state.options && shallowEqual(state.options, props.options)) return; const { options } = props; const lossless = options.maxQuantizer === 0 && options.minQuantizer === 0; - const minQuantizerValue = lossless ? defaultOptions.minQuantizer : options.minQuantizer; - const maxQuantizerValue = lossless ? defaultOptions.maxQuantizer : options.maxQuantizer; - const losslessAlpha = options.maxQuantizerAlpha === 0 && options.minQuantizerAlpha === 0; - const minQuantizerAlphaValue = losslessAlpha ? - defaultOptions.minQuantizerAlpha : options.minQuantizerAlpha; - const maxQuantizerAlphaValue = losslessAlpha ? - defaultOptions.maxQuantizerAlpha : options.maxQuantizerAlpha; + const minQuantizerValue = lossless + ? defaultOptions.minQuantizer + : options.minQuantizer; + const maxQuantizerValue = lossless + ? defaultOptions.maxQuantizer + : options.maxQuantizer; + const losslessAlpha = + options.maxQuantizerAlpha === 0 && options.minQuantizerAlpha === 0; + const minQuantizerAlphaValue = losslessAlpha + ? defaultOptions.minQuantizerAlpha + : options.minQuantizerAlpha; + const maxQuantizerAlphaValue = losslessAlpha + ? defaultOptions.maxQuantizerAlpha + : options.maxQuantizerAlpha; // Create default form state from options return { @@ -54,12 +64,16 @@ export default class AVIFEncoderOptions extends Component { losslessAlpha, maxQuality: maxQuant - minQuantizerValue, minQuality: maxQuant - maxQuantizerValue, - separateAlpha: options.maxQuantizer !== options.maxQuantizerAlpha || + separateAlpha: + options.maxQuantizer !== options.maxQuantizerAlpha || options.minQuantizer !== options.minQuantizerAlpha, maxAlphaQuality: maxQuant - minQuantizerAlphaValue, minAlphaQuality: maxQuant - maxQuantizerAlphaValue, grayscale: options.subsample === 0, - subsample: options.subsample === 0 || lossless ? defaultOptions.subsample : options.subsample, + subsample: + options.subsample === 0 || lossless + ? defaultOptions.subsample + : options.subsample, tileRows: options.tileRowsLog2, tileCols: options.tileColsLog2, effort: maxSpeed - options.speed, @@ -78,9 +92,12 @@ export default class AVIFEncoderOptions extends Component { if (!this._inputChangeCallbacks.has(prop)) { this._inputChangeCallbacks.set(prop, (event: Event) => { const formEl = event.target as HTMLInputElement | HTMLSelectElement; - const newVal = type === 'boolean' ? - 'checked' in formEl ? formEl.checked : !!formEl.value : - Number(formEl.value); + const newVal = + type === 'boolean' + ? 'checked' in formEl + ? formEl.checked + : !!formEl.value + : Number(formEl.value); const newState: Partial = { [prop]: newVal, @@ -115,20 +132,32 @@ export default class AVIFEncoderOptions extends Component { ...newState, }; - const maxQuantizer = optionState.lossless ? 0 : (maxQuant - optionState.minQuality); - const minQuantizer = optionState.lossless ? 0 : (maxQuant - optionState.maxQuality); + const maxQuantizer = optionState.lossless + ? 0 + : maxQuant - optionState.minQuality; + const minQuantizer = optionState.lossless + ? 0 + : maxQuant - optionState.maxQuality; const newOptions: EncodeOptions = { maxQuantizer, minQuantizer, - maxQuantizerAlpha: optionState.separateAlpha ? - (optionState.losslessAlpha ? 0 : (maxQuant - optionState.minAlphaQuality)) : - maxQuantizer, - minQuantizerAlpha: optionState.separateAlpha ? - (optionState.losslessAlpha ? 0 : (maxQuant - optionState.maxAlphaQuality)) : - minQuantizer, + maxQuantizerAlpha: optionState.separateAlpha + ? optionState.losslessAlpha + ? 0 + : maxQuant - optionState.minAlphaQuality + : maxQuantizer, + minQuantizerAlpha: optionState.separateAlpha + ? optionState.losslessAlpha + ? 0 + : maxQuant - optionState.maxAlphaQuality + : minQuantizer, // Always set to 4:4:4 if lossless - subsample: optionState.grayscale ? 0 : optionState.lossless ? 3 : optionState.subsample, + subsample: optionState.grayscale + ? 0 + : optionState.lossless + ? 3 + : optionState.subsample, tileColsLog2: optionState.tileCols, tileRowsLog2: optionState.tileRows, speed: maxSpeed - optionState.effort, @@ -147,13 +176,24 @@ export default class AVIFEncoderOptions extends Component { } return this._inputChangeCallbacks.get(prop)!; - } + }; render( _: Props, { - effort, grayscale, lossless, losslessAlpha, maxAlphaQuality, maxQuality, minAlphaQuality, - minQuality, separateAlpha, showAdvanced, subsample, tileCols, tileRows, + effort, + grayscale, + lossless, + losslessAlpha, + maxAlphaQuality, + maxQuality, + minAlphaQuality, + minQuality, + separateAlpha, + showAdvanced, + subsample, + tileCols, + tileRows, }: State, ) { return ( @@ -199,7 +239,7 @@ export default class AVIFEncoderOptions extends Component { Separate alpha quality - {separateAlpha && ( + {separateAlpha && (
- {!losslessAlpha && + {!losslessAlpha && (
{
- } + )}
)} @@ -245,7 +285,7 @@ export default class AVIFEncoderOptions extends Component { Show advanced settings - {showAdvanced && + {showAdvanced && (
{/**/} - {!grayscale && !lossless && + {!grayscale && !lossless && ( - } + )}
{
- } + )}