Compare commits

...

8 Commits

Author SHA1 Message Date
Jason Miller
4e04053e4c LibSquoosh: add smoke test 2021-09-07 14:26:36 -04:00
Jake Archibald
255dfa434a Update pointer-tracker (#1147) 2021-09-07 14:43:19 +01:00
François Beaufort
0bef05bcd4 Remove origin trial header 2021-09-06 15:00:48 +01:00
Surma
00cfdafdf3 Merge pull request #1138 from jcao219/fix-windows-path
Use pathify to calculate absolute paths for cross-platform compatibility
2021-08-31 13:34:28 +01:00
Jimmy Cao
92f52319da Use pathify to calculate absolute paths for cross-platform compatibility 2021-08-31 00:18:21 -07:00
Jake Archibald
023304803f JPEG-XL: Better 'effort' and adding noise synth (#1039) 2021-08-27 14:18:54 +01:00
Vishal
6b08cd2355 Aliasing toggle button (#1132) 2021-08-26 16:44:20 +01:00
Jake Archibald
db1a5138e6 Fix pinch zoom on iOS (#1133) 2021-08-26 15:59:42 +01:00
29 changed files with 372 additions and 2168 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.

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,8 @@
"/build/*" "/build/*"
], ],
"scripts": { "scripts": {
"build": "rollup -c" "build": "rollup -c",
"test": "uvu -r ts-node/register test"
}, },
"keywords": [], "keywords": [],
"author": "Google Chrome Developers <chromium-dev@google.com>", "author": "Google Chrome Developers <chromium-dev@google.com>",
@@ -34,7 +35,9 @@
"@types/node": "^15.6.1", "@types/node": "^15.6.1",
"rollup": "^2.46.0", "rollup": "^2.46.0",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"ts-node": "^10.2.1",
"typescript": "^4.1.3", "typescript": "^4.1.3",
"uvu": "^0.5.1",
"which": "^2.0.2" "which": "^2.0.2"
} }
} }

View File

@@ -19,7 +19,7 @@ export function instantiateEmscriptenWasm<T extends EmscriptenWasm.Module>(
// These will have changed in the bundling process and // These will have changed in the bundling process and
// we need to inject them here. // we need to inject them here.
if (requestPath.endsWith('.wasm')) return pathify(path); if (requestPath.endsWith('.wasm')) return pathify(path);
if (requestPath.endsWith('.worker.js')) return new URL(workerJS).pathname; if (requestPath.endsWith('.worker.js')) return pathify(workerJS);
return requestPath; return requestPath;
}, },
}); });

View File

@@ -0,0 +1,43 @@
import * as path from 'path';
import { test } from 'uvu';
import * as assert from 'uvu/assert';
import { ImagePool } from '..';
let imagePool: ImagePool;
test.after.each(async () => {
if (imagePool) {
try {
await imagePool.close();
} catch (e) {}
}
imagePool = undefined;
});
test('smoke test', async () => {
imagePool = new ImagePool(1);
const imagePath = path.resolve(__dirname, '../../icon-large-maskable.png');
const image = imagePool.ingestImage(imagePath);
const { bitmap } = await image.decoded;
assert.equal(bitmap.width, 1024);
await image.preprocess({
resize: {
enabled: true,
width: 100,
},
});
await image.encode({
mozjpeg: {},
});
const { size } = await image.encodedWith.mozjpeg;
// resulting image is 1554b
assert.ok(size > 500);
assert.ok(size < 5000);
});
test.run();

View File

@@ -5,5 +5,12 @@
"types": ["node"], "types": ["node"],
"allowJs": true "allowJs": true
}, },
"include": ["src/**/*", "../codecs/**/*"] "include": ["src/**/*", "../codecs/**/*"],
"ts-node": {
"transpileOnly": true,
"compilerOptions": {
"module": "commonjs"
},
"include": ["tests/**/*"]
}
} }

28
package-lock.json generated
View File

