MozJPEG options updated

This commit is contained in:
Jake Archibald
2018-10-18 19:02:01 +01:00
parent 093331517e
commit 112305bf46
6 changed files with 205 additions and 148 deletions

View File

@@ -30,15 +30,14 @@ export default class QuantizerOptions extends Component<Props, State> {
@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 { options } = this.props;
const options: QuantizeOptions = { const newOptions: QuantizeOptions = {
zx: form.zx ? inputFieldValueAsNumber(form.zx) : this.props.options.zx, zx: inputFieldValueAsNumber(form.zx, options.zx),
maxNumColors: form.maxNumColors maxNumColors: inputFieldValueAsNumber(form.maxNumColors, options.maxNumColors),
? inputFieldValueAsNumber(form.maxNumColors)
: this.props.options.maxNumColors,
dither: inputFieldValueAsNumber(form.dither), dither: inputFieldValueAsNumber(form.dither),
}; };
this.props.onChange(options); this.props.onChange(newOptions);
} }
render({ options }: Props, { extendedSettings }: State) { render({ options }: Props, { extendedSettings }: State) {

View File

@@ -2,108 +2,140 @@ import { h, Component } from 'preact';
import { bind } from '../../lib/initial-util'; import { bind } from '../../lib/initial-util';
import { inputFieldChecked, inputFieldValueAsNumber } from '../../lib/util'; import { inputFieldChecked, inputFieldValueAsNumber } from '../../lib/util';
import { EncodeOptions, MozJpegColorSpace } from './encoder-meta'; import { EncodeOptions, MozJpegColorSpace } from './encoder-meta';
import '../../custom-els/RangeInput'; import * as style from '../../components/options/style.scss';
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;
}
export default class MozJPEGEncoderOptions extends Component<Props, State> {
state: State = {
showAdvanced: false,
};
export default class MozJPEGEncoderOptions extends Component<Props, {}> {
@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 { options } = this.props;
const options: EncodeOptions = { const newOptions: EncodeOptions = {
// Copy over options the form doesn't currently care about, eg arithmetic // Copy over options the form doesn't currently care about, eg arithmetic
...this.props.options, ...this.props.options,
// And now stuff from the form: // And now stuff from the form:
// .checked // .checked
baseline: inputFieldChecked(form.baseline), baseline: inputFieldChecked(form.baseline, options.baseline),
progressive: inputFieldChecked(form.progressive), progressive: inputFieldChecked(form.progressive, options.progressive),
optimize_coding: inputFieldChecked(form.optimize_coding), optimize_coding: inputFieldChecked(form.optimize_coding, options.optimize_coding),
trellis_multipass: inputFieldChecked(form.trellis_multipass), trellis_multipass: inputFieldChecked(form.trellis_multipass, options.trellis_multipass),
trellis_opt_zero: inputFieldChecked(form.trellis_opt_zero), trellis_opt_zero: inputFieldChecked(form.trellis_opt_zero, options.trellis_opt_zero),
trellis_opt_table: inputFieldChecked(form.trellis_opt_table), trellis_opt_table: inputFieldChecked(form.trellis_opt_table, options.trellis_opt_table),
// .value // .value
quality: inputFieldValueAsNumber(form.quality), quality: inputFieldValueAsNumber(form.quality, options.quality),
smoothing: inputFieldValueAsNumber(form.smoothing), smoothing: inputFieldValueAsNumber(form.smoothing, options.smoothing),
color_space: inputFieldValueAsNumber(form.color_space), color_space: inputFieldValueAsNumber(form.color_space, options.color_space),
quant_table: inputFieldValueAsNumber(form.quant_table), quant_table: inputFieldValueAsNumber(form.quant_table, options.quant_table),
trellis_loops: inputFieldValueAsNumber(form.trellis_loops), trellis_loops: inputFieldValueAsNumber(form.trellis_loops, options.trellis_loops),
}; };
this.props.onChange(options); this.props.onChange(newOptions);
} }
render({ options }: Props) { render({ options }: Props, { showAdvanced }: State) {
// 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> <div class={style.optionOneCell}>
Quality: <Range
<range-input
name="quality" name="quality"
min="0" min="0"
max="100" max="100"
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>
<label> <Expander>
<input {showAdvanced ?
<div>
<label class={style.optionInputFirst}>
<Checkbox
name="baseline" name="baseline"
type="checkbox"
checked={options.baseline} checked={options.baseline}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Baseline (worse but legacy-compatible)</span> Pointless spec compliance
</label> </label>
<label style={{ display: options.baseline ? 'none' : '' }}> <Expander>
<input {options.baseline ? null :
<label class={style.optionInputFirst}>
<Checkbox
name="progressive" name="progressive"
type="checkbox"
checked={options.progressive} checked={options.progressive}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Progressive multi-pass rendering</span> Progressive rendering
</label> </label>
<label style={{ display: options.baseline ? '' : 'none' }}> }
<input </Expander>
<Expander>
{options.baseline ?
<label class={style.optionInputFirst}>
<Checkbox
name="optimize_coding" name="optimize_coding"
type="checkbox"
checked={options.optimize_coding} checked={options.optimize_coding}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Optimize Huffman table</span> Optimize Huffman table
</label> </label>
<label> : null
Smoothing: }
<range-input </Expander>
<div class={style.optionOneCell}>
<Range
name="smoothing" name="smoothing"
min="0" min="0"
max="100" max="100"
value={'' + options.smoothing} value={options.smoothing}
onChange={this.onChange} onInput={this.onChange}
/> >
</label> Smoothing:
<label> </Range>
Output color space: </div>
<select <label class={style.optionTextFirst}>
Channels:
<Select
name="color_space" name="color_space"
value={'' + options.color_space} value={options.color_space}
onChange={this.onChange} onChange={this.onChange}
> >
<option value={MozJpegColorSpace.GRAYSCALE}>Grayscale</option> <option value={MozJpegColorSpace.GRAYSCALE}>Grayscale</option>
<option value={MozJpegColorSpace.RGB}>RGB (sub-optimal)</option> <option value={MozJpegColorSpace.RGB}>RGB</option>
<option value={MozJpegColorSpace.YCbCr}>YCbCr (optimized for color)</option> <option value={MozJpegColorSpace.YCbCr}>YCbCr</option>
</select> </Select>
</label> </label>
<label> <label class={style.optionTextFirst}>
Quantization table: Quantization:
<select <Select
name="quant_table" name="quant_table"
value={'' + options.quant_table} value={options.quant_table}
onChange={this.onChange} onChange={this.onChange}
> >
<option value="0">JPEG Annex K</option> <option value="0">JPEG Annex K</option>
@@ -115,45 +147,52 @@ export default class MozJPEGEncoderOptions extends Component<Props, {}> {
<option value="6">Watson et al</option> <option value="6">Watson et al</option>
<option value="7">Ahumada et al</option> <option value="7">Ahumada et al</option>
<option value="8">Peterson et al</option> <option value="8">Peterson et al</option>
</select> </Select>
</label> </label>
<label> <label class={style.optionInputFirst}>
<input <Checkbox
name="trellis_multipass" name="trellis_multipass"
type="checkbox"
checked={options.trellis_multipass} checked={options.trellis_multipass}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Consider multiple scans during trellis quantization</span> Trellis multipass
</label> </label>
<label style={{ display: options.trellis_multipass ? '' : 'none' }}> <Expander>
<input {options.trellis_multipass ?
<label class={style.optionInputFirst}>
<Checkbox
name="trellis_opt_zero" name="trellis_opt_zero"
type="checkbox"
checked={options.trellis_opt_zero} checked={options.trellis_opt_zero}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Optimize runs of zero blocks</span> Optimize zero block runs
</label> </label>
<label> : null
<input }
</Expander>
<label class={style.optionInputFirst}>
<Checkbox
name="trellis_opt_table" name="trellis_opt_table"
type="checkbox"
checked={options.trellis_opt_table} checked={options.trellis_opt_table}
onChange={this.onChange} onChange={this.onChange}
/> />
<span>Optimize after trellis quantization</span> Optimize after trellis quantization
</label> </label>
<label> <div class={style.optionOneCell}>
Trellis quantization passes: <Range
<range-input
name="trellis_loops" name="trellis_loops"
min="1" min="1"
max="50" max="50"
value={'' + options.trellis_loops} value={options.trellis_loops}
onChange={this.onChange} onInput={this.onChange}
/> >
</label> Trellis quantization passes:
</Range>
</div>
</div>
: null
}
</Expander>
</form> </form>
); );
} }

