Adding result cache. Fixes #87. (#148)

This commit is contained in:
Jake Archibald
2018-09-05 08:29:17 +01:00
committed by GitHub
parent 32f6f8b941
commit 485ba174e3
2 changed files with 104 additions and 22 deletions

View File

@@ -6,6 +6,7 @@ import Output from '../Output';
import Options from '../Options';
import { FileDropEvent } from './custom-els/FileDrop';
import './custom-els/FileDrop';
import ResultCache from './result-cache';
import * as quantizer from '../../codecs/imagequant/quantizer';
import * as optiPNG from '../../codecs/optipng/encoder';
@@ -39,7 +40,7 @@ import { cleanMerge, cleanSet } from '../../lib/clean-modify';
type Orientation = 'horizontal' | 'vertical';
interface SourceImage {
export interface SourceImage {
file: File;
bmp: ImageBitmap;
data: ImageData;
@@ -138,7 +139,8 @@ export default class App extends Component<Props, State> {
orientation: this.widthQuery.matches ? 'horizontal' : 'vertical',
};
private snackbar?: SnackBarElement;
snackbar?: SnackBarElement;
readonly encodeCache = new ResultCache();
constructor() {
super();
@@ -254,19 +256,38 @@ export default class App extends Component<Props, State> {
const image = images[index];
let file;
try {
// Special case for identity
if (image.encoderState.type === identity.type) {
file = source.file;
} else {
if (!skipPreprocessing || !image.preprocessed) {
image.preprocessed = await preprocessImage(source, image.preprocessorState);
let preprocessed;
let bmp;
const cacheResult = this.encodeCache.match(source, image.preprocessorState, image.encoderState);
if (cacheResult) {
({ file, preprocessed, bmp } = cacheResult);
} else {
try {
// Special case for identity
if (image.encoderState.type === identity.type) {
({ file, bmp } = source);
} else {
preprocessed = (skipPreprocessing && image.preprocessed)
? image.preprocessed
: await preprocessImage(source, image.preprocessorState);
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,
});
}
file = await compressImage(image.preprocessed, image.encoderState, source.file.name);
} catch (err) {
this.showError(`Processing error (type=${image.encoderState.type}): ${err}`);
throw err;
}
} catch (err) {
this.showError(`Encoding error (type=${image.encoderState.type}): ${err}`);
throw err;
}
const latestImage = this.state.images[index];
@@ -275,17 +296,10 @@ export default class App extends Component<Props, State> {
return;
}
let bmp;
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, {
images = cleanMerge(this.state.images, index, {
file,
bmp,
preprocessed,
downloadUrl: URL.createObjectURL(file),
loading: images[index].loadingCounter !== loadingCounter,
loadedCounter: loadingCounter,

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