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");
struct JXLOptions {
// 1 = slowest
// 7 = fastest
int speed;
int effort;
float quality;
bool progressive;
int epf;
int nearLossless;
bool lossyPalette;
size_t decodingSpeedTier;
float photonNoiseIso;
};
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;
#endif
cparams.epf = options.epf;
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed);
cparams.decoding_speed_tier = options.decodingSpeedTier;
size_t st = 10 - options.effort;
cparams.speed_tier = jxl::SpeedTier(st);
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.palette_colors = 0;
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) {
value_object<JXLOptions>("JXLOptions")
.field("speed", &JXLOptions::speed)
.field("effort", &JXLOptions::effort)
.field("quality", &JXLOptions::quality)
.field("progressive", &JXLOptions::progressive)
.field("nearLossless", &JXLOptions::nearLossless)
.field("lossyPalette", &JXLOptions::lossyPalette)
.field("decodingSpeedTier", &JXLOptions::decodingSpeedTier)
.field("photonNoiseIso", &JXLOptions::photonNoiseIso)
.field("epf", &JXLOptions::epf);
function("encode", &encode);

View File

@@ -1,11 +1,11 @@
export interface EncodeOptions {
speed: number;
effort: number;
quality: number;
progressive: boolean;
epf: number;
nearLossless: number;
lossyPalette: boolean;
decodingSpeedTier: number;
photonNoiseIso: number;
}
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',
];
function getPrescision(value: string): number {
function getPrecision(value: string): number {
const afterDecimal = value.split('.')[1];
return afterDecimal ? afterDecimal.length : 0;
}
@@ -112,18 +112,24 @@ class RangeInputElement extends HTMLElement {
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 = () => {
// Not connected?
if (!this._valueDisplay) return;
const value = Number(this.value) || 0;
const min = Number(this.min) || 0;
const max = Number(this.max) || 100;
const labelPrecision =
Number(this.labelPrecision) || getPrescision(this.step) || 0;
const percent = (100 * (value - min)) / (max - min);
const displayValue = labelPrecision
? value.toFixed(labelPrecision)
: Math.round(value).toString();
const displayValue = this._getDisplayValue(value);
this._valueDisplay!.textContent = displayValue;
this.style.setProperty('--value-percent', percent + '%');

View File

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

View File

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

View File

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