Compare commits

...

6 Commits

Author SHA1 Message Date
b9611716fc Fixing lossless slider for webP. Previously you couldn't select "9" :D (#236)
* MozJPEG chroma subsampling and quality (#235)

* Adding chroma subsampling for mozjpeg

* Adding separate chroma quality.

* Preact sometimes removes the inline styles, this fixes it.

* Simplifying chroma subsample

* Adding comments

* Fixing lossless slider for webP. Previously you couldn't select "9" :D

* Destructuring args.
2018-11-06 13:47:42 +00:00
798f7ee275 Adding comments 2018-11-06 13:46:17 +00:00
e8192b4aed Simplifying chroma subsample 2018-11-06 13:46:17 +00:00
4b671395fb Preact sometimes removes the inline styles, this fixes it. 2018-11-06 13:46:16 +00:00
c59f4c4aaf Adding separate chroma quality. 2018-11-06 13:46:16 +00:00
1bdc23364a Adding chroma subsampling for mozjpeg 2018-11-06 13:46:16 +00:00
9 changed files with 120 additions and 24 deletions

View File

@ -39,5 +39,9 @@ struct MozJpegOptions {
bool trellis_opt_zero; bool trellis_opt_zero;
bool trellis_opt_table; bool trellis_opt_table;
int trellis_loops; int trellis_loops;
bool auto_subsample;
int chroma_subsample;
bool separate_chroma_quality;
int chroma_quality;
}; };
``` ```

View File

@ -21,7 +21,7 @@
console.log('Version:', module.version().toString(16)); console.log('Version:', module.version().toString(16));
const image = await loadImage('../example.png'); const image = await loadImage('../example.png');
const result = module.encode(image.data, image.width, image.height, { const result = module.encode(image.data, image.width, image.height, {
quality: 40, quality: 75,
baseline: false, baseline: false,
arithmetic: false, arithmetic: false,
progressive: true, progressive: true,
@ -29,10 +29,14 @@
smoothing: 0, smoothing: 0,
color_space: 3, color_space: 3,
quant_table: 3, quant_table: 3,
trellis_multipass: true, trellis_multipass: false,
trellis_opt_zero: true, trellis_opt_zero: false,
trellis_opt_table: true, trellis_opt_table: false,
trellis_loops: 1, trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75,
}); });
const blob = new Blob([result], {type: 'image/jpeg'}); const blob = new Blob([result], {type: 'image/jpeg'});

View File