View File

@@ -1,7 +1,7 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import linkState from 'linkstate'; import linkState from 'linkstate';
import { bind, linkRef } from '../../lib/initial-util'; import { bind, linkRef } from '../../lib/initial-util';
import { inputFieldValueAsNumber } from '../../lib/util'; import { inputFieldValueAsNumber, inputFieldValue } from '../../lib/util';
import { ResizeOptions } from './processor-meta'; import { ResizeOptions } from './processor-meta';
import * as style from '../../components/options/style.scss'; import * as style from '../../components/options/style.scss';
import Checkbox from '../../components/checkbox'; import Checkbox from '../../components/checkbox';
@@ -27,18 +27,21 @@ export default class ResizerOptions extends Component<Props, State> {
form?: HTMLFormElement; form?: HTMLFormElement;
private reportOptions() { private reportOptions() {
const width = this.form!.width as HTMLInputElement; const form = this.form!;
const height = this.form!.height as HTMLInputElement; const width = form.width as HTMLInputElement;
const height = form.height as HTMLInputElement;
const { options } = this.props;
if (!width.checkValidity() || !height.checkValidity()) return; if (!width.checkValidity() || !height.checkValidity()) return;
const options: ResizeOptions = { const newOptions: ResizeOptions = {
width: inputFieldValueAsNumber(width), width: inputFieldValueAsNumber(width),
height: inputFieldValueAsNumber(height), height: inputFieldValueAsNumber(height),
method: this.form!.resizeMethod.value, method: form.resizeMethod.value,
fitMethod: this.form!.fitMethod ? this.form!.fitMethod.value : this.props.options.fitMethod, // Casting, as the formfield only returns the correct values.
fitMethod: inputFieldValue(form.fitMethod, options.fitMethod) as ResizeOptions['fitMethod'],
}; };
this.props.onChange(options); this.props.onChange(newOptions);
} }
@bind @bind

View File

@@ -192,8 +192,6 @@ export default class Options extends Component<Props, State> {
} }
</section> </section>
{/*
{EncoderOptionComponent && {EncoderOptionComponent &&
<EncoderOptionComponent <EncoderOptionComponent
options={ options={
@@ -205,6 +203,8 @@ export default class Options extends Component<Props, State> {
/> />
} }
{/*
<div class={style.row}> <div class={style.row}>
<button onClick={this.onCopyToOtherClick}>Copy settings to other side</button> <button onClick={this.onCopyToOtherClick}>Copy settings to other side</button>
</div> </div>

View File

@@ -29,6 +29,7 @@
box-sizing: border-box; box-sizing: border-box;
text-decoration: underline; text-decoration: underline;
text-decoration-style: dotted; text-decoration-style: dotted;
text-underline-position: under;
width: 48px; width: 48px;
position: relative; position: relative;
left: 5px; left: 5px;

View File

@@ -203,25 +203,40 @@ export function nativeResize(
/** /**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange. * @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
* @param defaultVal Value to return if 'field' doesn't exist.
*/ */
export function inputFieldValueAsNumber(field: any): number { export function inputFieldValueAsNumber(field: any, defaultVal: number = 0): number {
return Number((field as HTMLInputElement).value); if (!field) return defaultVal;
return Number(inputFieldValue(field));
} }
/** /**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange. * @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
* @param defaultVal Value to return if 'field' doesn't exist.
*/ */
export function inputFieldCheckedAsNumber(field: any): number { export function inputFieldCheckedAsNumber(field: any, defaultVal: number = 0): number {
if (!field) return defaultVal;
return Number(inputFieldChecked(field)); return Number(inputFieldChecked(field));
} }
/** /**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange. * @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
* @param defaultVal Value to return if 'field' doesn't exist.
*/ */
export function inputFieldChecked(field: any): boolean { export function inputFieldChecked(field: any, defaultVal: boolean = false): boolean {
if (!field) return defaultVal;
return (field as HTMLInputElement).checked; return (field as HTMLInputElement).checked;
} }
/**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
* @param defaultVal Value to return if 'field' doesn't exist.
*/
export function inputFieldValue(field: any, defaultVal: string = ''): string {
if (!field) return defaultVal;
return (field as HTMLInputElement).value;
}
/** /**
* Creates a promise that resolves when the user types the konami code. * Creates a promise that resolves when the user types the konami code.
*/ */