mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-15 01:59:57 +00:00
Adding resize preprocessor (#152)
* Adding resize preprocessor * Using ! on form * Haha oops * Using createImageBitmapPolyfill * Updating package.json * Oops again * Ooops again
This commit is contained in:
128
src/codecs/resize/options.tsx
Normal file
128
src/codecs/resize/options.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
import { h, Component } from 'preact';
|
||||
import linkState from 'linkstate';
|
||||
import { bind, inputFieldValueAsNumber } from '../../lib/util';
|
||||
import { ResizeOptions } from './resize';
|
||||
|
||||
interface Props {
|
||||
options: ResizeOptions;
|
||||
aspect: number;
|
||||
onChange(newOptions: ResizeOptions): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
maintainAspect: boolean;
|
||||
}
|
||||
|
||||
export default class ResizerOptions extends Component<Props, State> {
|
||||
state: State = {
|
||||
maintainAspect: true,
|
||||
};
|
||||
|
||||
form?: HTMLFormElement;
|
||||
|
||||
reportOptions() {
|
||||
const width = this.form!.width as HTMLInputElement;
|
||||
const height = this.form!.height as HTMLInputElement;
|
||||
|
||||
if (!width.checkValidity() || !height.checkValidity()) return;
|
||||
|
||||
const options: ResizeOptions = {
|
||||
width: inputFieldValueAsNumber(width),
|
||||
height: inputFieldValueAsNumber(height),
|
||||
method: this.form!.resizeMethod.value,
|
||||
fitMethod: this.form!.fitMethod.value,
|
||||
};
|
||||
this.props.onChange(options);
|
||||
}
|
||||
|
||||
@bind
|
||||
onChange(event: Event) {
|
||||
this.reportOptions();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props, prevState: State) {
|
||||
if (!prevState.maintainAspect && this.state.maintainAspect) {
|
||||
this.form!.height.value = Math.round(Number(this.form!.width.value) / this.props.aspect);
|
||||
this.reportOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@bind
|
||||
onWidthInput(event: Event) {
|
||||
if (!this.state.maintainAspect) return;
|
||||
|
||||
const width = inputFieldValueAsNumber(this.form!.width);
|
||||
this.form!.height.value = Math.round(width / this.props.aspect);
|
||||
}
|
||||
|
||||
@bind
|
||||
onHeightInput(event: Event) {
|
||||
if (!this.state.maintainAspect) return;
|
||||
|
||||
const height = inputFieldValueAsNumber(this.form!.height);
|
||||
this.form!.width.value = Math.round(height * this.props.aspect);
|
||||
}
|
||||
|
||||
render({ options, aspect }: Props, { maintainAspect }: State) {
|
||||
return (
|
||||
<form ref={el => this.form = el}>
|
||||
<label>
|
||||
Method:
|
||||
<select
|
||||
name="resizeMethod"
|
||||
value={options.method}
|
||||
onChange={this.onChange}
|
||||
>
|
||||
<option value="browser-pixelated">Browser pixelated</option>
|
||||
<option value="browser-low">Browser low quality</option>
|
||||
<option value="browser-medium">Browser medium quality</option>
|
||||
<option value="browser-high">Browser high quality</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Width:
|
||||
<input
|
||||
required
|
||||
name="width"
|
||||
type="number"
|
||||
min="1"
|
||||
value={'' + options.width}
|
||||
onChange={this.onChange}
|
||||
onInput={this.onWidthInput}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Height:
|
||||
<input
|
||||
required
|
||||
name="height"
|
||||
type="number"
|
||||
min="1"
|
||||
value={'' + options.height}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
name="maintainAspect"
|
||||
type="checkbox"
|
||||
checked={maintainAspect}
|
||||
onChange={linkState(this, 'maintainAspect')}
|
||||
/>
|
||||
Maintain aspect ratio
|
||||
</label>
|
||||
<label style={{ display: maintainAspect ? 'none' : '' }}>
|
||||
Fit method:
|
||||
<select
|
||||
name="fitMethod"
|
||||
value={options.fitMethod}
|
||||
onChange={this.onChange}
|
||||
>
|
||||
<option value="stretch">Stretch</option>
|
||||
<option value="cover">Cover</option>
|
||||
</select>
|
||||
</label>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
46
src/codecs/resize/resize.ts
Normal file
46
src/codecs/resize/resize.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { bitmapToImageData, createImageBitmapPolyfill } from '../../lib/util';
|
||||
|
||||
type CreateImageBitmapResize = 'pixelated' | 'low' | 'medium' | 'high';
|
||||
|
||||
export async function resize(data: ImageData, opts: ResizeOptions): Promise<ImageData> {
|
||||
let sx = 0;
|
||||
let sy = 0;
|
||||
let sw = data.width;
|
||||
let sh = data.height;
|
||||
|
||||
if (opts.fitMethod === 'cover') {
|
||||
const currentAspect = data.width / data.height;
|
||||
const endAspect = opts.width / opts.height;
|
||||
if (endAspect > currentAspect) {
|
||||
sh = opts.height / (opts.width / data.width);
|
||||
sy = (data.height - sh) / 2;
|
||||
} else {
|
||||
sw = opts.width / (opts.height / data.height);
|
||||
sx = (data.width - sw) / 2;
|
||||
}
|
||||
}
|
||||
|
||||
const bmp = await createImageBitmapPolyfill(data, sx, sy, sw, sh, {
|
||||
resizeQuality: opts.method.slice('browser-'.length) as CreateImageBitmapResize,
|
||||
resizeWidth: opts.width,
|
||||
resizeHeight: opts.height,
|
||||
});
|
||||
|
||||
return bitmapToImageData(bmp);
|
||||
}
|
||||
|
||||
export interface ResizeOptions {
|
||||
width: number;
|
||||
height: number;
|
||||
method: 'browser-pixelated' | 'browser-low' | 'browser-medium' | 'browser-high';
|
||||
fitMethod: 'stretch' | 'cover';
|
||||
}
|
||||
|
||||
export const defaultOptions: ResizeOptions = {
|
||||
// Width and height will always default to the image size.
|
||||
// This is set elsewhere.
|
||||
width: 1,
|
||||
height: 1,
|
||||
method: 'browser-high',
|
||||
fitMethod: 'stretch',
|
||||
};
|
||||
Reference in New Issue
Block a user