mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-15 10:09:45 +00:00
AVIF in worker
This commit is contained in:
5
codecs/avif/dec/avif_dec.d.ts
vendored
5
codecs/avif/dec/avif_dec.d.ts
vendored
@@ -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<AVIFModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
|
||||
15
codecs/avif/enc/avif_enc.d.ts
vendored
15
codecs/avif/enc/avif_enc.d.ts
vendored
@@ -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<AVIFModule>;
|
||||
|
||||
export default moduleFactory;
|
||||
|
||||
28
src/image-worker/avifDecode/index.ts
Normal file
28
src/image-worker/avifDecode/index.ts
Normal file
@@ -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<AVIFModule>;
|
||||
|
||||
export default async function decode(data: ArrayBuffer): Promise<ImageData> {
|
||||
if (!emscriptenModule) {
|
||||
emscriptenModule = initEmscriptenModule(avifDecoder, wasmUrl);
|
||||
}
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.decode(data);
|
||||
if (!result) throw new Error('Decoding error');
|
||||
return result;
|
||||
}
|
||||
45
src/image-worker/avifEncode/index.ts
Normal file
45
src/image-worker/avifEncode/index.ts
Normal file
@@ -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<AVIFModule>;
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
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;
|
||||
}
|
||||
@@ -5,7 +5,8 @@ import { initEmscriptenModule } from '../util';
|
||||
let emscriptenModule: Promise<AVIFModule>;
|
||||
|
||||
export async function decode(data: ArrayBuffer): Promise<ImageData> {
|
||||
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_dec, wasmUrl);
|
||||
if (!emscriptenModule)
|
||||
emscriptenModule = initEmscriptenModule(avif_dec, wasmUrl);
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.decode(data);
|
||||
@@ -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;
|
||||
}
|
||||
@@ -5,8 +5,12 @@ import { initEmscriptenModule } from '../util';
|
||||
|
||||
let emscriptenModule: Promise<AVIFModule>;
|
||||
|
||||
export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl);
|
||||
export async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule)
|
||||
emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl);
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.encode(data.data, data.width, data.height, options);
|
||||
@@ -34,18 +34,28 @@ const maxQuant = 63;
|
||||
const maxSpeed = 10;
|
||||
|
||||
export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
static getDerivedStateFromProps(props: Props, state: State): Partial<State> | undefined {
|
||||
static getDerivedStateFromProps(
|
||||
props: Props,
|
||||
state: State,
|
||||
): Partial<State> | 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<Props, State> {
|
||||
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<Props, State> {
|
||||
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<State> = {
|
||||
[prop]: newVal,
|
||||
@@ -115,20 +132,32 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
...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<Props, State> {
|
||||
}
|
||||
|
||||
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 (
|
||||
@@ -209,7 +249,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
Lossless alpha
|
||||
</label>
|
||||
<Expander>
|
||||
{!losslessAlpha &&
|
||||
{!losslessAlpha && (
|
||||
<div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
@@ -232,7 +272,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
</Range>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</Expander>
|
||||
</div>
|
||||
)}
|
||||
@@ -245,7 +285,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
Show advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced &&
|
||||
{showAdvanced && (
|
||||
<div>
|
||||
{/*<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
@@ -256,7 +296,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
Grayscale
|
||||
</label>*/}
|
||||
<Expander>
|
||||
{!grayscale && !lossless &&
|
||||
{!grayscale && !lossless && (
|
||||
<label class={style.optionTextFirst}>
|
||||
Subsample chroma:
|
||||
<Select
|
||||
@@ -269,7 +309,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
<option value="3">4:4:4</option>
|
||||
</Select>
|
||||
</label>
|
||||
}
|
||||
)}
|
||||
</Expander>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
@@ -292,7 +332,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
||||
</Range>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</Expander>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
Reference in New Issue
Block a user