mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-15 18:19:47 +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;
|
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 {
|
export interface AVIFModule extends EmscriptenWasm.Module {
|
||||||
encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array | null;
|
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>;
|
let emscriptenModule: Promise<AVIFModule>;
|
||||||
|
|
||||||
export async function decode(data: ArrayBuffer): Promise<ImageData> {
|
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 module = await emscriptenModule;
|
||||||
const result = module.decode(data);
|
const result = module.decode(data);
|
||||||
@@ -24,4 +24,7 @@ export const defaultOptions: EncodeOptions = {
|
|||||||
subsample: 1,
|
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>;
|
let emscriptenModule: Promise<AVIFModule>;
|
||||||
|
|
||||||
export async function encode(data: ImageData, options: EncodeOptions): Promise<ArrayBuffer> {
|
export async function encode(
|
||||||
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl);
|
data: ImageData,
|
||||||
|
options: EncodeOptions,
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
if (!emscriptenModule)
|
||||||
|
emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl);
|
||||||
|
|
||||||
const module = await emscriptenModule;
|
const module = await emscriptenModule;
|
||||||
const result = module.encode(data.data, data.width, data.height, options);
|
const result = module.encode(data.data, data.width, data.height, options);
|
||||||
@@ -34,18 +34,28 @@ const maxQuant = 63;
|
|||||||
const maxSpeed = 10;
|
const maxSpeed = 10;
|
||||||
|
|
||||||
export default class AVIFEncoderOptions extends Component<Props, State> {
|
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;
|
if (state.options && shallowEqual(state.options, props.options)) return;
|
||||||
const { options } = props;
|
const { options } = props;
|
||||||
|
|
||||||
const lossless = options.maxQuantizer === 0 && options.minQuantizer === 0;
|
const lossless = options.maxQuantizer === 0 && options.minQuantizer === 0;
|
||||||
const minQuantizerValue = lossless ? defaultOptions.minQuantizer : options.minQuantizer;
|
const minQuantizerValue = lossless
|
||||||
const maxQuantizerValue = lossless ? defaultOptions.maxQuantizer : options.maxQuantizer;
|
? defaultOptions.minQuantizer
|
||||||
const losslessAlpha = options.maxQuantizerAlpha === 0 && options.minQuantizerAlpha === 0;
|
: options.minQuantizer;
|
||||||
const minQuantizerAlphaValue = losslessAlpha ?
|
const maxQuantizerValue = lossless
|
||||||
defaultOptions.minQuantizerAlpha : options.minQuantizerAlpha;
|
? defaultOptions.maxQuantizer
|
||||||
const maxQuantizerAlphaValue = losslessAlpha ?
|
: options.maxQuantizer;
|
||||||
defaultOptions.maxQuantizerAlpha : options.maxQuantizerAlpha;
|
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
|
// Create default form state from options
|
||||||
return {
|
return {
|
||||||
@@ -54,12 +64,16 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
losslessAlpha,
|
losslessAlpha,
|
||||||
maxQuality: maxQuant - minQuantizerValue,
|
maxQuality: maxQuant - minQuantizerValue,
|
||||||
minQuality: maxQuant - maxQuantizerValue,
|
minQuality: maxQuant - maxQuantizerValue,
|
||||||
separateAlpha: options.maxQuantizer !== options.maxQuantizerAlpha ||
|
separateAlpha:
|
||||||
|
options.maxQuantizer !== options.maxQuantizerAlpha ||
|
||||||
options.minQuantizer !== options.minQuantizerAlpha,
|
options.minQuantizer !== options.minQuantizerAlpha,
|
||||||
maxAlphaQuality: maxQuant - minQuantizerAlphaValue,
|
maxAlphaQuality: maxQuant - minQuantizerAlphaValue,
|
||||||
minAlphaQuality: maxQuant - maxQuantizerAlphaValue,
|
minAlphaQuality: maxQuant - maxQuantizerAlphaValue,
|
||||||
grayscale: options.subsample === 0,
|
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,
|
tileRows: options.tileRowsLog2,
|
||||||
tileCols: options.tileColsLog2,
|
tileCols: options.tileColsLog2,
|
||||||
effort: maxSpeed - options.speed,
|
effort: maxSpeed - options.speed,
|
||||||
@@ -78,9 +92,12 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
if (!this._inputChangeCallbacks.has(prop)) {
|
if (!this._inputChangeCallbacks.has(prop)) {
|
||||||
this._inputChangeCallbacks.set(prop, (event: Event) => {
|
this._inputChangeCallbacks.set(prop, (event: Event) => {
|
||||||
const formEl = event.target as HTMLInputElement | HTMLSelectElement;
|
const formEl = event.target as HTMLInputElement | HTMLSelectElement;
|
||||||
const newVal = type === 'boolean' ?
|
const newVal =
|
||||||
'checked' in formEl ? formEl.checked : !!formEl.value :
|
type === 'boolean'
|
||||||
Number(formEl.value);
|
? 'checked' in formEl
|
||||||
|
? formEl.checked
|
||||||
|
: !!formEl.value
|
||||||
|
: Number(formEl.value);
|
||||||
|
|
||||||
const newState: Partial<State> = {
|
const newState: Partial<State> = {
|
||||||
[prop]: newVal,
|
[prop]: newVal,
|
||||||
@@ -115,20 +132,32 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
...newState,
|
...newState,
|
||||||
};
|
};
|
||||||
|
|
||||||
const maxQuantizer = optionState.lossless ? 0 : (maxQuant - optionState.minQuality);
|
const maxQuantizer = optionState.lossless
|
||||||
const minQuantizer = optionState.lossless ? 0 : (maxQuant - optionState.maxQuality);
|
? 0
|
||||||
|
: maxQuant - optionState.minQuality;
|
||||||
|
const minQuantizer = optionState.lossless
|
||||||
|
? 0
|
||||||
|
: maxQuant - optionState.maxQuality;
|
||||||
|
|
||||||
const newOptions: EncodeOptions = {
|
const newOptions: EncodeOptions = {
|
||||||
maxQuantizer,
|
maxQuantizer,
|
||||||
minQuantizer,
|
minQuantizer,
|
||||||
maxQuantizerAlpha: optionState.separateAlpha ?
|
maxQuantizerAlpha: optionState.separateAlpha
|
||||||
(optionState.losslessAlpha ? 0 : (maxQuant - optionState.minAlphaQuality)) :
|
? optionState.losslessAlpha
|
||||||
maxQuantizer,
|
? 0
|
||||||
minQuantizerAlpha: optionState.separateAlpha ?
|
: maxQuant - optionState.minAlphaQuality
|
||||||
(optionState.losslessAlpha ? 0 : (maxQuant - optionState.maxAlphaQuality)) :
|
: maxQuantizer,
|
||||||
minQuantizer,
|
minQuantizerAlpha: optionState.separateAlpha
|
||||||
|
? optionState.losslessAlpha
|
||||||
|
? 0
|
||||||
|
: maxQuant - optionState.maxAlphaQuality
|
||||||
|
: minQuantizer,
|
||||||
// Always set to 4:4:4 if lossless
|
// 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,
|
tileColsLog2: optionState.tileCols,
|
||||||
tileRowsLog2: optionState.tileRows,
|
tileRowsLog2: optionState.tileRows,
|
||||||
speed: maxSpeed - optionState.effort,
|
speed: maxSpeed - optionState.effort,
|
||||||
@@ -147,13 +176,24 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this._inputChangeCallbacks.get(prop)!;
|
return this._inputChangeCallbacks.get(prop)!;
|
||||||
}
|
};
|
||||||
|
|
||||||
render(
|
render(
|
||||||
_: Props,
|
_: Props,
|
||||||
{
|
{
|
||||||
effort, grayscale, lossless, losslessAlpha, maxAlphaQuality, maxQuality, minAlphaQuality,
|
effort,
|
||||||
minQuality, separateAlpha, showAdvanced, subsample, tileCols, tileRows,
|
grayscale,
|
||||||
|
lossless,
|
||||||
|
losslessAlpha,
|
||||||
|
maxAlphaQuality,
|
||||||
|
maxQuality,
|
||||||
|
minAlphaQuality,
|
||||||
|
minQuality,
|
||||||
|
separateAlpha,
|
||||||
|
showAdvanced,
|
||||||
|
subsample,
|
||||||
|
tileCols,
|
||||||
|
tileRows,
|
||||||
}: State,
|
}: State,
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -199,7 +239,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
Separate alpha quality
|
Separate alpha quality
|
||||||
</label>
|
</label>
|
||||||
<Expander>
|
<Expander>
|
||||||
{separateAlpha && (
|
{separateAlpha && (
|
||||||
<div>
|
<div>
|
||||||
<label class={style.optionInputFirst}>
|
<label class={style.optionInputFirst}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -209,7 +249,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
Lossless alpha
|
Lossless alpha
|
||||||
</label>
|
</label>
|
||||||
<Expander>
|
<Expander>
|
||||||
{!losslessAlpha &&
|
{!losslessAlpha && (
|
||||||
<div>
|
<div>
|
||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
@@ -232,7 +272,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</Expander>
|
</Expander>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -245,7 +285,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
Show advanced settings
|
Show advanced settings
|
||||||
</label>
|
</label>
|
||||||
<Expander>
|
<Expander>
|
||||||
{showAdvanced &&
|
{showAdvanced && (
|
||||||
<div>
|
<div>
|
||||||
{/*<label class={style.optionInputFirst}>
|
{/*<label class={style.optionInputFirst}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -256,7 +296,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
Grayscale
|
Grayscale
|
||||||
</label>*/}
|
</label>*/}
|
||||||
<Expander>
|
<Expander>
|
||||||
{!grayscale && !lossless &&
|
{!grayscale && !lossless && (
|
||||||
<label class={style.optionTextFirst}>
|
<label class={style.optionTextFirst}>
|
||||||
Subsample chroma:
|
Subsample chroma:
|
||||||
<Select
|
<Select
|
||||||
@@ -269,7 +309,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
<option value="3">4:4:4</option>
|
<option value="3">4:4:4</option>
|
||||||
</Select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
}
|
)}
|
||||||
</Expander>
|
</Expander>
|
||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
@@ -292,7 +332,7 @@ export default class AVIFEncoderOptions extends Component<Props, State> {
|
|||||||
</Range>
|
</Range>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
)}
|
||||||
</Expander>
|
</Expander>
|
||||||
<div class={style.optionOneCell}>
|
<div class={style.optionOneCell}>
|
||||||
<Range
|
<Range
|
||||||
Reference in New Issue
Block a user