import { EncodeOptions } from '../shared/meta'; import type WorkerBridge from 'client/lazy-app/worker-bridge'; import { h, Component } from 'preact'; import { preventDefault, shallowEqual } from 'client/lazy-app/util'; import * as style from 'client/lazy-app/Compress/Options/style.css'; import Range from 'client/lazy-app/Compress/Options/Range'; import Checkbox from 'client/lazy-app/Compress/Options/Checkbox'; import Expander from 'client/lazy-app/Compress/Options/Expander'; export const encode = ( signal: AbortSignal, workerBridge: WorkerBridge, imageData: ImageData, options: EncodeOptions, ) => workerBridge.jxlEncode(signal, imageData, options); interface Props { options: EncodeOptions; onChange(newOptions: EncodeOptions): void; } interface State { options: EncodeOptions; effort: number; quality: number; progressive: boolean; edgePreservingFilter: number; lossless: boolean; slightLoss: boolean; autoEdgePreservingFilter: boolean; decodingSpeedTier: number; photonNoiseIso: number; alternativeLossy: boolean; } export class Options extends Component { static getDerivedStateFromProps( props: Props, state: State, ): Partial | null { if (state.options && shallowEqual(state.options, props.options)) { return null; } const { options } = props; // Create default form state from options return { options, effort: options.effort, quality: options.quality, progressive: options.progressive, edgePreservingFilter: options.epf === -1 ? 2 : options.epf, lossless: options.quality === 100, slightLoss: options.lossyPalette, autoEdgePreservingFilter: options.epf === -1, decodingSpeedTier: options.decodingSpeedTier, photonNoiseIso: options.photonNoiseIso, alternativeLossy: options.lossyModular, }; } // The rest of the defaults are set in getDerivedStateFromProps state: State = { lossless: false, } as State; private _inputChangeCallbacks = new Map void>(); private _inputChange = (prop: keyof State, type: 'number' | 'boolean') => { // Cache the callback for performance if (!this._inputChangeCallbacks.has(prop)) { this._inputChangeCallbacks.set(prop, (event: Event) => { const formEl = event.target as HTMLInputElement | HTMLSelectElement; const newVal = type === 'boolean' ? 'checked' in formEl ? formEl.checked : !!formEl.value : Number(formEl.value); const newState: Partial = { [prop]: newVal, }; const optionState = { ...this.state, ...newState, }; const newOptions: EncodeOptions = { effort: optionState.effort, quality: optionState.lossless ? 100 : optionState.quality, progressive: optionState.progressive, epf: optionState.autoEdgePreservingFilter ? -1 : optionState.edgePreservingFilter, lossyPalette: optionState.lossless ? optionState.slightLoss : false, decodingSpeedTier: optionState.decodingSpeedTier, photonNoiseIso: optionState.photonNoiseIso, lossyModular: optionState.quality < 7 ? true : optionState.alternativeLossy, }; // Updating options, so we don't recalculate in getDerivedStateFromProps. newState.options = newOptions; this.setState(newState); this.props.onChange(newOptions); }); } return this._inputChangeCallbacks.get(prop)!; }; render( {}: Props, { effort, quality, progressive, edgePreservingFilter, lossless, slightLoss, autoEdgePreservingFilter, decodingSpeedTier, photonNoiseIso, alternativeLossy, }: State, ) { // I'm rendering both lossy and lossless forms, as it becomes much easier when // gathering the data. return (
{lossless && ( )} {!lossless && (
Quality:
{!autoEdgePreservingFilter && (
Edge preserving filter:
)}
Optimise for decoding speed (worse compression):
Noise equivalent to ISO:
)}
Effort:
); } }