mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 01:07:18 +00:00
Refactor resize
This commit is contained in:
@@ -145,21 +145,24 @@ export default function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function generateFeatureMeta() {
|
async function generateFeatureMeta() {
|
||||||
const encoderMetas = (
|
const getTsFiles = (glob) =>
|
||||||
await globP('src/features/encoders/*/shared/meta.ts', {
|
globP(glob, {
|
||||||
absolute: true,
|
absolute: true,
|
||||||
})
|
}).then((paths) =>
|
||||||
)
|
paths
|
||||||
.filter((tsFile) => !tsFile.endsWith('.d.ts'))
|
.filter((tsFile) => !tsFile.endsWith('.d.ts'))
|
||||||
.map((tsFile) => tsFile.slice(0, -'.ts'.length));
|
.map((tsFile) => tsFile.slice(0, -'.ts'.length)),
|
||||||
|
);
|
||||||
|
|
||||||
const processorMetas = (
|
const metas = await Promise.all(
|
||||||
await globP('src/features/processors/*/shared/meta.ts', {
|
[
|
||||||
absolute: true,
|
'src/features/encoders/*/shared/meta.ts',
|
||||||
})
|
'src/features/processors/*/shared/meta.ts',
|
||||||
)
|
'src/features/preprocessors/*/shared/meta.ts',
|
||||||
.filter((tsFile) => !tsFile.endsWith('.d.ts'))
|
].map((glob) => getTsFiles(glob)),
|
||||||
.map((tsFile) => tsFile.slice(0, -'.ts'.length));
|
);
|
||||||
|
|
||||||
|
const [encoderMetas, processorMetas, preprocessorMetas] = metas;
|
||||||
|
|
||||||
const featureMetaBasePath = path.join(
|
const featureMetaBasePath = path.join(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@@ -169,7 +172,7 @@ export default function () {
|
|||||||
'feature-meta',
|
'feature-meta',
|
||||||
);
|
);
|
||||||
|
|
||||||
const joinedMetas = [...encoderMetas, ...processorMetas].join();
|
const joinedMetas = metas.flat().join();
|
||||||
|
|
||||||
// Avoid regenerating if nothing's changed.
|
// Avoid regenerating if nothing's changed.
|
||||||
// This also prevents an infinite loop in the watcher.
|
// This also prevents an infinite loop in the watcher.
|
||||||
@@ -181,8 +184,15 @@ export default function () {
|
|||||||
path.basename(tsImport.slice(0, -'/shared/meta'.length)),
|
path.basename(tsImport.slice(0, -'/shared/meta'.length)),
|
||||||
];
|
];
|
||||||
|
|
||||||
const encoderMetaTsNames = encoderMetas.map(getTsName);
|
const encoderMetaTsNames = encoderMetas.map((tsImport) =>
|
||||||
const processorMetaTsNames = processorMetas.map(getTsName);
|
getTsName(tsImport),
|
||||||
|
);
|
||||||
|
const processorMetaTsNames = processorMetas.map((tsImport) =>
|
||||||
|
getTsName(tsImport),
|
||||||
|
);
|
||||||
|
const preprocessorMetaTsNames = preprocessorMetas.map((tsImport) =>
|
||||||
|
getTsName(tsImport),
|
||||||
|
);
|
||||||
|
|
||||||
const featureMeta = [
|
const featureMeta = [
|
||||||
autoGenComment,
|
autoGenComment,
|
||||||
@@ -222,6 +232,20 @@ export default function () {
|
|||||||
` ${name}: { enabled: false, ...${name}ProcessorMeta.defaultOptions },`,
|
` ${name}: { enabled: false, ...${name}ProcessorMeta.defaultOptions },`,
|
||||||
),
|
),
|
||||||
`}`,
|
`}`,
|
||||||
|
// Preprocessor stuff
|
||||||
|
preprocessorMetaTsNames.map(
|
||||||
|
([path, name]) => `import * as ${name}PreprocessorMeta from '${path}';`,
|
||||||
|
),
|
||||||
|
`export interface PreprocessorState {`,
|
||||||
|
preprocessorMetaTsNames.map(
|
||||||
|
([_, name]) => ` ${name}: ${name}PreprocessorMeta.Options,`,
|
||||||
|
),
|
||||||
|
`}`,
|
||||||
|
`export const defaultPreprocessorState: PreprocessorState = {`,
|
||||||
|
preprocessorMetaTsNames.map(
|
||||||
|
([_, name]) => ` ${name}: ${name}PreprocessorMeta.defaultOptions,`,
|
||||||
|
),
|
||||||
|
`};`,
|
||||||
]
|
]
|
||||||
.flat(Infinity)
|
.flat(Infinity)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { h, Component } from 'preact';
|
import { h, Component } from 'preact';
|
||||||
|
|
||||||
|
import * as style from './style.css';
|
||||||
|
import 'add-css:./style.css';
|
||||||
import {
|
import {
|
||||||
blobToImg,
|
blobToImg,
|
||||||
drawableToImageData,
|
drawableToImageData,
|
||||||
@@ -7,41 +9,35 @@ import {
|
|||||||
builtinDecode,
|
builtinDecode,
|
||||||
sniffMimeType,
|
sniffMimeType,
|
||||||
canDecodeImageType,
|
canDecodeImageType,
|
||||||
|
abortable,
|
||||||
|
assertSignal,
|
||||||
} from '../util';
|
} from '../util';
|
||||||
import * as style from './style.css';
|
import {
|
||||||
import 'add-css:./style.css';
|
PreprocessorState,
|
||||||
|
ProcessorState,
|
||||||
|
EncoderState,
|
||||||
|
} from '../feature-meta';
|
||||||
import Output from '../Output';
|
import Output from '../Output';
|
||||||
import Options from '../Options';
|
import Options from '../Options';
|
||||||
import ResultCache from './result-cache';
|
import ResultCache from './result-cache';
|
||||||
import { decodeImage } from '../../codecs/decoders';
|
|
||||||
import { cleanMerge, cleanSet } from '../../lib/clean-modify';
|
import { cleanMerge, cleanSet } from '../../lib/clean-modify';
|
||||||
import Processor from '../../codecs/processor';
|
|
||||||
import {
|
|
||||||
BrowserResizeOptions,
|
|
||||||
isWorkerOptions as isWorkerResizeOptions,
|
|
||||||
isHqx,
|
|
||||||
WorkerResizeOptions,
|
|
||||||
} from '../../codecs/resize/processor-meta';
|
|
||||||
import './custom-els/MultiPanel';
|
import './custom-els/MultiPanel';
|
||||||
import Results from '../results';
|
import Results from '../results';
|
||||||
import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons';
|
import { ExpandIcon, CopyAcrossIconProps } from '../../lib/icons';
|
||||||
import SnackBarElement from '../../lib/SnackBar';
|
import SnackBarElement from '../../lib/SnackBar';
|
||||||
import {
|
|
||||||
InputProcessorState,
|
|
||||||
defaultInputProcessorState,
|
|
||||||
} from '../../codecs/input-processors';
|
|
||||||
import WorkerBridge from '../worker-bridge';
|
import WorkerBridge from '../worker-bridge';
|
||||||
|
import { resize } from 'features/processors/resize/client';
|
||||||
|
|
||||||
export interface SourceImage {
|
export interface SourceImage {
|
||||||
file: File;
|
file: File;
|
||||||
decoded: ImageData;
|
decoded: ImageData;
|
||||||
processed: ImageData;
|
processed: ImageData;
|
||||||
vectorImage?: HTMLImageElement;
|
vectorImage?: HTMLImageElement;
|
||||||
inputProcessorState: InputProcessorState;
|
preprocessorState: PreprocessorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SideSettings {
|
interface SideSettings {
|
||||||
preprocessorState: PreprocessorState;
|
processorState: ProcessorState;
|
||||||
encoderState: EncoderState;
|
encoderState: EncoderState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,8 +75,9 @@ async function decodeImage(
|
|||||||
blob: Blob,
|
blob: Blob,
|
||||||
workerBridge: WorkerBridge,
|
workerBridge: WorkerBridge,
|
||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
const mimeType = await sniffMimeType(blob);
|
assertSignal(signal);
|
||||||
const canDecode = await canDecodeImageType(mimeType);
|
const mimeType = await abortable(signal, sniffMimeType(blob));
|
||||||
|
const canDecode = await abortable(signal, canDecodeImageType(mimeType));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!canDecode) {
|
if (!canDecode) {
|
||||||
@@ -92,63 +89,52 @@ async function decodeImage(
|
|||||||
}
|
}
|
||||||
// If it's not one of those types, fall through and try built-in decoding for a laugh.
|
// If it's not one of those types, fall through and try built-in decoding for a laugh.
|
||||||
}
|
}
|
||||||
return await builtinDecode(blob);
|
return await abortable(signal, builtinDecode(blob));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.name === 'AbortError') throw err;
|
||||||
|
console.log(err);
|
||||||
throw Error("Couldn't decode image");
|
throw Error("Couldn't decode image");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processInput(
|
async function preprocessImage(
|
||||||
|
signal: AbortSignal,
|
||||||
data: ImageData,
|
data: ImageData,
|
||||||
inputProcessData: InputProcessorState,
|
preprocessorState: PreprocessorState,
|
||||||
processor: Processor,
|
workerBridge: WorkerBridge,
|
||||||
) {
|
): Promise<ImageData> {
|
||||||
|
assertSignal(signal);
|
||||||
let processedData = data;
|
let processedData = data;
|
||||||
|
|
||||||
if (inputProcessData.rotate.rotate !== 0) {
|
if (preprocessorState.rotate.rotate !== 0) {
|
||||||
processedData = await processor.rotate(
|
processedData = await workerBridge.rotate(
|
||||||
|
signal,
|
||||||
processedData,
|
processedData,
|
||||||
inputProcessData.rotate,
|
preprocessorState.rotate,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return processedData;
|
return processedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function preprocessImage(
|
async function processImage(
|
||||||
|
signal: AbortSignal,
|
||||||
source: SourceImage,
|
source: SourceImage,
|
||||||
preprocessData: PreprocessorState,
|
processorState: ProcessorState,
|
||||||
processor: Processor,
|
workerBridge: WorkerBridge,
|
||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
|
assertSignal(signal);
|
||||||
let result = source.processed;
|
let result = source.processed;
|
||||||
|
|
||||||
if (preprocessData.resize.enabled) {
|
if (processorState.resize.enabled) {
|
||||||
if (preprocessData.resize.method === 'vector' && source.vectorImage) {
|
result = await resize(signal, source, processorState.resize, workerBridge);
|
||||||
result = processor.vectorResize(
|
|
||||||
source.vectorImage,
|
|
||||||
preprocessData.resize,
|
|
||||||
);
|
|
||||||
} else if (isHqx(preprocessData.resize)) {
|
|
||||||
// Hqx can only do x2, x3 or x4.
|
|
||||||
result = await processor.workerResize(result, preprocessData.resize);
|
|
||||||
// If the target size is not a clean x2, x3 or x4, use Catmull-Rom
|
|
||||||
// for the remaining scaling.
|
|
||||||
const pixelOpts = { ...preprocessData.resize, method: 'catrom' };
|
|
||||||
result = await processor.workerResize(
|
|
||||||
result,
|
|
||||||
pixelOpts as WorkerResizeOptions,
|
|
||||||
);
|
|
||||||
} else if (isWorkerResizeOptions(preprocessData.resize)) {
|
|
||||||
result = await processor.workerResize(result, preprocessData.resize);
|
|
||||||
} else {
|
|
||||||
result = processor.resize(
|
|
||||||
result,
|
|
||||||
preprocessData.resize as BrowserResizeOptions,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
if (processorState.quantizer.enabled) {
|
||||||
if (preprocessData.quantizer.enabled) {
|
result = await workerBridge.imageQuant(
|
||||||
result = await processor.imageQuant(result, preprocessData.quantizer);
|
signal,
|
||||||
|
result,
|
||||||
|
processorState.quantizer,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -264,7 +250,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
sides: [
|
sides: [
|
||||||
{
|
{
|
||||||
latestSettings: {
|
latestSettings: {
|
||||||
preprocessorState: defaultPreprocessorState,
|
processorState: defaultPreprocessorState,
|
||||||
encoderState: {
|
encoderState: {
|
||||||
type: identity.type,
|
type: identity.type,
|
||||||
options: identity.defaultOptions,
|
options: identity.defaultOptions,
|
||||||
@@ -276,7 +262,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
latestSettings: {
|
latestSettings: {
|
||||||
preprocessorState: defaultPreprocessorState,
|
processorState: defaultPreprocessorState,
|
||||||
encoderState: { type: mozJPEG.type, options: mozJPEG.defaultOptions },
|
encoderState: { type: mozJPEG.type, options: mozJPEG.defaultOptions },
|
||||||
},
|
},
|
||||||
loadingCounter: 0,
|
loadingCounter: 0,
|
||||||
@@ -376,8 +362,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const encoderChanged =
|
const encoderChanged =
|
||||||
side.latestSettings.encoderState !== prevSettings.encoderState;
|
side.latestSettings.encoderState !== prevSettings.encoderState;
|
||||||
const preprocessorChanged =
|
const preprocessorChanged =
|
||||||
side.latestSettings.preprocessorState !==
|
side.latestSettings.processorState !== prevSettings.processorState;
|
||||||
prevSettings.preprocessorState;
|
|
||||||
|
|
||||||
// The image only needs updated if the encoder/preprocessor settings have changed, or the
|
// The image only needs updated if the encoder/preprocessor settings have changed, or the
|
||||||
// source has changed.
|
// source has changed.
|
||||||
@@ -421,7 +406,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const source = this.state.source;
|
const source = this.state.source;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
const oldRotate = source.inputProcessorState.rotate.rotate;
|
const oldRotate = source.preprocessorState.rotate.rotate;
|
||||||
const newRotate = options.rotate.rotate;
|
const newRotate = options.rotate.rotate;
|
||||||
const orientationChanged = oldRotate % 180 !== newRotate % 180;
|
const orientationChanged = oldRotate % 180 !== newRotate % 180;
|
||||||
const loadingCounter = this.state.loadingCounter + 1;
|
const loadingCounter = this.state.loadingCounter + 1;
|
||||||
@@ -439,7 +424,11 @@ export default class Compress extends Component<Props, State> {
|
|||||||
this.rightProcessor.abortCurrent();
|
this.rightProcessor.abortCurrent();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const processed = await processInput(source.decoded, options, processor);
|
const processed = await preprocessImage(
|
||||||
|
source.decoded,
|
||||||
|
options,
|
||||||
|
processor,
|
||||||
|
);
|
||||||
|
|
||||||
// Another file has been opened/processed before this one processed.
|
// Another file has been opened/processed before this one processed.
|
||||||
if (this.state.loadingCounter !== loadingCounter) return;
|
if (this.state.loadingCounter !== loadingCounter) return;
|
||||||
@@ -452,7 +441,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
// If orientation has changed, we should flip the resize values.
|
// If orientation has changed, we should flip the resize values.
|
||||||
for (const i of [0, 1]) {
|
for (const i of [0, 1]) {
|
||||||
const resizeSettings =
|
const resizeSettings =
|
||||||
newState.sides[i].latestSettings.preprocessorState.resize;
|
newState.sides[i].latestSettings.processorState.resize;
|
||||||
newState = cleanMerge(
|
newState = cleanMerge(
|
||||||
newState,
|
newState,
|
||||||
`sides.${i}.latestSettings.preprocessorState.resize`,
|
`sides.${i}.latestSettings.preprocessorState.resize`,
|
||||||
@@ -500,7 +489,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
decoded = await decodeImage(file, processor);
|
decoded = await decodeImage(file, processor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const processed = await processInput(
|
const processed = await preprocessImage(
|
||||||
decoded,
|
decoded,
|
||||||
defaultInputProcessorState,
|
defaultInputProcessorState,
|
||||||
processor,
|
processor,
|
||||||
@@ -516,7 +505,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
file,
|
file,
|
||||||
vectorImage,
|
vectorImage,
|
||||||
processed,
|
processed,
|
||||||
inputProcessorState: defaultInputProcessorState,
|
preprocessorState: defaultInputProcessorState,
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
};
|
};
|
||||||
@@ -617,7 +606,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
preprocessed =
|
preprocessed =
|
||||||
skipPreprocessing && side.preprocessed
|
skipPreprocessing && side.preprocessed
|
||||||
? side.preprocessed
|
? side.preprocessed
|
||||||
: await preprocessImage(
|
: await processImage(
|
||||||
source,
|
source,
|
||||||
settings.preprocessorState,
|
settings.preprocessorState,
|
||||||
processor,
|
processor,
|
||||||
@@ -679,7 +668,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
<Options
|
<Options
|
||||||
source={source}
|
source={source}
|
||||||
mobileView={mobileView}
|
mobileView={mobileView}
|
||||||
preprocessorState={side.latestSettings.preprocessorState}
|
preprocessorState={side.latestSettings.processorState}
|
||||||
encoderState={side.latestSettings.encoderState}
|
encoderState={side.latestSettings.encoderState}
|
||||||
onEncoderTypeChange={this.onEncoderTypeChange.bind(
|
onEncoderTypeChange={this.onEncoderTypeChange.bind(
|
||||||
this,
|
this,
|
||||||
@@ -729,11 +718,11 @@ export default class Compress extends Component<Props, State> {
|
|||||||
const rightDisplaySettings =
|
const rightDisplaySettings =
|
||||||
rightSide.encodedSettings || rightSide.latestSettings;
|
rightSide.encodedSettings || rightSide.latestSettings;
|
||||||
const leftImgContain =
|
const leftImgContain =
|
||||||
leftDisplaySettings.preprocessorState.resize.enabled &&
|
leftDisplaySettings.processorState.resize.enabled &&
|
||||||
leftDisplaySettings.preprocessorState.resize.fitMethod === 'contain';
|
leftDisplaySettings.processorState.resize.fitMethod === 'contain';
|
||||||
const rightImgContain =
|
const rightImgContain =
|
||||||
rightDisplaySettings.preprocessorState.resize.enabled &&
|
rightDisplaySettings.processorState.resize.enabled &&
|
||||||
rightDisplaySettings.preprocessorState.resize.fitMethod === 'contain';
|
rightDisplaySettings.processorState.resize.fitMethod === 'contain';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={style.compress}>
|
<div class={style.compress}>
|
||||||
@@ -745,7 +734,7 @@ export default class Compress extends Component<Props, State> {
|
|||||||
leftImgContain={leftImgContain}
|
leftImgContain={leftImgContain}
|
||||||
rightImgContain={rightImgContain}
|
rightImgContain={rightImgContain}
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
inputProcessorState={source && source.inputProcessorState}
|
inputProcessorState={source && source.preprocessorState}
|
||||||
onInputProcessorChange={this.onInputProcessorChange}
|
onInputProcessorChange={this.onInputProcessorChange}
|
||||||
/>
|
/>
|
||||||
{mobileView ? (
|
{mobileView ? (
|
||||||
|
|||||||
@@ -367,6 +367,13 @@ export function preventDefault(event: Event) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw an abort error if a signal is aborted.
|
||||||
|
*/
|
||||||
|
export function assertSignal(signal: AbortSignal) {
|
||||||
|
if (signal.aborted) throw new DOMException('AbortError', 'AbortError');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Take a signal and promise, and returns a promise that rejects with an AbortError if the abort is
|
* Take a signal and promise, and returns a promise that rejects with an AbortError if the abort is
|
||||||
* signalled, otherwise resolves with the promise.
|
* signalled, otherwise resolves with the promise.
|
||||||
@@ -375,7 +382,7 @@ export async function abortable<T>(
|
|||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
promise: Promise<T>,
|
promise: Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
if (signal.aborted) throw new DOMException('AbortError', 'AbortError');
|
assertSignal(signal);
|
||||||
return Promise.race([
|
return Promise.race([
|
||||||
promise,
|
promise,
|
||||||
new Promise<T>((_, reject) => {
|
new Promise<T>((_, reject) => {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
{ "path": "../features/encoders/identity/shared" },
|
{ "path": "../features/encoders/identity/shared" },
|
||||||
{ "path": "../features/encoders/browserGIF/shared" },
|
{ "path": "../features/encoders/browserGIF/shared" },
|
||||||
{ "path": "../features/encoders/browserJPEG/shared" },
|
{ "path": "../features/encoders/browserJPEG/shared" },
|
||||||
{ "path": "../features/encoders/browserPNG/shared" }
|
{ "path": "../features/encoders/browserPNG/shared" },
|
||||||
|
{ "path": "../features/processors/resize/client" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/features/preprocessors/rotate/shared/meta.ts
Normal file
19
src/features/preprocessors/rotate/shared/meta.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
export interface Options {
|
||||||
|
rotate: 0 | 90 | 180 | 270;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const defaultOptions: Options = {
|
||||||
|
rotate: 0,
|
||||||
|
};
|
||||||
13
src/features/preprocessors/rotate/shared/missing-types.d.ts
vendored
Normal file
13
src/features/preprocessors/rotate/shared/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/// <reference path="../../../../../missing-types.d.ts" />
|
||||||
7
src/features/preprocessors/rotate/shared/tsconfig.json
Normal file
7
src/features/preprocessors/rotate/shared/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../../../../generic-tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["webworker", "esnext"]
|
||||||
|
},
|
||||||
|
"references": [{ "path": "../../../" }]
|
||||||
|
}
|
||||||
@@ -11,10 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import wasmUrl from 'url:codecs/rotate/rotate.wasm';
|
import wasmUrl from 'url:codecs/rotate/rotate.wasm';
|
||||||
|
import { Options } from '../shared/meta';
|
||||||
export interface RotateOptions {
|
|
||||||
rotate: 0 | 90 | 180 | 270;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RotateModuleInstance {
|
export interface RotateModuleInstance {
|
||||||
exports: {
|
exports: {
|
||||||
@@ -33,7 +30,7 @@ const instancePromise = fetch(wasmUrl)
|
|||||||
|
|
||||||
export default async function rotate(
|
export default async function rotate(
|
||||||
data: ImageData,
|
data: ImageData,
|
||||||
opts: RotateOptions,
|
opts: Options,
|
||||||
): Promise<ImageData> {
|
): Promise<ImageData> {
|
||||||
const instance = (await instancePromise).instance as RotateModuleInstance;
|
const instance = (await instancePromise).instance as RotateModuleInstance;
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["webworker", "esnext"]
|
"lib": ["webworker", "esnext"]
|
||||||
},
|
},
|
||||||
"references": [{ "path": "../../../" }]
|
"references": [{ "path": "../../../" }, { "path": "../shared" }]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,27 @@ import {
|
|||||||
BuiltinResizeMethod,
|
BuiltinResizeMethod,
|
||||||
drawableToImageData,
|
drawableToImageData,
|
||||||
} from 'client/lazy-app/util';
|
} from 'client/lazy-app/util';
|
||||||
import { BrowserResizeOptions, VectorResizeOptions } from '../shared/meta';
|
import {
|
||||||
|
BrowserResizeOptions,
|
||||||
|
VectorResizeOptions,
|
||||||
|
WorkerResizeOptions,
|
||||||
|
Options,
|
||||||
|
workerResizeMethods,
|
||||||
|
} from '../shared/meta';
|
||||||
import { getContainOffsets } from '../shared/util';
|
import { getContainOffsets } from '../shared/util';
|
||||||
|
import type { SourceImage } from 'client/lazy-app/Compress';
|
||||||
|
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
|
|
||||||
export function browserResize(
|
/**
|
||||||
data: ImageData,
|
* Return whether a set of options are worker resize options.
|
||||||
opts: BrowserResizeOptions,
|
*
|
||||||
): ImageData {
|
* @param opts
|
||||||
|
*/
|
||||||
|
function isWorkerOptions(opts: Options): opts is WorkerResizeOptions {
|
||||||
|
return (workerResizeMethods as string[]).includes(opts.method);
|
||||||
|
}
|
||||||
|
|
||||||
|
function browserResize(data: ImageData, opts: BrowserResizeOptions): ImageData {
|
||||||
let sx = 0;
|
let sx = 0;
|
||||||
let sy = 0;
|
let sy = 0;
|
||||||
let sw = data.width;
|
let sw = data.width;
|
||||||
@@ -31,7 +45,7 @@ export function browserResize(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function vectorResize(
|
function vectorResize(
|
||||||
data: HTMLImageElement,
|
data: HTMLImageElement,
|
||||||
opts: VectorResizeOptions,
|
opts: VectorResizeOptions,
|
||||||
): ImageData {
|
): ImageData {
|
||||||
@@ -53,3 +67,19 @@ export function vectorResize(
|
|||||||
height: opts.height,
|
height: opts.height,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resize(
|
||||||
|
signal: AbortSignal,
|
||||||
|
source: SourceImage,
|
||||||
|
options: Options,
|
||||||
|
workerBridge: WorkerBridge,
|
||||||
|
) {
|
||||||
|
if (options.method === 'vector') {
|
||||||
|
if (!source.vectorImage) throw Error('No vector image available');
|
||||||
|
return vectorResize(source.vectorImage, options);
|
||||||
|
}
|
||||||
|
if (isWorkerOptions(options)) {
|
||||||
|
return workerBridge.resize(signal, source.processed, options);
|
||||||
|
}
|
||||||
|
return browserResize(source.processed, options);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"./*.ts",
|
"./*.ts",
|
||||||
"../../../../client/lazy-app/util.ts",
|
"../../../../client/lazy-app/util.ts",
|
||||||
|
"../../../../client/lazy-app/Compress/index.tsx",
|
||||||
"../shared/*.ts"
|
"../shared/*.ts"
|
||||||
],
|
],
|
||||||
"references": [{ "path": "../shared" }]
|
"references": [{ "path": "../shared" }]
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ type WorkerResizeMethods =
|
|||||||
| 'lanczos3'
|
| 'lanczos3'
|
||||||
| 'hqx';
|
| 'hqx';
|
||||||
|
|
||||||
|
export const workerResizeMethods: WorkerResizeMethods[] = [
|
||||||
|
'triangle',
|
||||||
|
'catrom',
|
||||||
|
'mitchell',
|
||||||
|
'lanczos3',
|
||||||
|
'hqx',
|
||||||
|
];
|
||||||
|
|
||||||
export type Options =
|
export type Options =
|
||||||
| BrowserResizeOptions
|
| BrowserResizeOptions
|
||||||
| WorkerResizeOptions
|
| WorkerResizeOptions
|
||||||
|
|||||||
Reference in New Issue
Block a user