mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 08:47:31 +00:00
WebP options
This commit is contained in:
@@ -2,13 +2,21 @@ import { h, Component } from 'preact';
|
|||||||
import { bind } from '../../lib/initial-util';
|
import { bind } from '../../lib/initial-util';
|
||||||
import { inputFieldCheckedAsNumber, inputFieldValueAsNumber } from '../../lib/util';
|
import { inputFieldCheckedAsNumber, inputFieldValueAsNumber } from '../../lib/util';
|
||||||
import { EncodeOptions, WebPImageHint } from './encoder-meta';
|
import { EncodeOptions, WebPImageHint } from './encoder-meta';
|
||||||
import * as styles from './styles.scss';
|
import * as style from '../../components/options/style.scss';
|
||||||
import '../../custom-els/RangeInput';
|
import Checkbox from '../../components/checkbox';
|
||||||
|
import Expander from '../../components/expander';
|
||||||
|
import Select from '../../components/select';
|
||||||
|
import Range from '../../components/range';
|
||||||
|
import linkState from 'linkstate';
|
||||||
|
|
||||||
type Props = {
|
interface Props {
|
||||||
options: EncodeOptions,
|
options: EncodeOptions;
|
||||||
onChange(newOptions: EncodeOptions): void,
|
onChange(newOptions: EncodeOptions): void;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
showAdvanced: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// From kLosslessPresets in config_enc.c
|
// From kLosslessPresets in config_enc.c
|
||||||
// The format is [method, quality].
|
// The format is [method, quality].
|
||||||
@@ -26,249 +34,281 @@ function determineLosslessQuality(quality: number): number {
|
|||||||
return losslessPresetDefault;
|
return losslessPresetDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class WebPEncoderOptions extends Component<Props, {}> {
|
export default class WebPEncoderOptions extends Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
showAdvanced: false,
|
||||||
|
};
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onChange(event: Event) {
|
onChange(event: Event) {
|
||||||
const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement;
|
const form = (event.currentTarget as HTMLInputElement).closest('form') as HTMLFormElement;
|
||||||
const lossless = inputFieldCheckedAsNumber(form.lossless);
|
const lossless = inputFieldCheckedAsNumber(form.lossless);
|
||||||
const losslessPresetInput = (form.lossless_preset as HTMLInputElement);
|
const { options } = this.props;
|
||||||
|
const losslessPresetValue = inputFieldValueAsNumber(
|
||||||
|
form.lossless_preset, determineLosslessQuality(options.quality),
|
||||||
|
);
|
||||||
|
|
||||||
const options: EncodeOptions = {
|
const newOptions: EncodeOptions = {
|
||||||
// Copy over options the form doesn't care about, eg emulate_jpeg_size
|
// Copy over options the form doesn't care about, eg emulate_jpeg_size
|
||||||
...this.props.options,
|
...options,
|
||||||
// And now stuff from the form:
|
// And now stuff from the form:
|
||||||
lossless,
|
lossless,
|
||||||
// Special-cased inputs:
|
// Special-cased inputs:
|
||||||
// In lossless mode, the quality is derived from the preset.
|
// In lossless mode, the quality is derived from the preset.
|
||||||
quality: lossless ?
|
quality: lossless ?
|
||||||
losslessPresets[Number(losslessPresetInput.value)][1] :
|
losslessPresets[losslessPresetValue][1] :
|
||||||
inputFieldValueAsNumber(form.quality),
|
inputFieldValueAsNumber(form.quality, options.quality),
|
||||||
// In lossless mode, the method is derived from the preset.
|
// In lossless mode, the method is derived from the preset.
|
||||||
method: lossless ?
|
method: lossless ?
|
||||||
losslessPresets[Number(losslessPresetInput.value)][0] :
|
losslessPresets[losslessPresetValue][0] :
|
||||||
inputFieldValueAsNumber(form.method_input),
|
inputFieldValueAsNumber(form.method_input, options.method),
|
||||||
image_hint: (form.image_hint as HTMLInputElement).checked ?
|
image_hint: inputFieldCheckedAsNumber(form.image_hint, options.image_hint) ?
|
||||||
WebPImageHint.WEBP_HINT_GRAPH :
|
WebPImageHint.WEBP_HINT_GRAPH :
|
||||||
WebPImageHint.WEBP_HINT_DEFAULT,
|
WebPImageHint.WEBP_HINT_DEFAULT,
|
||||||
// .checked
|
// .checked
|
||||||
exact: inputFieldCheckedAsNumber(form.exact),
|
exact: inputFieldCheckedAsNumber(form.exact, options.exact),
|
||||||
alpha_compression: inputFieldCheckedAsNumber(form.alpha_compression),
|
alpha_compression: inputFieldCheckedAsNumber(
|
||||||
autofilter: inputFieldCheckedAsNumber(form.autofilter),
|
form.alpha_compression, options.alpha_compression,
|
||||||
filter_type: inputFieldCheckedAsNumber(form.filter_type),
|
),
|
||||||
use_sharp_yuv: inputFieldCheckedAsNumber(form.use_sharp_yuv),
|
autofilter: inputFieldCheckedAsNumber(form.autofilter, options.autofilter),
|
||||||
|
filter_type: inputFieldCheckedAsNumber(form.filter_type, options.filter_type),
|
||||||
|
use_sharp_yuv: inputFieldCheckedAsNumber(form.use_sharp_yuv, options.use_sharp_yuv),
|
||||||
// .value
|
// .value
|
||||||
near_lossless: 100 - inputFieldValueAsNumber(form.near_lossless),
|
near_lossless: 100 - inputFieldValueAsNumber(form.near_lossless, 100 - options.near_lossless),
|
||||||
alpha_quality: inputFieldValueAsNumber(form.alpha_quality),
|
alpha_quality: inputFieldValueAsNumber(form.alpha_quality, options.alpha_quality),
|
||||||
alpha_filtering: inputFieldValueAsNumber(form.alpha_filtering),
|
alpha_filtering: inputFieldValueAsNumber(form.alpha_filtering, options.alpha_filtering),
|
||||||
sns_strength: inputFieldValueAsNumber(form.sns_strength),
|
sns_strength: inputFieldValueAsNumber(form.sns_strength, options.sns_strength),
|
||||||
filter_strength: inputFieldValueAsNumber(form.filter_strength),
|
filter_strength: inputFieldValueAsNumber(form.filter_strength, options.filter_strength),
|
||||||
filter_sharpness: 7 - inputFieldValueAsNumber(form.filter_sharpness),
|
filter_sharpness:
|
||||||
pass: inputFieldValueAsNumber(form.pass),
|
7 - inputFieldValueAsNumber(form.filter_sharpness, 7 - options.filter_sharpness),
|
||||||
preprocessing: inputFieldValueAsNumber(form.preprocessing),
|
pass: inputFieldValueAsNumber(form.pass, options.pass),
|
||||||
segments: inputFieldValueAsNumber(form.segments),
|
preprocessing: inputFieldValueAsNumber(form.preprocessing, options.preprocessing),
|
||||||
partitions: inputFieldValueAsNumber(form.partitions),
|
segments: inputFieldValueAsNumber(form.segments, options.segments),
|
||||||
|
partitions: inputFieldValueAsNumber(form.partitions, options.partitions),
|
||||||
};
|
};
|
||||||
this.props.onChange(options);
|
this.props.onChange(newOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _losslessSpecificOptions(options: EncodeOptions) {
|
private _losslessSpecificOptions(options: EncodeOptions) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div key="lossless">
|
||||||
<label>
|
<div class={style.optionOneCell}>
|
||||||
Effort:
|
<Range
|
||||||
<range-input
|
|
||||||
name="lossless_preset"
|
name="lossless_preset"
|
||||||
min="0"
|
min="0"
|
||||||
max="9"
|
max="9"
|
||||||
value={'' + determineLosslessQuality(options.quality)}
|
value={determineLosslessQuality(options.quality)}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Effort:
|
||||||
<label>
|
</Range>
|
||||||
Slight loss:
|
</div>
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="near_lossless"
|
name="near_lossless"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={'' + (100 - options.near_lossless)}
|
value={'' + (100 - options.near_lossless)}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Slight loss:
|
||||||
<label>
|
</Range>
|
||||||
|
</div>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
{/*
|
{/*
|
||||||
Although there are 3 different kinds of image hint, webp only
|
Although there are 3 different kinds of image hint, webp only
|
||||||
seems to do something with the 'graph' type, and I don't really
|
seems to do something with the 'graph' type, and I don't really
|
||||||
understand what it does.
|
understand what it does.
|
||||||
*/}
|
*/}
|
||||||
<input
|
<Checkbox
|
||||||
name="image_hint"
|
name="image_hint"
|
||||||
type="checkbox"
|
|
||||||
checked={options.image_hint === WebPImageHint.WEBP_HINT_GRAPH}
|
checked={options.image_hint === WebPImageHint.WEBP_HINT_GRAPH}
|
||||||
value={'' + WebPImageHint.WEBP_HINT_GRAPH}
|
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
<span>Discrete tone image (graph, map-tile etc)</span>
|
Discrete tone image
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _lossySpecificOptions(options: EncodeOptions) {
|
private _lossySpecificOptions(options: EncodeOptions) {
|
||||||
|
const { showAdvanced } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div key="lossy">
|
||||||
<label>
|
<div class={style.optionOneCell}>
|
||||||
Effort:
|
<Range
|
||||||
<range-input
|
|
||||||
name="method_input"
|
name="method_input"
|
||||||
min="0"
|
min="0"
|
||||||
max="6"
|
max="6"
|
||||||
value={'' + options.method}
|
value={options.method}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Effort:
|
||||||
<label>
|
</Range>
|
||||||
Quality:
|
</div>
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="quality"
|
name="quality"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
value={'' + options.quality}
|
value={options.quality}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
|
>
|
||||||
|
Quality:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
|
checked={showAdvanced}
|
||||||
|
onChange={linkState(this, 'showAdvanced')}
|
||||||
/>
|
/>
|
||||||
|
Show advanced settings
|
||||||
</label>
|
</label>
|
||||||
<hr />
|
<Expander>
|
||||||
<label>
|
{showAdvanced ?
|
||||||
<input
|
<div>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
name="alpha_compression"
|
name="alpha_compression"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.alpha_compression}
|
checked={!!options.alpha_compression}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
Compress alpha
|
Compress alpha
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<div class={style.optionOneCell}>
|
||||||
Alpha quality:
|
<Range
|
||||||
<range-input
|
|
||||||
name="alpha_quality"
|
name="alpha_quality"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={'' + options.alpha_quality}
|
value={options.alpha_quality}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Alpha quality:
|
||||||
<label>
|
</Range>
|
||||||
Alpha filter quality:
|
</div>
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="alpha_filtering"
|
name="alpha_filtering"
|
||||||
min="0"
|
min="0"
|
||||||
max="2"
|
max="2"
|
||||||
value={'' + options.alpha_filtering}
|
value={options.alpha_filtering}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Alpha filter quality:
|
||||||
<hr />
|
</Range>
|
||||||
<label>
|
</div>
|
||||||
<input
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
name="autofilter"
|
name="autofilter"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.autofilter}
|
checked={!!options.autofilter}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
<span>Auto adjust filter strength</span>
|
Auto adjust filter strength
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<Expander>
|
||||||
Filter strength:
|
{options.autofilter ? null :
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="filter_strength"
|
name="filter_strength"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
disabled={!!options.autofilter}
|
value={options.filter_strength}
|
||||||
value={'' + options.filter_strength}
|
onInput={this.onChange}
|
||||||
onChange={this.onChange}
|
>
|
||||||
/>
|
Filter strength:
|
||||||
</label>
|
</Range>
|
||||||
<label>
|
</div>
|
||||||
<input
|
}
|
||||||
|
</Expander>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
name="filter_type"
|
name="filter_type"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.filter_type}
|
checked={!!options.filter_type}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
Strong filter
|
Strong filter
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<div class={style.optionOneCell}>
|
||||||
Filter sharpness:
|
<Range
|
||||||
<range-input
|
|
||||||
name="filter_sharpness"
|
name="filter_sharpness"
|
||||||
min="0"
|
min="0"
|
||||||
max="7"
|
max="7"
|
||||||
value={'' + (7 - options.filter_sharpness)}
|
value={7 - options.filter_sharpness}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Filter sharpness:
|
||||||
<label>
|
</Range>
|
||||||
<input
|
</div>
|
||||||
|
<label class={style.optionInputFirst}>
|
||||||
|
<Checkbox
|
||||||
name="use_sharp_yuv"
|
name="use_sharp_yuv"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.use_sharp_yuv}
|
checked={!!options.use_sharp_yuv}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
Sharp RGB->YUV conversion
|
Sharp RGB→YUV conversion
|
||||||
</label>
|
</label>
|
||||||
<hr />
|
<div class={style.optionOneCell}>
|
||||||
<label>
|
<Range
|
||||||
Passes:
|
|
||||||
<range-input
|
|
||||||
name="pass"
|
name="pass"
|
||||||
min="1"
|
min="1"
|
||||||
max="10"
|
max="10"
|
||||||
value={'' + options.pass}
|
value={options.pass}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Passes:
|
||||||
<label>
|
</Range>
|
||||||
Spacial noise shaping:
|
</div>
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="sns_strength"
|
name="sns_strength"
|
||||||
min="0"
|
min="0"
|
||||||
max="100"
|
max="100"
|
||||||
value={'' + options.sns_strength}
|
value={options.sns_strength}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Spacial noise shaping:
|
||||||
<label>
|
</Range>
|
||||||
Preprocessing type:
|
</div>
|
||||||
<select
|
<label class={style.optionTextFirst}>
|
||||||
|
Preprocess:
|
||||||
|
<Select
|
||||||
name="preprocessing"
|
name="preprocessing"
|
||||||
value={'' + options.preprocessing}
|
value={options.preprocessing}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
>
|
>
|
||||||
<option value="0">None</option>
|
<option value="0">None</option>
|
||||||
<option value="1">Segment smooth</option>
|
<option value="1">Segment smooth</option>
|
||||||
<option value="2">Pseudo-random dithering</option>
|
<option value="2">Pseudo-random dithering</option>
|
||||||
</select>
|
</Select>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<div class={style.optionOneCell}>
|
||||||
Segments:
|
<Range
|
||||||
<range-input
|
|
||||||
name="segments"
|
name="segments"
|
||||||
min="1"
|
min="1"
|
||||||
max="4"
|
max="4"
|
||||||
value={'' + options.segments}
|
value={options.segments}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Segments:
|
||||||
<label>
|
</Range>
|
||||||
Partitions:
|
</div>
|
||||||
<range-input
|
<div class={style.optionOneCell}>
|
||||||
|
<Range
|
||||||
name="partitions"
|
name="partitions"
|
||||||
min="0"
|
min="0"
|
||||||
max="3"
|
max="3"
|
||||||
value={'' + options.partitions}
|
value={options.partitions}
|
||||||
onChange={this.onChange}
|
onInput={this.onChange}
|
||||||
/>
|
>
|
||||||
</label>
|
Partitions:
|
||||||
|
</Range>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Expander>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -277,32 +317,26 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
|||||||
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
// I'm rendering both lossy and lossless forms, as it becomes much easier when
|
||||||
// gathering the data.
|
// gathering the data.
|
||||||
return (
|
return (
|
||||||
<form>
|
<form class={style.optionsSection}>
|
||||||
<label>
|
<label class={style.optionInputFirst}>
|
||||||
<input
|
<Checkbox
|
||||||
name="lossless"
|
name="lossless"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.lossless}
|
checked={!!options.lossless}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
Lossless
|
Lossless
|
||||||
</label>
|
</label>
|
||||||
<div class={options.lossless ? '' : styles.hide}>
|
{options.lossless
|
||||||
{this._losslessSpecificOptions(options)}
|
? this._losslessSpecificOptions(options)
|
||||||
</div>
|
: this._lossySpecificOptions(options)
|
||||||
<div class={options.lossless ? styles.hide : ''}>
|
}
|
||||||
{this._lossySpecificOptions(options)}
|
<label class={style.optionInputFirst}>
|
||||||
</div>
|
<Checkbox
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
name="exact"
|
name="exact"
|
||||||
type="checkbox"
|
|
||||||
checked={!!options.exact}
|
checked={!!options.exact}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
/>
|
/>
|
||||||
<span>
|
Preserve transparent data
|
||||||
Preserve transparent data. Otherwise, pixels with zero alpha will have RGB also zeroed.
|
|
||||||
</span>
|
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@@ -180,6 +180,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
|
|
||||||
<h2 class={style.optionsTitle}>Compress</h2>
|
<h2 class={style.optionsTitle}>Compress</h2>
|
||||||
|
|
||||||
|
<div class={style.optionsScroller}>
|
||||||
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
<section class={`${style.optionOneCell} ${style.optionsSection}`}>
|
||||||
{encoderSupportMap ?
|
{encoderSupportMap ?
|
||||||
<Select value={encoderState.type} onChange={this.onEncoderTypeChange} large>
|
<Select value={encoderState.type} onChange={this.onEncoderTypeChange} large>
|
||||||
@@ -202,6 +203,7 @@ export default class Options extends Component<Props, State> {
|
|||||||
onChange={onEncoderOptionsChange}
|
onChange={onEncoderOptionsChange}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/*
|
{/*
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ $horizontalPadding: 15px;
|
|||||||
width: 300px;
|
width: 300px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
max-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-title {
|
.options-title {
|
||||||
@@ -55,3 +58,8 @@ $horizontalPadding: 15px;
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.options-scroller {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user