@ -29,6 +29,10 @@ struct MozJpegOptions {
bool trellis_opt_zero; bool trellis_opt_zero;
bool trellis_opt_table; bool trellis_opt_table;
int trellis_loops; int trellis_loops;
bool auto_subsample;
int chroma_subsample;
bool separate_chroma_quality;
int chroma_quality;
}; };
int version() { int version() {
@ -119,9 +123,6 @@ val encode(std::string image_in, int image_width, int image_height, MozJpegOptio
*/ */
jpeg_set_defaults(&cinfo); jpeg_set_defaults(&cinfo);
/* Now you can set any non-default parameters you wish to.
* Here we just illustrate the use of quality (quantization table) scaling:
*/
jpeg_set_colorspace(&cinfo, (J_COLOR_SPACE) opts.color_space); jpeg_set_colorspace(&cinfo, (J_COLOR_SPACE) opts.color_space);
if (opts.quant_table != -1) { if (opts.quant_table != -1) {
@ -142,11 +143,23 @@ val encode(std::string image_in, int image_width, int image_height, MozJpegOptio
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_Q_OPT, opts.trellis_opt_table); jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_Q_OPT, opts.trellis_opt_table);
jpeg_c_set_int_param(&cinfo, JINT_TRELLIS_NUM_LOOPS, opts.trellis_loops); jpeg_c_set_int_param(&cinfo, JINT_TRELLIS_NUM_LOOPS, opts.trellis_loops);
// A little hacky to build a string for this, but it means we can use set_quality_ratings which
// does some useful heuristic stuff.
std::string quality_str = std::to_string(opts.quality); std::string quality_str = std::to_string(opts.quality);
if (opts.separate_chroma_quality && opts.color_space == JCS_YCbCr) {
quality_str += "," + std::to_string(opts.chroma_quality);
}
char const *pqual = quality_str.c_str(); char const *pqual = quality_str.c_str();
set_quality_ratings(&cinfo, (char*) pqual, opts.baseline); set_quality_ratings(&cinfo, (char*) pqual, opts.baseline);
if (!opts.auto_subsample && opts.color_space == JCS_YCbCr) {
cinfo.comp_info[0].h_samp_factor = opts.chroma_subsample;
cinfo.comp_info[0].v_samp_factor = opts.chroma_subsample;
}
if (!opts.baseline && opts.progressive) { if (!opts.baseline && opts.progressive) {
jpeg_simple_progression(&cinfo); jpeg_simple_progression(&cinfo);
} else { } else {
@ -209,6 +222,10 @@ EMSCRIPTEN_BINDINGS(my_module) {
.field("trellis_opt_zero", &MozJpegOptions::trellis_opt_zero) .field("trellis_opt_zero", &MozJpegOptions::trellis_opt_zero)
.field("trellis_opt_table", &MozJpegOptions::trellis_opt_table) .field("trellis_opt_table", &MozJpegOptions::trellis_opt_table)
.field("trellis_loops", &MozJpegOptions::trellis_loops) .field("trellis_loops", &MozJpegOptions::trellis_loops)
.field("chroma_subsample", &MozJpegOptions::chroma_subsample)
.field("auto_subsample", &MozJpegOptions::auto_subsample)
.field("separate_chroma_quality", &MozJpegOptions::separate_chroma_quality)
.field("chroma_quality", &MozJpegOptions::chroma_quality)
; ;
function("version", &version); function("version", &version);

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -17,6 +17,10 @@ export interface EncodeOptions {
trellis_opt_zero: boolean; trellis_opt_zero: boolean;
trellis_opt_table: boolean; trellis_opt_table: boolean;
trellis_loops: number; trellis_loops: number;
auto_subsample: boolean;
chroma_subsample: number;
separate_chroma_quality: boolean;
chroma_quality: number;
} }
export interface EncoderState { type: typeof type; options: EncodeOptions; } export interface EncoderState { type: typeof type; options: EncodeOptions; }
@ -38,4 +42,8 @@ export const defaultOptions: EncodeOptions = {
trellis_opt_zero: false, trellis_opt_zero: false,
trellis_opt_table: false, trellis_opt_table: false,
trellis_loops: 1, trellis_loops: 1,
auto_subsample: true,
chroma_subsample: 2,
separate_chroma_quality: false,
chroma_quality: 75,
}; };

View File

@ -39,8 +39,13 @@ export default class MozJPEGEncoderOptions extends Component<Props, State> {
trellis_multipass: inputFieldChecked(form.trellis_multipass, options.trellis_multipass), trellis_multipass: inputFieldChecked(form.trellis_multipass, options.trellis_multipass),
trellis_opt_zero: inputFieldChecked(form.trellis_opt_zero, options.trellis_opt_zero), trellis_opt_zero: inputFieldChecked(form.trellis_opt_zero, options.trellis_opt_zero),
trellis_opt_table: inputFieldChecked(form.trellis_opt_table, options.trellis_opt_table), trellis_opt_table: inputFieldChecked(form.trellis_opt_table, options.trellis_opt_table),
auto_subsample: inputFieldChecked(form.auto_subsample, options.auto_subsample),
separate_chroma_quality:
inputFieldChecked(form.separate_chroma_quality, options.separate_chroma_quality),
// .value // .value
quality: inputFieldValueAsNumber(form.quality, options.quality), quality: inputFieldValueAsNumber(form.quality, options.quality),
chroma_quality: inputFieldValueAsNumber(form.chroma_quality, options.chroma_quality),
chroma_subsample: inputFieldValueAsNumber(form.chroma_subsample, options.chroma_subsample),
smoothing: inputFieldValueAsNumber(form.smoothing, options.smoothing), smoothing: inputFieldValueAsNumber(form.smoothing, options.smoothing),
color_space: inputFieldValueAsNumber(form.color_space, options.color_space), color_space: inputFieldValueAsNumber(form.color_space, options.color_space),
quant_table: inputFieldValueAsNumber(form.quant_table, options.quant_table), quant_table: inputFieldValueAsNumber(form.quant_table, options.quant_table),
@ -75,6 +80,72 @@ export default class MozJPEGEncoderOptions extends Component<Props, State> {
<Expander> <Expander>
{showAdvanced ? {showAdvanced ?
<div> <div>
<label class={style.optionTextFirst}>
Channels:
<Select
name="color_space"
value={options.color_space}
onChange={this.onChange}
>
<option value={MozJpegColorSpace.GRAYSCALE}>Grayscale</option>
<option value={MozJpegColorSpace.RGB}>RGB</option>
<option value={MozJpegColorSpace.YCbCr}>YCbCr</option>
</Select>
</label>
<Expander>
{options.color_space === MozJpegColorSpace.YCbCr ?
<div>
<label class={style.optionInputFirst}>
<Checkbox
name="auto_subsample"
checked={options.auto_subsample}
onChange={this.onChange}
/>
Auto subsample chroma
</label>
<Expander>
{options.auto_subsample ? null :
<div class={style.optionOneCell}>
<Range
name="chroma_subsample"
min="1"
max="4"
value={options.chroma_subsample}
onInput={this.onChange}
>
Subsample chroma by:
</Range>
</div>
}
</Expander>
<label class={style.optionInputFirst}>
<Checkbox
name="separate_chroma_quality"
checked={options.separate_chroma_quality}
onChange={this.onChange}
/>
Separate chroma quality
</label>
<Expander>
{options.separate_chroma_quality ?
<div class={style.optionOneCell}>
<Range
name="chroma_quality"
min="0"
max="100"
value={options.chroma_quality}
onInput={this.onChange}
>
Chroma quality:
</Range>
</div>
: null
}
</Expander>
</div>
: null
}
</Expander>
<label class={style.optionInputFirst}> <label class={style.optionInputFirst}>
<Checkbox <Checkbox
name="baseline" name="baseline"
@ -119,18 +190,6 @@ export default class MozJPEGEncoderOptions extends Component<Props, State> {
Smoothing: Smoothing:
</Range> </Range>
</div> </div>
<label class={style.optionTextFirst}>
Channels:
<Select
name="color_space"
value={options.color_space}
onChange={this.onChange}
>
<option value={MozJpegColorSpace.GRAYSCALE}>Grayscale</option>
<option value={MozJpegColorSpace.RGB}>RGB</option>
<option value={MozJpegColorSpace.YCbCr}>YCbCr</option>
</Select>
</label>
<label class={style.optionTextFirst}> <label class={style.optionTextFirst}>
Quantization: Quantization:
<Select <Select

View File

@ -26,8 +26,10 @@ const losslessPresets:[number, number][] = [
]; ];
const losslessPresetDefault = 6; const losslessPresetDefault = 6;
function determineLosslessQuality(quality: number): number { function determineLosslessQuality(quality: number, method: number): number {
const index = losslessPresets.findIndex(item => item[1] === quality); const index = losslessPresets.findIndex(
([presetMethod, presetQuality]) => presetMethod === method && presetQuality === quality,
);
if (index !== -1) return index; if (index !== -1) return index;
// Quality doesn't match one of the presets. // Quality doesn't match one of the presets.
// This can happen when toggling 'lossless'. // This can happen when toggling 'lossless'.
@ -45,7 +47,7 @@ export default class WebPEncoderOptions extends Component<Props, State> {
const lossless = inputFieldCheckedAsNumber(form.lossless); const lossless = inputFieldCheckedAsNumber(form.lossless);
const { options } = this.props; const { options } = this.props;
const losslessPresetValue = inputFieldValueAsNumber( const losslessPresetValue = inputFieldValueAsNumber(
form.lossless_preset, determineLosslessQuality(options.quality), form.lossless_preset, determineLosslessQuality(options.quality, options.method),
); );
const newOptions: EncodeOptions = { const newOptions: EncodeOptions = {
@ -97,7 +99,7 @@ export default class WebPEncoderOptions extends Component<Props, State> {
name="lossless_preset" name="lossless_preset"
min="0" min="0"
max="9" max="9"
value={determineLosslessQuality(options.quality)} value={determineLosslessQuality(options.quality, options.method)}
onInput={this.onChange} onInput={this.onChange}
> >
Effort: Effort:

View File

@ -57,6 +57,8 @@ class RangeInputElement extends HTMLElement {
this.insertBefore(this._input, this.firstChild); this.insertBefore(this._input, this.firstChild);
this._valueDisplay = this.querySelector('.' + style.valueDisplay) as HTMLDivElement; this._valueDisplay = this.querySelector('.' + style.valueDisplay) as HTMLDivElement;
// Set inline styles (this is useful when used with frameworks which might clear inline styles)
this._update();
} }
get labelPrecision(): string { get labelPrecision(): string {