@@ -32,7 +32,7 @@
"lodash.camelcase": "^4.3.0", "lodash.camelcase": "^4.3.0",
"mime-types": "^2.1.28", "mime-types": "^2.1.28",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pointer-tracker": "^2.4.0", "pointer-tracker": "^2.5.3",
"postcss": "^7.0.35", "postcss": "^7.0.35",
"postcss-modules": "^3.2.2", "postcss-modules": "^3.2.2",
"postcss-nested": "^4.2.3", "postcss-nested": "^4.2.3",
@@ -48,6 +48,20 @@
"which": "^2.0.2" "which": "^2.0.2"
} }
}, },
"../pointer-tracker": {
"version": "2.5.0",
"extraneous": true,
"license": "Apache-2.0",
"devDependencies": {
"husky": "^4.2.5",
"lint-staged": "^10.2.11",
"prettier": "^2.0.5",
"rollup": "^2.23.1",
"rollup-plugin-terser": "^7.0.0",
"rollup-plugin-typescript2": "^0.27.2",
"typescript": "^3.9.7"
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.10.4", "version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@@ -3871,9 +3885,9 @@
} }
}, },
"node_modules/pointer-tracker": { "node_modules/pointer-tracker": {
"version": "2.4.0", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz", "resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.5.3.tgz",
"integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==", "integrity": "sha512-LiJUeIbzk4dXq678YeyrZ++mdY17q4n/2sBHfU9wIuvmSzdiPgMvmvWN2g8mY4J7YwYOIrqrZUWP/MfFHVwYtg==",
"dev": true "dev": true
}, },
"node_modules/postcss": { "node_modules/postcss": {
@@ -11946,9 +11960,9 @@
} }
}, },
"pointer-tracker": { "pointer-tracker": {
"version": "2.4.0", "version": "2.5.3",
"resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.4.0.tgz", "resolved": "https://registry.npmjs.org/pointer-tracker/-/pointer-tracker-2.5.3.tgz",
"integrity": "sha512-pWI2tpaM/XNtc9mUTv42Rmjf6mkHvE8LT5DDEq0G7baPNhxNM9E3CepubPplSoSLk9E5bwQrAMyDcPVmJyTW4g==", "integrity": "sha512-LiJUeIbzk4dXq678YeyrZ++mdY17q4n/2sBHfU9wIuvmSzdiPgMvmvWN2g8mY4J7YwYOIrqrZUWP/MfFHVwYtg==",
"dev": true "dev": true
}, },
"postcss": { "postcss": {

View File

@@ -32,7 +32,7 @@
"lodash.camelcase": "^4.3.0", "lodash.camelcase": "^4.3.0",
"mime-types": "^2.1.28", "mime-types": "^2.1.28",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"pointer-tracker": "^2.4.0", "pointer-tracker": "^2.5.3",
"postcss": "^7.0.35", "postcss": "^7.0.35",
"postcss-modules": "^3.2.2", "postcss-modules": "^3.2.2",
"postcss-nested": "^4.2.3", "postcss-nested": "^4.2.3",

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

@@ -1,5 +1,6 @@
import PointerTracker, { Pointer } from 'pointer-tracker'; import PointerTracker, { Pointer } from 'pointer-tracker';
import 'add-css:./styles.css'; import 'add-css:./styles.css';
import { isSafari } from 'client/lazy-app/util';
interface Point { interface Point {
clientX: number; clientX: number;
@@ -105,14 +106,23 @@ export default class PinchZoom extends HTMLElement {
const pointerTracker: PointerTracker = new PointerTracker(this, { const pointerTracker: PointerTracker = new PointerTracker(this, {
start: (pointer, event) => { start: (pointer, event) => {
// We only want to track 2 pointers at most // We only want to track 2 pointers at most
if (pointerTracker.currentPointers.length === 2 || !this._positioningEl) if (
pointerTracker.currentPointers.length === 2 ||
!this._positioningEl
) {
return false; return false;
}
event.preventDefault(); event.preventDefault();
return true; return true;
}, },
move: (previousPointers) => { move: (previousPointers) => {
this._onPointerMove(previousPointers, pointerTracker.currentPointers); this._onPointerMove(previousPointers, pointerTracker.currentPointers);
}, },
// Unfortunately Safari on iOS has a bug where pointer event capturing
// doesn't work in some cases, and we hit those cases due to our event
// retargeting in pinch-zoom.
// https://bugs.webkit.org/show_bug.cgi?id=220196
avoidPointerEvents: isSafari,
}); });
this.addEventListener('wheel', (event) => this._onWheel(event)); this.addEventListener('wheel', (event) => this._onWheel(event));

View File

@@ -5,8 +5,10 @@ import './custom-els/PinchZoom';
import './custom-els/TwoUp'; import './custom-els/TwoUp';
import * as style from './style.css'; import * as style from './style.css';
import 'add-css:./style.css'; import 'add-css:./style.css';
import { shallowEqual } from '../../util'; import { shallowEqual, isSafari } from '../../util';
import { import {
ToggleAliasingIcon,
ToggleAliasingActiveIcon,
ToggleBackgroundIcon, ToggleBackgroundIcon,
AddIcon, AddIcon,
RemoveIcon, RemoveIcon,
@@ -19,7 +21,6 @@ import { cleanSet } from '../../util/clean-modify';
import type { SourceImage } from '../../Compress'; import type { SourceImage } from '../../Compress';
import { linkRef } from 'shared/prerendered-app/util'; import { linkRef } from 'shared/prerendered-app/util';
import { drawDataToCanvas } from 'client/lazy-app/util/canvas'; import { drawDataToCanvas } from 'client/lazy-app/util/canvas';
interface Props { interface Props {
source?: SourceImage; source?: SourceImage;
preprocessorState?: PreprocessorState; preprocessorState?: PreprocessorState;
@@ -35,6 +36,7 @@ interface State {
scale: number; scale: number;
editingScale: boolean; editingScale: boolean;
altBackground: boolean; altBackground: boolean;
aliasing: boolean;
} }
const scaleToOpts: ScaleToOpts = { const scaleToOpts: ScaleToOpts = {
@@ -49,6 +51,7 @@ export default class Output extends Component<Props, State> {
scale: 1, scale: 1,
editingScale: false, editingScale: false,
altBackground: false, altBackground: false,
aliasing: false,
}; };
canvasLeft?: HTMLCanvasElement; canvasLeft?: HTMLCanvasElement;
canvasRight?: HTMLCanvasElement; canvasRight?: HTMLCanvasElement;
@@ -145,6 +148,12 @@ export default class Output extends Component<Props, State> {
return props.rightCompressed || (props.source && props.source.preprocessed); return props.rightCompressed || (props.source && props.source.preprocessed);
} }
private toggleAliasing = () => {
this.setState((state) => ({
aliasing: !state.aliasing,
}));
};
private toggleBackground = () => { private toggleBackground = () => {
this.setState({ this.setState({
altBackground: !this.state.altBackground, altBackground: !this.state.altBackground,
@@ -255,7 +264,7 @@ export default class Output extends Component<Props, State> {
render( render(
{ mobileView, leftImgContain, rightImgContain, source }: Props, { mobileView, leftImgContain, rightImgContain, source }: Props,
{ scale, editingScale, altBackground }: State, { scale, editingScale, altBackground, aliasing }: State,
) { ) {
const leftDraw = this.leftDrawable(); const leftDraw = this.leftDrawable();
const rightDraw = this.rightDrawable(); const rightDraw = this.rightDrawable();
@@ -275,7 +284,11 @@ export default class Output extends Component<Props, State> {
onTouchStartCapture={this.onRetargetableEvent} onTouchStartCapture={this.onRetargetableEvent}
onTouchEndCapture={this.onRetargetableEvent} onTouchEndCapture={this.onRetargetableEvent}
onTouchMoveCapture={this.onRetargetableEvent} onTouchMoveCapture={this.onRetargetableEvent}
onPointerDownCapture={this.onRetargetableEvent} onPointerDownCapture={
// We avoid pointer events in our PinchZoom due to a Safari bug.
// That means we also need to avoid them here too, else we end up preventing the fallback mouse events.
isSafari ? undefined : this.onRetargetableEvent
}
onMouseDownCapture={this.onRetargetableEvent} onMouseDownCapture={this.onRetargetableEvent}
onWheelCapture={this.onRetargetableEvent} onWheelCapture={this.onRetargetableEvent}
> >
@@ -285,7 +298,9 @@ export default class Output extends Component<Props, State> {
ref={linkRef(this, 'pinchZoomLeft')} ref={linkRef(this, 'pinchZoomLeft')}
> >
<canvas <canvas
class={style.pinchTarget} class={`${style.pinchTarget} ${
aliasing ? style.pixelated : ''
}`}
ref={linkRef(this, 'canvasLeft')} ref={linkRef(this, 'canvasLeft')}
width={leftDraw && leftDraw.width} width={leftDraw && leftDraw.width}
height={leftDraw && leftDraw.height} height={leftDraw && leftDraw.height}
@@ -301,7 +316,9 @@ export default class Output extends Component<Props, State> {
ref={linkRef(this, 'pinchZoomRight')} ref={linkRef(this, 'pinchZoomRight')}
> >
<canvas <canvas
class={style.pinchTarget} class={`${style.pinchTarget} ${
aliasing ? style.pixelated : ''
}`}
ref={linkRef(this, 'canvasRight')} ref={linkRef(this, 'canvasRight')}
width={rightDraw && rightDraw.width} width={rightDraw && rightDraw.width}
height={rightDraw && rightDraw.height} height={rightDraw && rightDraw.height}
@@ -345,10 +362,31 @@ export default class Output extends Component<Props, State> {
</button> </button>
</div> </div>
<div class={style.buttonGroup}> <div class={style.buttonGroup}>
<button class={style.firstButton} onClick={this.onRotateClick}> <button
class={style.firstButton}
onClick={this.onRotateClick}
title="Rotate"
>
<RotateIcon /> <RotateIcon />
</button> </button>
<button class={style.lastButton} onClick={this.toggleBackground}> {!isSafari && (
<button
class={style.button}
onClick={this.toggleAliasing}
title="Toggle smoothing"
>
{aliasing ? (
<ToggleAliasingActiveIcon />
) : (
<ToggleAliasingIcon />
)}
</button>
)}
<button
class={style.lastButton}
onClick={this.toggleBackground}
title="Toggle background"
>
{altBackground ? ( {altBackground ? (
<ToggleBackgroundActiveIcon /> <ToggleBackgroundActiveIcon />
) : ( ) : (

View File

@@ -86,8 +86,7 @@
font-size: 1.2rem; font-size: 1.2rem;
cursor: pointer; cursor: pointer;
&:focus { &:focus-visible {
/* box-shadow: 0 0 0 2px var(--hot-pink); */
box-shadow: 0 0 0 2px #fff; box-shadow: 0 0 0 2px #fff;
outline: none; outline: none;
z-index: 1; z-index: 1;
@@ -161,3 +160,8 @@ input.zoom {
pointer-events: auto; pointer-events: auto;
} }
} }
.pixelated {
image-rendering: crisp-edges;
image-rendering: pixelated;
}

View File

@@ -11,6 +11,25 @@ const Icon = (props: preact.JSX.HTMLAttributes) => (
/> />
); );
export const ToggleAliasingIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<circle
cx="12"
cy="12"
r="8"
fill="none"
stroke="currentColor"
stroke-width="2"
/>
</Icon>
);
export const ToggleAliasingActiveIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}>
<path d="M12 3h5v2h2v2h2v5h-2V9h-2V7h-2V5h-3V3M21 12v5h-2v2h-2v2h-5v-2h3v-2h2v-2h2v-3h2M12 21H7v-2H5v-2H3v-5h2v3h2v2h2v2h3v2M3 12V7h2V5h2V3h5v2H9v2H7v2H5v3H3" />
</Icon>
);
export const ToggleBackgroundIcon = (props: preact.JSX.HTMLAttributes) => ( export const ToggleBackgroundIcon = (props: preact.JSX.HTMLAttributes) => (
<Icon {...props}> <Icon {...props}>
<path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.9 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h10a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z" /> <path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm2 4v-2H3c0 1.1.9 2 2 2zM3 9h2V7H3v2zm12 12h2v-2h-2v2zm4-18H9a2 2 0 0 0-2 2v10c0 1.1.9 2 2 2h10a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2zm0 12H9V5h10v10zm-8 6h2v-2h-2v2zm-4 0h2v-2H7v2z" />

View File

@@ -14,6 +14,11 @@
import * as WebCodecs from '../util/web-codecs'; import * as WebCodecs from '../util/web-codecs';
import { drawableToImageData } from './canvas'; import { drawableToImageData } from './canvas';
/** If render engine is Safari */
export const isSafari =
/Safari\//.test(navigator.userAgent) &&
!/Chrom(e|ium)\//.test(navigator.userAgent);
/** /**
* Compare two objects, returning a boolean indicating if * Compare two objects, returning a boolean indicating if
* they have the same properties and strictly equal values. * they have the same properties and strictly equal values.

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,
}; };

View File

@@ -25,18 +25,6 @@ import { lookup as lookupMime } from 'mime-types';
const manifestSize = ({ width, height }: { width: number; height: number }) => const manifestSize = ({ width, height }: { width: number; height: number }) =>
`${width}x${height}`; `${width}x${height}`;
// Set by Netlify
const branch = process.env.BRANCH;
const branchOriginTrialIds = new Map([
[
'live',
'Aj5GY7W9AHM8di+yvMCajIhLRHoYN7slruwOYXE/Iub5hgmW/r2RQt07vrUuT4eUTkWxcyNCAVkiI+5ugdVW3gAAAABUeyJvcmlnaW4iOiJodHRwczovL3NxdW9vc2guYXBwOjQ0MyIsImZlYXR1cmUiOiJXZWJBc3NlbWJseVNpbWQiLCJleHBpcnkiOjE2MjM4MDE1OTl9',
],
]);
const originTrialId = branchOriginTrialIds.get(branch || '');
interface Output { interface Output {
[outputPath: string]: string; [outputPath: string]: string;
} }
@@ -110,15 +98,6 @@ const toOutput: Output = {
/* /*
Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin Cross-Origin-Opener-Policy: same-origin
# Origin trial for WebAssembly SIMD.
${
originTrialId
? ` Origin-Trial: ${originTrialId}`
: `# Cannot find origin trial ID. process.env.BRANCH is: ${JSON.stringify(
branch,
)}`
}
`, `,
}; };