JPEG-XL: Better 'effort' and adding noise synth (#1039)

This commit is contained in:
Jake Archibald
2021-08-27 14:18:54 +01:00
committed by GitHub
parent 6b08cd2355
commit 023304803f
16 changed files with 51 additions and 33 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -10,15 +10,13 @@ using namespace emscripten;
thread_local const val Uint8Array = val::global("Uint8Array"); thread_local const val Uint8Array = val::global("Uint8Array");
struct JXLOptions { struct JXLOptions {
// 1 = slowest int effort;
// 7 = fastest
int speed;
float quality; float quality;
bool progressive; bool progressive;
int epf; int epf;
int nearLossless;
bool lossyPalette; bool lossyPalette;
size_t decodingSpeedTier; size_t decodingSpeedTier;
float photonNoiseIso;
}; };
val encode(std::string image, int width, int height, JXLOptions options) { val encode(std::string image, int width, int height, JXLOptions options) {
@@ -33,11 +31,14 @@ val encode(std::string image, int width, int height, JXLOptions options) {
pool_ptr = &pool; pool_ptr = &pool;
#endif #endif
cparams.epf = options.epf; size_t st = 10 - options.effort;
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed); cparams.speed_tier = jxl::SpeedTier(st);
cparams.decoding_speed_tier = options.decodingSpeedTier;
if (options.lossyPalette || options.nearLossless) { cparams.epf = options.epf;
cparams.decoding_speed_tier = options.decodingSpeedTier;
cparams.photon_noise_iso = options.photonNoiseIso;
if (options.lossyPalette) {
cparams.lossy_palette = true; cparams.lossy_palette = true;
cparams.palette_colors = 0; cparams.palette_colors = 0;
cparams.options.predictor = jxl::Predictor::Zero; cparams.options.predictor = jxl::Predictor::Zero;
@@ -106,12 +107,12 @@ val encode(std::string image, int width, int height, JXLOptions options) {
EMSCRIPTEN_BINDINGS(my_module) { EMSCRIPTEN_BINDINGS(my_module) {
value_object<JXLOptions>("JXLOptions") value_object<JXLOptions>("JXLOptions")
.field("speed", &JXLOptions::speed) .field("effort", &JXLOptions::effort)
.field("quality", &JXLOptions::quality) .field("quality", &JXLOptions::quality)
.field("progressive", &JXLOptions::progressive) .field("progressive", &JXLOptions::progressive)
.field("nearLossless", &JXLOptions::nearLossless)
.field("lossyPalette", &JXLOptions::lossyPalette) .field("lossyPalette", &JXLOptions::lossyPalette)
.field("decodingSpeedTier", &JXLOptions::decodingSpeedTier) .field("decodingSpeedTier", &JXLOptions::decodingSpeedTier)
.field("photonNoiseIso", &JXLOptions::photonNoiseIso)
.field("epf", &JXLOptions::epf); .field("epf", &JXLOptions::epf);
function("encode", &encode); function("encode", &encode);

View File

@@ -1,11 +1,11 @@
export interface EncodeOptions { export interface EncodeOptions {
speed: number; effort: number;
quality: number; quality: number;
progressive: boolean; progressive: boolean;
epf: number; epf: number;
nearLossless: number;
lossyPalette: boolean; lossyPalette: boolean;
decodingSpeedTier: number; decodingSpeedTier: number;
photonNoiseIso: number;
} }
export interface JXLModule extends EmscriptenWasm.Module { export interface JXLModule extends EmscriptenWasm.Module {

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -20,7 +20,7 @@ const REFLECTED_ATTRIBUTES = [
'disabled', 'disabled',
]; ];
function getPrescision(value: string): number { function getPrecision(value: string): number {
const afterDecimal = value.split('.')[1]; const afterDecimal = value.split('.')[1];
return afterDecimal ? afterDecimal.length : 0; return afterDecimal ? afterDecimal.length : 0;
} }
@@ -112,18 +112,24 @@ class RangeInputElement extends HTMLElement {
this.dispatchEvent(retargetted); this.dispatchEvent(retargetted);
}; };
private _getDisplayValue(value: number): string {
if (value >= 10000) return (value / 1000).toFixed(1) + 'k';
const labelPrecision =
Number(this.labelPrecision) || getPrecision(this.step) || 0;
return labelPrecision
? value.toFixed(labelPrecision)
: Math.round(value).toString();
}
private _update = () => { private _update = () => {
// Not connected? // Not connected?
if (!this._valueDisplay) return; if (!this._valueDisplay) return;
const value = Number(this.value) || 0; const value = Number(this.value) || 0;
const min = Number(this.min) || 0; const min = Number(this.min) || 0;
const max = Number(this.max) || 100; const max = Number(this.max) || 100;
const labelPrecision =
Number(this.labelPrecision) || getPrescision(this.step) || 0;
const percent = (100 * (value - min)) / (max - min); const percent = (100 * (value - min)) / (max - min);
const displayValue = labelPrecision const displayValue = this._getDisplayValue(value);
? value.toFixed(labelPrecision)
: Math.round(value).toString();
this._valueDisplay!.textContent = displayValue; this._valueDisplay!.textContent = displayValue;
this.style.setProperty('--value-percent', percent + '%'); this.style.setProperty('--value-percent', percent + '%');

View File

@@ -35,7 +35,7 @@
text-decoration-style: dotted; text-decoration-style: dotted;
text-decoration-color: var(--main-theme-color); text-decoration-color: var(--main-theme-color);
text-underline-position: under; text-underline-position: under;
width: 48px; width: 54px;
position: relative; position: relative;
left: 5px; left: 5px;

View File

@@ -29,10 +29,9 @@ interface State {
slightLoss: boolean; slightLoss: boolean;
autoEdgePreservingFilter: boolean; autoEdgePreservingFilter: boolean;
decodingSpeedTier: number; decodingSpeedTier: number;
photonNoiseIso: number;
} }
const maxSpeed = 7;
export class Options extends Component<Props, State> { export class Options extends Component<Props, State> {
static getDerivedStateFromProps( static getDerivedStateFromProps(
props: Props, props: Props,
@@ -47,7 +46,7 @@ export class Options extends Component<Props, State> {
// Create default form state from options // Create default form state from options
return { return {
options, options,
effort: maxSpeed - options.speed, effort: options.effort,
quality: options.quality, quality: options.quality,
progressive: options.progressive, progressive: options.progressive,
edgePreservingFilter: options.epf === -1 ? 2 : options.epf, edgePreservingFilter: options.epf === -1 ? 2 : options.epf,
@@ -55,6 +54,7 @@ export class Options extends Component<Props, State> {
slightLoss: options.lossyPalette, slightLoss: options.lossyPalette,
autoEdgePreservingFilter: options.epf === -1, autoEdgePreservingFilter: options.epf === -1,
decodingSpeedTier: options.decodingSpeedTier, decodingSpeedTier: options.decodingSpeedTier,
photonNoiseIso: options.photonNoiseIso,
}; };
} }
@@ -87,15 +87,15 @@ export class Options extends Component<Props, State> {
}; };
const newOptions: EncodeOptions = { const newOptions: EncodeOptions = {
speed: maxSpeed - optionState.effort, effort: optionState.effort,
quality: optionState.lossless ? 100 : optionState.quality, quality: optionState.lossless ? 100 : optionState.quality,
progressive: optionState.progressive, progressive: optionState.progressive,
epf: optionState.autoEdgePreservingFilter epf: optionState.autoEdgePreservingFilter
? -1 ? -1
: optionState.edgePreservingFilter, : optionState.edgePreservingFilter,
nearLossless: 0,
lossyPalette: optionState.lossless ? optionState.slightLoss : false, lossyPalette: optionState.lossless ? optionState.slightLoss : false,
decodingSpeedTier: optionState.decodingSpeedTier, decodingSpeedTier: optionState.decodingSpeedTier,
photonNoiseIso: optionState.photonNoiseIso,
}; };
// Updating options, so we don't recalculate in getDerivedStateFromProps. // Updating options, so we don't recalculate in getDerivedStateFromProps.
@@ -121,6 +121,7 @@ export class Options extends Component<Props, State> {
slightLoss, slightLoss,
autoEdgePreservingFilter, autoEdgePreservingFilter,
decodingSpeedTier, decodingSpeedTier,
photonNoiseIso,
}: State, }: 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
@@ -164,7 +165,6 @@ export class Options extends Component<Props, State> {
<label class={style.optionToggle}> <label class={style.optionToggle}>
Auto edge filter Auto edge filter
<Checkbox <Checkbox
name="autoEdgeFilter"
checked={autoEdgePreservingFilter} checked={autoEdgePreservingFilter}
onChange={this._inputChange( onChange={this._inputChange(
'autoEdgePreservingFilter', 'autoEdgePreservingFilter',
@@ -199,6 +199,17 @@ export class Options extends Component<Props, State> {
Optimise for decoding speed (worse compression): Optimise for decoding speed (worse compression):
</Range> </Range>
</div> </div>
<div class={style.optionOneCell}>
<Range
min="0"
max="50000"
step="100"
value={photonNoiseIso}
onInput={this._inputChange('photonNoiseIso', 'number')}
>
Noise equivalent to ISO:
</Range>
</div>
</div> </div>
)} )}
</Expander> </Expander>
@@ -212,8 +223,8 @@ export class Options extends Component<Props, State> {
</label> </label>
<div class={style.optionOneCell}> <div class={style.optionOneCell}>
<Range <Range
min="0" min="3"
max={maxSpeed - 1} max="9"
value={effort} value={effort}
onInput={this._inputChange('effort', 'number')} onInput={this._inputChange('effort', 'number')}
> >

View File

@@ -18,11 +18,11 @@ export const label = 'JPEG XL (beta)';
export const mimeType = 'image/jxl'; export const mimeType = 'image/jxl';
export const extension = 'jxl'; export const extension = 'jxl';
export const defaultOptions: EncodeOptions = { export const defaultOptions: EncodeOptions = {
speed: 4, effort: 7,
quality: 75, quality: 75,
progressive: false, progressive: false,
epf: -1, epf: -1,
nearLossless: 0,
lossyPalette: false, lossyPalette: false,
decodingSpeedTier: 0, decodingSpeedTier: 0,
photonNoiseIso: 0,
}; };