forked from external-repos/squoosh
Extra Wp2 Options (#853)
* wip * wip * Add extra options * Even more options! * Update src/features/encoders/wp2/client/index.tsx Co-authored-by: Surma <surma@surma.dev> Co-authored-by: Surma <surma@surma.dev>
This commit is contained in:
@@ -7,7 +7,31 @@ using namespace emscripten;
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
val encode(std::string image_in, int image_width, int image_height, WP2::EncoderConfig config) {
|
||||
struct WP2Options {
|
||||
float quality;
|
||||
float alpha_quality;
|
||||
int speed;
|
||||
int pass;
|
||||
int uv_mode;
|
||||
float sns;
|
||||
int csp_type;
|
||||
int error_diffusion;
|
||||
bool use_random_matrix;
|
||||
};
|
||||
|
||||
val encode(std::string image_in, int image_width, int image_height, WP2Options options) {
|
||||
WP2::EncoderConfig config = {};
|
||||
|
||||
config.quality = options.quality;
|
||||
config.alpha_quality = options.alpha_quality;
|
||||
config.speed = options.speed;
|
||||
config.pass = options.pass;
|
||||
config.uv_mode = static_cast<WP2::EncoderConfig::UVMode>(options.uv_mode);
|
||||
config.csp_type = static_cast<WP2::Csp>(options.csp_type);
|
||||
config.sns = options.sns;
|
||||
config.error_diffusion = options.error_diffusion;
|
||||
config.use_random_matrix = options.use_random_matrix;
|
||||
|
||||
uint8_t* image_buffer = (uint8_t*)image_in.c_str();
|
||||
WP2::ArgbBuffer src = WP2::ArgbBuffer();
|
||||
WP2Status status =
|
||||
@@ -27,12 +51,16 @@ val encode(std::string image_in, int image_width, int image_height, WP2::Encoder
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<WP2::EncoderConfig>("WP2EncoderConfig")
|
||||
.field("quality", &WP2::EncoderConfig::quality)
|
||||
.field("alpha_quality", &WP2::EncoderConfig::alpha_quality)
|
||||
.field("speed", &WP2::EncoderConfig::speed)
|
||||
.field("pass", &WP2::EncoderConfig::pass)
|
||||
.field("sns", &WP2::EncoderConfig::sns);
|
||||
value_object<WP2Options>("WP2Options")
|
||||
.field("quality", &WP2Options::quality)
|
||||
.field("alpha_quality", &WP2Options::alpha_quality)
|
||||
.field("speed", &WP2Options::speed)
|
||||
.field("pass", &WP2Options::pass)
|
||||
.field("uv_mode", &WP2Options::uv_mode)
|
||||
.field("csp_type", &WP2Options::csp_type)
|
||||
.field("error_diffusion", &WP2Options::error_diffusion)
|
||||
.field("use_random_matrix", &WP2Options::use_random_matrix)
|
||||
.field("sns", &WP2Options::sns);
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
||||
|
||||
18
codecs/wp2/enc/wp2_enc.d.ts
vendored
18
codecs/wp2/enc/wp2_enc.d.ts
vendored
@@ -4,6 +4,24 @@ export interface EncodeOptions {
|
||||
speed: number;
|
||||
pass: number;
|
||||
sns: number;
|
||||
uv_mode: UVMode;
|
||||
csp_type: Csp;
|
||||
error_diffusion: number;
|
||||
use_random_matrix: boolean;
|
||||
}
|
||||
|
||||
export const enum UVMode {
|
||||
UVModeAdapt = 0, // Mix of 420 and 444 (per block)
|
||||
UVMode420, // All blocks 420
|
||||
UVMode444, // All blocks 444
|
||||
UVModeAuto, // Choose any of the above automatically
|
||||
}
|
||||
|
||||
export const enum Csp {
|
||||
kYCoCg,
|
||||
kYCbCr,
|
||||
kCustom,
|
||||
kYIQ,
|
||||
}
|
||||
|
||||
export interface WP2Module extends EmscriptenWasm.Module {
|
||||
|
||||
@@ -576,7 +576,7 @@ var wp2_enc = (function () {
|
||||
},
|
||||
});
|
||||
var ob = {
|
||||
p: function (a, b, c, d) {
|
||||
t: function (a, b, c, d) {
|
||||
A(
|
||||
'Assertion failed: ' +
|
||||
C(a) +
|
||||
@@ -807,7 +807,7 @@ var wp2_enc = (function () {
|
||||
return [];
|
||||
});
|
||||
},
|
||||
c: function (a, b, c, d, e) {
|
||||
d: function (a, b, c, d, e) {
|
||||
function g(l) {
|
||||
return l;
|
||||
}
|
||||
@@ -1000,10 +1000,10 @@ var wp2_enc = (function () {
|
||||
},
|
||||
});
|
||||
},
|
||||
q: function (a, b, c, d, e, g) {
|
||||
p: function (a, b, c, d, e, g) {
|
||||
Fa[a] = { name: U(b), da: Y(c, d), ea: Y(e, g), U: [] };
|
||||
},
|
||||
e: function (a, b, c, d, e, g, m, h, k, l) {
|
||||
c: function (a, b, c, d, e, g, m, h, k, l) {
|
||||
Fa[a].U.push({
|
||||
X: U(b),
|
||||
aa: c,
|
||||
@@ -1034,7 +1034,7 @@ var wp2_enc = (function () {
|
||||
m: function (a) {
|
||||
4 < a && (X[a].T += 1);
|
||||
},
|
||||
t: function (a, b, c, d) {
|
||||
q: function (a, b, c, d) {
|
||||
a || W('Cannot use deleted val. handle = ' + a);
|
||||
a = X[a].value;
|
||||
var e = ib[b];
|
||||
@@ -1079,7 +1079,7 @@ var wp2_enc = (function () {
|
||||
u: function (a, b, c) {
|
||||
D.copyWithin(a, b, b + c);
|
||||
},
|
||||
d: function (a) {
|
||||
e: function (a) {
|
||||
a >>>= 0;
|
||||
var b = D.length;
|
||||
if (2147483648 < a) return !1;
|
||||
|
||||
Binary file not shown.
@@ -315,9 +315,9 @@ export class Options extends Component<Props, State> {
|
||||
value={subsample}
|
||||
onChange={this._inputChange('subsample', 'number')}
|
||||
>
|
||||
<option value="1">4:2:0</option>
|
||||
<option value="1">Half</option>
|
||||
{/*<option value="2">4:2:2</option>*/}
|
||||
<option value="3">4:4:4</option>
|
||||
<option value="3">Off</option>
|
||||
</Select>
|
||||
</label>
|
||||
)}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { EncodeOptions } from '../shared/meta';
|
||||
import { EncodeOptions, UVMode, Csp } from '../shared/meta';
|
||||
import { defaultOptions } from '../shared/meta';
|
||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||
import { h, Component } from 'preact';
|
||||
import { inputFieldValueAsNumber, preventDefault } from 'client/lazy-app/util';
|
||||
import { preventDefault, shallowEqual } from 'client/lazy-app/util';
|
||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import linkState from 'linkstate';
|
||||
|
||||
export const encode = (
|
||||
signal: AbortSignal,
|
||||
@@ -18,93 +23,286 @@ interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
options: EncodeOptions;
|
||||
effort: number;
|
||||
quality: number;
|
||||
alphaQuality: number;
|
||||
passes: number;
|
||||
sns: number;
|
||||
uvMode: number;
|
||||
lossless: boolean;
|
||||
slightLoss: number;
|
||||
colorSpace: number;
|
||||
errorDiffusion: number;
|
||||
useRandomMatrix: boolean;
|
||||
showAdvanced: boolean;
|
||||
separateAlpha: boolean;
|
||||
}
|
||||
|
||||
export class Options extends Component<Props, State> {
|
||||
state: State = {
|
||||
showAdvanced: false,
|
||||
};
|
||||
static getDerivedStateFromProps(
|
||||
props: Props,
|
||||
state: State,
|
||||
): Partial<State> | null {
|
||||
if (state.options && shallowEqual(state.options, props.options)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private onChange = (event: Event) => {
|
||||
const form = (event.currentTarget as HTMLInputElement).closest(
|
||||
'form',
|
||||
) as HTMLFormElement;
|
||||
const { options } = this.props;
|
||||
const newOptions: EncodeOptions = {
|
||||
quality: inputFieldValueAsNumber(form.quality, options.quality),
|
||||
alpha_quality: inputFieldValueAsNumber(
|
||||
form.alpha_quality,
|
||||
options.alpha_quality,
|
||||
),
|
||||
speed: inputFieldValueAsNumber(form.speed, options.speed),
|
||||
pass: inputFieldValueAsNumber(form.pass, options.pass),
|
||||
sns: inputFieldValueAsNumber(form.sns, options.sns),
|
||||
const { options } = props;
|
||||
|
||||
const modifyState: Partial<State> = {
|
||||
options,
|
||||
effort: options.speed,
|
||||
alphaQuality: options.alpha_quality,
|
||||
passes: options.pass,
|
||||
sns: options.sns,
|
||||
uvMode: options.uv_mode,
|
||||
colorSpace: options.csp_type,
|
||||
errorDiffusion: options.error_diffusion,
|
||||
useRandomMatrix: options.use_random_matrix,
|
||||
separateAlpha: options.quality !== options.alpha_quality,
|
||||
};
|
||||
this.props.onChange(newOptions);
|
||||
|
||||
// If quality is > 95, it's lossless with slight loss
|
||||
if (options.quality > 95) {
|
||||
modifyState.lossless = true;
|
||||
modifyState.slightLoss = 100 - options.quality;
|
||||
} else {
|
||||
modifyState.quality = options.quality;
|
||||
modifyState.lossless = false;
|
||||
}
|
||||
|
||||
return modifyState;
|
||||
}
|
||||
|
||||
// Other state is set in getDerivedStateFromProps
|
||||
state: State = {
|
||||
lossless: false,
|
||||
slightLoss: 0,
|
||||
quality: defaultOptions.quality,
|
||||
showAdvanced: false,
|
||||
} as State;
|
||||
|
||||
private _inputChangeCallbacks = new Map<string, (event: Event) => void>();
|
||||
|
||||
private _inputChange = (prop: keyof State, type: 'number' | 'boolean') => {
|
||||
// Cache the callback for performance
|
||||
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 newState: Partial<State> = {
|
||||
[prop]: newVal,
|
||||
};
|
||||
|
||||
const optionState = {
|
||||
...this.state,
|
||||
...newState,
|
||||
};
|
||||
|
||||
const newOptions: EncodeOptions = {
|
||||
speed: optionState.effort,
|
||||
quality: optionState.lossless
|
||||
? 100 - optionState.slightLoss
|
||||
: optionState.quality,
|
||||
alpha_quality: optionState.separateAlpha
|
||||
? optionState.alphaQuality
|
||||
: optionState.quality,
|
||||
pass: optionState.passes,
|
||||
sns: optionState.sns,
|
||||
uv_mode: optionState.uvMode,
|
||||
csp_type: optionState.colorSpace,
|
||||
error_diffusion: optionState.errorDiffusion,
|
||||
use_random_matrix: optionState.useRandomMatrix,
|
||||
};
|
||||
|
||||
// Updating options, so we don't recalculate in getDerivedStateFromProps.
|
||||
newState.options = newOptions;
|
||||
|
||||
this.setState(newState);
|
||||
|
||||
this.props.onChange(newOptions);
|
||||
});
|
||||
}
|
||||
|
||||
return this._inputChangeCallbacks.get(prop)!;
|
||||
};
|
||||
|
||||
render({ options }: Props) {
|
||||
render(
|
||||
{}: Props,
|
||||
{
|
||||
effort,
|
||||
alphaQuality,
|
||||
passes,
|
||||
quality,
|
||||
sns,
|
||||
uvMode,
|
||||
lossless,
|
||||
slightLoss,
|
||||
colorSpace,
|
||||
errorDiffusion,
|
||||
useRandomMatrix,
|
||||
separateAlpha,
|
||||
showAdvanced,
|
||||
}: State,
|
||||
) {
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
checked={lossless}
|
||||
onChange={this._inputChange('lossless', 'boolean')}
|
||||
/>
|
||||
Lossless
|
||||
</label>
|
||||
<Expander>
|
||||
{lossless && (
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="5"
|
||||
step="0.1"
|
||||
value={slightLoss}
|
||||
onInput={this._inputChange('slightLoss', 'number')}
|
||||
>
|
||||
Slight loss:
|
||||
</Range>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<Expander>
|
||||
{!lossless && (
|
||||
<div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="95"
|
||||
step="0.1"
|
||||
value={quality}
|
||||
onInput={this._inputChange('quality', 'number')}
|
||||
>
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
checked={separateAlpha}
|
||||
onChange={this._inputChange('separateAlpha', 'boolean')}
|
||||
/>
|
||||
Separate alpha quality
|
||||
</label>
|
||||
<Expander>
|
||||
{separateAlpha && (
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={alphaQuality}
|
||||
onInput={this._inputChange('alphaQuality', 'number')}
|
||||
>
|
||||
Alpha Quality:
|
||||
</Range>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Show advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced && (
|
||||
<div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
value={passes}
|
||||
onInput={this._inputChange('passes', 'number')}
|
||||
>
|
||||
Passes:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={sns}
|
||||
onInput={this._inputChange('sns', 'number')}
|
||||
>
|
||||
Spatial noise shaping:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={errorDiffusion}
|
||||
onInput={this._inputChange('errorDiffusion', 'number')}
|
||||
>
|
||||
Error diffusion:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionTextFirst}>
|
||||
Subsample chroma:
|
||||
<Select
|
||||
value={uvMode}
|
||||
onInput={this._inputChange('uvMode', 'number')}
|
||||
>
|
||||
<option value={UVMode.UVModeAuto}>Auto</option>
|
||||
<option value={UVMode.UVModeAdapt}>Vary</option>
|
||||
<option value={UVMode.UVMode420}>Half</option>
|
||||
<option value={UVMode.UVMode444}>Off</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionTextFirst}>
|
||||
Color space:
|
||||
<Select
|
||||
value={colorSpace}
|
||||
onInput={this._inputChange('colorSpace', 'number')}
|
||||
>
|
||||
<option value={Csp.kYCoCg}>YCoCg</option>
|
||||
<option value={Csp.kYCbCr}>YCbCr</option>
|
||||
<option value={Csp.kYIQ}>YIQ</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionInputFirst}>
|
||||
<Checkbox
|
||||
checked={useRandomMatrix}
|
||||
onChange={this._inputChange(
|
||||
'useRandomMatrix',
|
||||
'boolean',
|
||||
)}
|
||||
/>
|
||||
Random matrix
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
</div>
|
||||
)}
|
||||
</Expander>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="quality"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="alpha_quality"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.alpha_quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Alpha Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="speed"
|
||||
min="0"
|
||||
max="9"
|
||||
step="1"
|
||||
value={options.speed}
|
||||
onInput={this.onChange}
|
||||
value={effort}
|
||||
onInput={this._inputChange('effort', 'number')}
|
||||
>
|
||||
Speed:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="pass"
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
value={options.pass}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Pass:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="sns"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
value={options.sns}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Spatial noise shaping:
|
||||
Effort:
|
||||
</Range>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -11,16 +11,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type { EncodeOptions } from 'codecs/wp2/enc/wp2_enc';
|
||||
import { UVMode, Csp } from 'codecs/wp2/enc/wp2_enc';
|
||||
|
||||
export { EncodeOptions };
|
||||
export { EncodeOptions, UVMode, Csp };
|
||||
|
||||
export const label = 'WebP v2 (unstable)';
|
||||
export const mimeType = 'image/webp2';
|
||||
export const extension = 'wp2';
|
||||
export const defaultOptions: EncodeOptions = {
|
||||
quality: 75,
|
||||
alpha_quality: 100,
|
||||
alpha_quality: 75,
|
||||
speed: 5,
|
||||
pass: 1,
|
||||
sns: 50,
|
||||
uv_mode: UVMode.UVModeAuto,
|
||||
csp_type: Csp.kYCoCg,
|
||||
error_diffusion: 0,
|
||||
use_random_matrix: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user