forked from external-repos/squoosh
@@ -6,6 +6,7 @@ import Output from '../Output';
|
|||||||
import Options from '../Options';
|
import Options from '../Options';
|
||||||
import { FileDropEvent } from './custom-els/FileDrop';
|
import { FileDropEvent } from './custom-els/FileDrop';
|
||||||
import './custom-els/FileDrop';
|
import './custom-els/FileDrop';
|
||||||
|
import ResultCache from './result-cache';
|
||||||
|
|
||||||
import * as quantizer from '../../codecs/imagequant/quantizer';
|
import * as quantizer from '../../codecs/imagequant/quantizer';
|
||||||
import * as optiPNG from '../../codecs/optipng/encoder';
|
import * as optiPNG from '../../codecs/optipng/encoder';
|
||||||
@@ -39,7 +40,7 @@ import { cleanMerge, cleanSet } from '../../lib/clean-modify';
|
|||||||
|
|
||||||
type Orientation = 'horizontal' | 'vertical';
|
type Orientation = 'horizontal' | 'vertical';
|
||||||
|
|
||||||
interface SourceImage {
|
export interface SourceImage {
|
||||||
file: File;
|
file: File;
|
||||||
bmp: ImageBitmap;
|
bmp: ImageBitmap;
|
||||||
data: ImageData;
|
data: ImageData;
|
||||||
@@ -138,7 +139,8 @@ export default class App extends Component<Props, State> {
|
|||||||
orientation: this.widthQuery.matches ? 'horizontal' : 'vertical',
|
orientation: this.widthQuery.matches ? 'horizontal' : 'vertical',
|
||||||
};
|
};
|
||||||
|
|
||||||
private snackbar?: SnackBarElement;
|
snackbar?: SnackBarElement;
|
||||||
|
readonly encodeCache = new ResultCache();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -254,20 +256,39 @@ export default class App extends Component<Props, State> {
|
|||||||
const image = images[index];
|
const image = images[index];
|
||||||
|
|
||||||
let file;
|
let file;
|
||||||
|
let preprocessed;
|
||||||
|
let bmp;
|
||||||
|
const cacheResult = this.encodeCache.match(source, image.preprocessorState, image.encoderState);
|
||||||
|
|
||||||
|
if (cacheResult) {
|
||||||
|
({ file, preprocessed, bmp } = cacheResult);
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
// Special case for identity
|
// Special case for identity
|
||||||
if (image.encoderState.type === identity.type) {
|
if (image.encoderState.type === identity.type) {
|
||||||
file = source.file;
|
({ file, bmp } = source);
|
||||||
} else {
|
} else {
|
||||||
if (!skipPreprocessing || !image.preprocessed) {
|
preprocessed = (skipPreprocessing && image.preprocessed)
|
||||||
image.preprocessed = await preprocessImage(source, image.preprocessorState);
|
? image.preprocessed
|
||||||
}
|
: await preprocessImage(source, image.preprocessorState);
|
||||||
file = await compressImage(image.preprocessed, image.encoderState, source.file.name);
|
|
||||||
|
file = await compressImage(preprocessed, image.encoderState, source.file.name);
|
||||||
|
bmp = await decodeImage(file);
|
||||||
|
|
||||||
|
this.encodeCache.add({
|
||||||
|
source,
|
||||||
|
bmp,
|
||||||
|
preprocessed,
|
||||||
|
file,
|
||||||
|
encoderState: image.encoderState,
|
||||||
|
preprocessorState: image.preprocessorState,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.showError(`Encoding error (type=${image.encoderState.type}): ${err}`);
|
this.showError(`Processing error (type=${image.encoderState.type}): ${err}`);
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const latestImage = this.state.images[index];
|
const latestImage = this.state.images[index];
|
||||||
// If a later encode has landed before this one, return.
|
// If a later encode has landed before this one, return.
|
||||||
@@ -275,17 +296,10 @@ export default class App extends Component<Props, State> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bmp;
|
images = cleanMerge(this.state.images, index, {
|
||||||
try {
|
|
||||||
bmp = await decodeImage(file);
|
|
||||||
} catch (err) {
|
|
||||||
this.setState({ error: `Encoding error (type=${image.encoderState.type}): ${err}` });
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
images = cleanMerge(this.state.images, '' + index, {
|
|
||||||
file,
|
file,
|
||||||
bmp,
|
bmp,
|
||||||
|
preprocessed,
|
||||||
downloadUrl: URL.createObjectURL(file),
|
downloadUrl: URL.createObjectURL(file),
|
||||||
loading: images[index].loadingCounter !== loadingCounter,
|
loading: images[index].loadingCounter !== loadingCounter,
|
||||||
loadedCounter: loadingCounter,
|
loadedCounter: loadingCounter,
|
||||||
|
|||||||
68
src/components/App/result-cache.ts
Normal file
68
src/components/App/result-cache.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { EncoderState } from '../../codecs/encoders';
|
||||||
|
import { shallowEqual } from '../../lib/util';
|
||||||
|
import { SourceImage } from '.';
|
||||||
|
import { PreprocessorState } from '../../codecs/preprocessors';
|
||||||
|
|
||||||
|
import * as identity from '../../codecs/identity/encoder';
|
||||||
|
|
||||||
|
interface CacheResult {
|
||||||
|
preprocessed: ImageData;
|
||||||
|
bmp: ImageBitmap;
|
||||||
|
file: File;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CacheEntry extends CacheResult {
|
||||||
|
preprocessorState: PreprocessorState;
|
||||||
|
encoderState: EncoderState;
|
||||||
|
source: SourceImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIZE = 5;
|
||||||
|
|
||||||
|
export default class ResultCache {
|
||||||
|
private readonly _entries: CacheEntry[] = [];
|
||||||
|
private _nextIndex: number = 0;
|
||||||
|
|
||||||
|
add(entry: CacheEntry) {
|
||||||
|
if (entry.encoderState.type === identity.type) throw Error('Cannot cache identity encodes');
|
||||||
|
this._entries[this._nextIndex] = entry;
|
||||||
|
this._nextIndex = (this._nextIndex + 1) % SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
match(
|
||||||
|
source: SourceImage,
|
||||||
|
preprocessorState: PreprocessorState,
|
||||||
|
encoderState: EncoderState,
|
||||||
|
): CacheResult | undefined {
|
||||||
|
const matchingEntry = this._entries.find((entry) => {
|
||||||
|
// Check for quick exits:
|
||||||
|
if (entry.source !== source) return false;
|
||||||
|
if (entry.encoderState.type !== encoderState.type) return false;
|
||||||
|
|
||||||
|
// Check that each set of options in the preprocessor are the same
|
||||||
|
for (const prop in preprocessorState) {
|
||||||
|
if (
|
||||||
|
!shallowEqual(
|
||||||
|
(preprocessorState as any)[prop],
|
||||||
|
(entry.preprocessorState as any)[prop],
|
||||||
|
)
|
||||||
|
) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check detailed encoder options
|
||||||
|
if (!shallowEqual(encoderState.options, entry.encoderState.options)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matchingEntry) {
|
||||||
|
return {
|
||||||
|
bmp: matchingEntry.bmp,
|
||||||
|
preprocessed: matchingEntry.preprocessed,
|
||||||
|
file: matchingEntry.file,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user