Merge remote-tracking branch 'origin/dev' into avif-node-mt

This commit is contained in:
Surma
2021-07-20 11:51:10 +01:00
5 changed files with 109 additions and 35 deletions

View File

@@ -1,36 +1,42 @@
# [Squoosh]!
[Squoosh] is an image compression web app that allows you to dive into the advanced options provided
by various image compressors.
[Squoosh] is an image compression web app that reduces image sizes through numerous formats.
# API & CLI
Squoosh now has [an API](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh) and [a CLI](https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli) that allows you to compress many images at once.
Squoosh has [an API](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh) and [a CLI](https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli) to compress many images at once.
# Privacy
Google Analytics is used to record the following:
Squoosh does not send your image to a server. All image compression processes locally.
- [Basic visit data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
- Before and after image size once an image is downloaded. These values are rounded to the nearest
kilobyte.
- If install is available, when Squoosh is installed, and what method was used to install Squoosh.
However, Squoosh utilizes Google Analytics to collect the following:
Image compression is handled locally; no additional data is sent to the server.
- [Basic visitor data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
- The before and after image size value.
- If Squoosh PWA, the type of Squoosh installation.
- If Squoosh PWA, the installation time and date.
# Building locally
# Developing
Clone the repo, and:
To develop for Squoosh:
```sh
npm install
npm run build
```
1. Clone the repository
1. To install node packages, run:
```sh
npm install
```
1. Then build the app by running:
```sh
npm run build
```
1. After building, start the development server by running:
```sh
npm run dev
```
You can run the development server with:
# Contributing
```sh
npm run dev
```
Squoosh is an open-source project that appreciates all community involvement. To contribute to the project, follow the [contribute guide](/CONTRIBUTING.md).
[squoosh]: https://squoosh.app

View File

@@ -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);

View File

@@ -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();

View File

@@ -64,7 +64,7 @@ export default class WorkerPool {
async _nextWorker() {
const reader = this.workerQueue.readable.getReader();
const { value, done } = await reader.read();
const { value } = await reader.read();
reader.releaseLock();
return value;
}

View File

@@ -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>
);