forked from external-repos/squoosh
Compare commits
30 Commits
visdif-err
...
prototype-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b243344f9e | ||
|
|
e0a852cb9f | ||
|
|
7684c8f386 | ||
|
|
b5c1c72e65 | ||
|
|
ad5002c79c | ||
|
|
883bb92e48 | ||
|
|
79efd0d32e | ||
|
|
99741d665d | ||
|
|
b38765b4e6 | ||
|
|
a11ac15008 | ||
|
|
fb7e00067f | ||
|
|
a2121ec47b | ||
|
|
149ebf5a67 | ||
|
|
d8297aad10 | ||
|
|
0e84a5b5f7 | ||
|
|
187a5bed01 | ||
|
|
07a288398e | ||
|
|
dbb31a1add | ||
|
|
6e58e8edb0 | ||
|
|
955079b18f | ||
|
|
f6f70c590e | ||
|
|
55c4aa51ac | ||
|
|
aea316c604 | ||
|
|
d604e94ad2 | ||
|
|
61de471e52 | ||
|
|
955b2ac1ba | ||
|
|
3d225966c5 | ||
|
|
851da25302 | ||
|
|
32e3528666 | ||
|
|
1e52837931 |
@@ -42,7 +42,7 @@ Options:
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
The default values for each `config` option can be found in the [`codecs.js`][codecs.js] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified here. _Better documentation is needed here._
|
||||
The default values for each `config` option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified here. _Better documentation is needed here._
|
||||
|
||||
## Auto optimizer
|
||||
|
||||
@@ -55,5 +55,5 @@ $ npx @squoosh/cli --wp2 auto test.png
|
||||
```
|
||||
|
||||
[squoosh]: https://squoosh.app
|
||||
[codecs.js]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.js
|
||||
[codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts
|
||||
[butteraugli]: https://github.com/google/butteraugli
|
||||
|
||||
21
cli/package-lock.json
generated
21
cli/package-lock.json
generated
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@squoosh/lib": "^0.2.0",
|
||||
"@squoosh/lib": "^0.3.1",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
@@ -18,12 +18,15 @@
|
||||
"bin": {
|
||||
"cli": "src/index.js",
|
||||
"squoosh-cli": "src/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": " ^12.20.2 || ^14.13.1 || ^16.0.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/@squoosh/lib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.2.0.tgz",
|
||||
"integrity": "sha512-zKId9h/LzEnCdoOGnIgjzvqbk5g2aHgfEqgKQ+S+r5K3TasgD/DAsT6r7v6gXft1ao0f/00CTcwIp1KviWTQbw==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.3.1.tgz",
|
||||
"integrity": "sha512-ni8gyTGgW9lH/yqaqgkxerGT7daSWNU9mtvWqj+/1qqzIT9YGfFvnTtDQRMIRjRCKQ5pkK/COufvWKuzHI6Dxw==",
|
||||
"dependencies": {
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
@@ -342,9 +345,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@squoosh/lib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.2.0.tgz",
|
||||
"integrity": "sha512-zKId9h/LzEnCdoOGnIgjzvqbk5g2aHgfEqgKQ+S+r5K3TasgD/DAsT6r7v6gXft1ao0f/00CTcwIp1KviWTQbw==",
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.3.1.tgz",
|
||||
"integrity": "sha512-ni8gyTGgW9lH/yqaqgkxerGT7daSWNU9mtvWqj+/1qqzIT9YGfFvnTtDQRMIRjRCKQ5pkK/COufvWKuzHI6Dxw==",
|
||||
"requires": {
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
{
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.1",
|
||||
"description": "A CLI for Squoosh",
|
||||
"public": true,
|
||||
"type": "module",
|
||||
"homepage": "https://github.com/GoogleChromeLabs/squoosh",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoogleChromeLabs/squoosh.git"
|
||||
},
|
||||
"bin": {
|
||||
"squoosh-cli": "src/index.js",
|
||||
"@squoosh/cli": "src/index.js"
|
||||
@@ -14,8 +19,11 @@
|
||||
"keywords": [],
|
||||
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": " ^12.20.2 || ^14.13.1 || ^16.0.0 "
|
||||
},
|
||||
"dependencies": {
|
||||
"@squoosh/lib": "^0.2.0",
|
||||
"@squoosh/lib": "^0.3.1",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
|
||||
@@ -177,8 +177,8 @@ async function processFiles(files) {
|
||||
jobsFinished++;
|
||||
const outputPath = path.join(
|
||||
program.opts().outputDir,
|
||||
program.opts().suffix +
|
||||
path.basename(originalFile, path.extname(originalFile)),
|
||||
path.basename(originalFile, path.extname(originalFile)) +
|
||||
program.opts().suffix
|
||||
);
|
||||
for (const output of Object.values(image.encodedWith)) {
|
||||
const outputFile = `${outputPath}.${(await output).extension}`;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
import {dirname} from "path";
|
||||
globalThis.__dirname = dirname(import.meta.url);
|
||||
import { createRequire } from 'module';
|
||||
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
import visdif from './visdif.js';
|
||||
|
||||
const {VisDiff} = await visdif({
|
||||
locateFile() {
|
||||
return new URL("./visdif.wasm", import.meta.url).pathname;
|
||||
}
|
||||
});
|
||||
|
||||
const comparator = new VisDiff(
|
||||
new Uint8ClampedArray([0, 0, 0, 255]),
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
const distance = comparator.distance(new Uint8ClampedArray([1,1,1,255]));
|
||||
console.log({distance});
|
||||
@@ -1,21 +0,0 @@
|
||||
import {dirname} from "path";
|
||||
globalThis.__dirname = dirname(import.meta.url);
|
||||
import { createRequire } from 'module';
|
||||
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
import visdif from './visdif.js';
|
||||
|
||||
const {VisDiff} = await visdif({
|
||||
locateFile() {
|
||||
return new URL("./visdif.wasm", import.meta.url).pathname;
|
||||
}
|
||||
});
|
||||
|
||||
const comparator = new VisDiff(
|
||||
new Uint8ClampedArray([0, 0, 0, 255]),
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
const distance = comparator.distance(new Uint8ClampedArray([1,1,1,255]));
|
||||
console.log({distance});
|
||||
@@ -42,11 +42,19 @@ When an image has been ingested, you can start preprocessing it and encoding it
|
||||
await image.decoded; //Wait until the image is decoded before running preprocessors.
|
||||
|
||||
const preprocessOptions = {
|
||||
//When both width and height are specified, the image resized to specified size.
|
||||
resize: {
|
||||
enabled: true,
|
||||
width: 100,
|
||||
height: 50,
|
||||
}
|
||||
/*
|
||||
//When either width or height is specified, the image resized to specified size keeping aspect ratio.
|
||||
resize: {
|
||||
enabled: true,
|
||||
width: 100,
|
||||
}
|
||||
*/
|
||||
}
|
||||
await image.preprocess(preprocessOptions);
|
||||
|
||||
@@ -60,7 +68,7 @@ await image.encode(encodeOptions);
|
||||
|
||||
```
|
||||
|
||||
The default values for each option can be found in the [`codecs.js`][codecs.js] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._
|
||||
The default values for each option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._
|
||||
|
||||
You can run your own code inbetween the different steps, if, for example, you want to change how much the image should be resized based on its original height. (See [Extracting image information](#extracting-image-information) to learn how to get the image dimensions).
|
||||
|
||||
@@ -158,6 +166,6 @@ const encodeOptions: {
|
||||
```
|
||||
|
||||
[squoosh]: https://squoosh.app
|
||||
[codecs.js]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.js
|
||||
[codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts
|
||||
[butteraugli]: https://github.com/google/butteraugli
|
||||
[readfile]: https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import { ImagePool } from './build/index.js';
|
||||
|
||||
console.log("Starting");
|
||||
const imagePool = new ImagePool();
|
||||
// const imagePath = '/Users/surma/Downloads/happy_dog.png';
|
||||
const imagePath = './squoosh.png';
|
||||
console.log("INgesting");
|
||||
const image = imagePool.ingestImage(imagePath);
|
||||
console.log("Decoding");
|
||||
await image.decoded;
|
||||
const encodeOptions = {
|
||||
mozjpeg: 'auto',
|
||||
};
|
||||
console.log("Encoding");
|
||||
await image.encode(encodeOptions);
|
||||
console.log("Closing");
|
||||
await imagePool.close();
|
||||
console.log("Done");
|
||||
2086
libsquoosh/package-lock.json
generated
2086
libsquoosh/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@squoosh/lib",
|
||||
"version": "0.2.3",
|
||||
"version": "0.3.1",
|
||||
"description": "A Node library for Squoosh",
|
||||
"public": true,
|
||||
"main": "./build/index.js",
|
||||
@@ -12,7 +12,15 @@
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
||||
"homepage": "https://github.com/GoogleChromeLabs/squoosh",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoogleChromeLabs/squoosh.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": " ^12.5.0 || ^14.0.0 || ^16.0.0 "
|
||||
},
|
||||
"dependencies": {
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB |
@@ -2,6 +2,39 @@ import { instantiateEmscriptenWasm } from './emscripten-utils.js';
|
||||
|
||||
import visdif from '../../codecs/visdif/visdif.js';
|
||||
import visdifWasm from 'asset-url:../../codecs/visdif/visdif.wasm';
|
||||
import type ImageData from './image_data';
|
||||
|
||||
interface VisDiff {
|
||||
distance: (data: Uint8ClampedArray) => number;
|
||||
delete: () => void;
|
||||
}
|
||||
|
||||
interface VisdiffConstructor {
|
||||
new (data: Uint8ClampedArray, width: number, height: number): VisDiff;
|
||||
}
|
||||
|
||||
interface VisDiffModule extends EmscriptenWasm.Module {
|
||||
VisDiff: VisdiffConstructor;
|
||||
}
|
||||
|
||||
type VisDiffModuleFactory = EmscriptenWasm.ModuleFactory<VisDiffModule>;
|
||||
|
||||
interface BinarySearchParams {
|
||||
min?: number;
|
||||
max?: number;
|
||||
epsilon?: number;
|
||||
maxRounds?: number;
|
||||
}
|
||||
|
||||
interface AutoOptimizeParams extends BinarySearchParams {
|
||||
butteraugliDistanceGoal?: number;
|
||||
}
|
||||
|
||||
interface AutoOptimizeResult {
|
||||
bitmap: ImageData;
|
||||
binary: Uint8Array;
|
||||
quality: number;
|
||||
}
|
||||
|
||||
// `measure` is a (async) function that takes exactly one numeric parameter and
|
||||
// returns a value. The function is assumed to be monotonic (an increase in `parameter`
|
||||
@@ -9,9 +42,9 @@ import visdifWasm from 'asset-url:../../codecs/visdif/visdif.wasm';
|
||||
// to find `parameter` such that `measure` returns `measureGoal`, within an error
|
||||
// of `epsilon`. It will use at most `maxRounds` attempts.
|
||||
export async function binarySearch(
|
||||
measureGoal,
|
||||
measure,
|
||||
{ min = 0, max = 100, epsilon = 0.1, maxRounds = 6 } = {},
|
||||
measureGoal: number,
|
||||
measure: (val: number) => Promise<number>,
|
||||
{ min = 0, max = 100, epsilon = 0.1, maxRounds = 6 }: BinarySearchParams = {},
|
||||
) {
|
||||
let parameter = (max - min) / 2 + min;
|
||||
let delta = (max - min) / 4;
|
||||
@@ -33,12 +66,21 @@ export async function binarySearch(
|
||||
}
|
||||
|
||||
export async function autoOptimize(
|
||||
bitmapIn,
|
||||
encode,
|
||||
decode,
|
||||
{ butteraugliDistanceGoal = 1.4, ...otherOpts } = {},
|
||||
) {
|
||||
const { VisDiff } = await instantiateEmscriptenWasm(visdif, visdifWasm);
|
||||
bitmapIn: ImageData,
|
||||
encode: (
|
||||
bitmap: ImageData,
|
||||
quality: number,
|
||||
) => Promise<Uint8Array> | Uint8Array,
|
||||
decode: (binary: Uint8Array) => Promise<ImageData> | ImageData,
|
||||
{
|
||||
butteraugliDistanceGoal = 1.4,
|
||||
...binarySearchParams
|
||||
}: AutoOptimizeParams = {},
|
||||
): Promise<AutoOptimizeResult> {
|
||||
const { VisDiff } = await instantiateEmscriptenWasm(
|
||||
visdif as VisDiffModuleFactory,
|
||||
visdifWasm,
|
||||
);
|
||||
|
||||
const comparator = new VisDiff(
|
||||
bitmapIn.data,
|
||||
@@ -46,8 +88,11 @@ export async function autoOptimize(
|
||||
bitmapIn.height,
|
||||
);
|
||||
|
||||
let bitmapOut;
|
||||
let binaryOut;
|
||||
// We're able to do non null assertion because
|
||||
// we know that binarySearch will set these values
|
||||
let bitmapOut!: ImageData;
|
||||
let binaryOut!: Uint8Array;
|
||||
|
||||
// Increasing quality means _decrease_ in Butteraugli distance.
|
||||
// `binarySearch` assumes that increasing `parameter` will
|
||||
// increase the metric value. So multipliy Butteraugli values by -1.
|
||||
@@ -58,7 +103,7 @@ export async function autoOptimize(
|
||||
bitmapOut = await decode(binaryOut);
|
||||
return -1 * comparator.distance(bitmapOut.data);
|
||||
},
|
||||
otherOpts,
|
||||
binarySearchParams,
|
||||
);
|
||||
comparator.delete();
|
||||
|
||||
@@ -282,7 +282,7 @@ export const codecs = {
|
||||
webp: {
|
||||
name: 'WebP',
|
||||
extension: 'webp',
|
||||
detectors: [/^RIFF....WEBPVP8[LX ]/],
|
||||
detectors: [/^RIFF....WEBPVP8[LX ]/s],
|
||||
dec: () => instantiateEmscriptenWasm(webpDec, webpDecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(webpEnc, webpEncWasm),
|
||||
defaultEncoderOptions: {
|
||||
|
||||
@@ -6,10 +6,13 @@ import './custom-els/RangeInput';
|
||||
import { linkRef } from 'shared/prerendered-app/util';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {}
|
||||
interface State {}
|
||||
interface State {
|
||||
textFocused: boolean;
|
||||
}
|
||||
|
||||
export default class Range extends Component<Props, State> {
|
||||
rangeWc?: RangeInputElement;
|
||||
inputEl?: HTMLInputElement;
|
||||
|
||||
private onTextInput = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
@@ -23,10 +26,19 @@ export default class Range extends Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
render(props: Props) {
|
||||
private onTextFocus = () => {
|
||||
this.setState({ textFocused: true });
|
||||
};
|
||||
|
||||
private onTextBlur = () => {
|
||||
this.setState({ textFocused: false });
|
||||
};
|
||||
|
||||
render(props: Props, state: State) {
|
||||
const { children, ...otherProps } = props;
|
||||
|
||||
const { value, min, max, step } = props;
|
||||
const textValue = state.textFocused ? this.inputEl!.value : value;
|
||||
|
||||
return (
|
||||
<label class={style.range}>
|
||||
@@ -41,13 +53,16 @@ export default class Range extends Component<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref={linkRef(this, 'inputEl')}
|
||||
type="number"
|
||||
class={style.textInput}
|
||||
value={value}
|
||||
value={textValue}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
onInput={this.onTextInput}
|
||||
onFocus={this.onTextFocus}
|
||||
onBlur={this.onTextBlur}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
|
||||
@@ -110,7 +110,7 @@ export default class Results extends Component<Props, State> {
|
||||
<div class={style.percentOutput}>
|
||||
{diff && diff !== 1 && (
|
||||
<span class={style.sizeDirection}>
|
||||
{diff < 1 ? '↓' : '↑'}
|
||||
{diff < 1 ? '↓' : '+'}
|
||||
</span>
|
||||
)}
|
||||
<span class={style.sizeValue}>{percent || 0}</span>
|
||||
|
||||
@@ -172,6 +172,8 @@
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
line-height: 1;
|
||||
font-size: 2.6rem;
|
||||
gap: 0.2rem;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
background: var(--main-theme-color);
|
||||
@@ -185,19 +187,15 @@
|
||||
}
|
||||
|
||||
.size-direction {
|
||||
font-weight: 700;
|
||||
align-self: center;
|
||||
font-family: sans-serif;
|
||||
font-family: ui-monospace, monospace;
|
||||
opacity: 0.76;
|
||||
text-shadow: 0 2px rgba(0, 0, 0, 0.3);
|
||||
font-size: 1.5rem;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.size-value {
|
||||
font-family: 'Roboto Mono Numbers';
|
||||
font-size: 2.6rem;
|
||||
text-shadow: 0 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@@ -206,7 +204,7 @@
|
||||
position: relative;
|
||||
top: 4px;
|
||||
opacity: 0.76;
|
||||
margin-left: 0.2rem;
|
||||
font-size: 0.5em;
|
||||
}
|
||||
|
||||
.download {
|
||||
|
||||
@@ -95,7 +95,7 @@ const magicNumberMapInput = [
|
||||
[/^I I/, 'image/tiff'],
|
||||
[/^II*/, 'image/tiff'],
|
||||
[/^MM\x00*/, 'image/tiff'],
|
||||
[/^RIFF....WEBPVP8[LX ]/, 'image/webp'],
|
||||
[/^RIFF....WEBPVP8[LX ]/s, 'image/webp'],
|
||||
[/^\xF4\xFF\x6F/, 'image/webp2'],
|
||||
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
||||
[/^\xff\x0a/, 'image/jxl'],
|
||||
|
||||
@@ -329,10 +329,11 @@ export function startBlobAnim(canvas: HTMLCanvasElement) {
|
||||
hasFocus = false;
|
||||
};
|
||||
|
||||
new ResizeObserver(() => {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
// Redraw for new canvas size
|
||||
if (!animating) drawFrame(0);
|
||||
}).observe(canvas);
|
||||
});
|
||||
resizeObserver.observe(canvas);
|
||||
|
||||
addEventListener('focus', focusListener);
|
||||
addEventListener('blur', blurListener);
|
||||
@@ -341,6 +342,7 @@ export function startBlobAnim(canvas: HTMLCanvasElement) {
|
||||
function destruct() {
|
||||
removeEventListener('focus', focusListener);
|
||||
removeEventListener('blur', blurListener);
|
||||
resizeObserver.disconnect();
|
||||
document.removeEventListener('visibilitychange', visibilityListener);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user