mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 09:17:20 +00:00
Adding browser's webp encoder (#72)
* Adding WebP (without feature detect in place) * Adding WebP check * Remove unused import
This commit is contained in:
23
src/codecs/browser-webp/encoder.ts
Normal file
23
src/codecs/browser-webp/encoder.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { canvasEncode } from '../../lib/util';
|
||||||
|
|
||||||
|
export interface EncodeOptions { quality: number; }
|
||||||
|
export interface EncoderState { type: typeof type; options: EncodeOptions; }
|
||||||
|
|
||||||
|
export const type = 'browser-webp';
|
||||||
|
export const label = 'Browser WebP';
|
||||||
|
export const mimeType = 'image/webp';
|
||||||
|
export const extension = 'webp';
|
||||||
|
export const defaultOptions: EncodeOptions = { quality: 0.5 };
|
||||||
|
|
||||||
|
export async function featureTest() {
|
||||||
|
const data = new ImageData(1, 1);
|
||||||
|
const blob = await encode(data, defaultOptions);
|
||||||
|
// According to the spec, the blob should be null if the format isn't supported…
|
||||||
|
if (!blob) return false;
|
||||||
|
// …but Safari falls back to PNG, so we need to check the mime type.
|
||||||
|
return blob.type === mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encode(data: ImageData, { quality }: EncodeOptions) {
|
||||||
|
return canvasEncode(data, mimeType, quality);
|
||||||
|
}
|
||||||
3
src/codecs/browser-webp/options.ts
Normal file
3
src/codecs/browser-webp/options.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import qualityOption from '../generic/quality-option';
|
||||||
|
|
||||||
|
export default qualityOption({ min: 0, max: 1, step: 0 });
|
||||||
@@ -2,12 +2,18 @@ import * as mozJPEG from './mozjpeg/encoder';
|
|||||||
import * as identity from './identity/encoder';
|
import * as identity from './identity/encoder';
|
||||||
import * as browserPNG from './browser-png/encoder';
|
import * as browserPNG from './browser-png/encoder';
|
||||||
import * as browserJPEG from './browser-jpeg/encoder';
|
import * as browserJPEG from './browser-jpeg/encoder';
|
||||||
|
import * as browserWebP from './browser-webp/encoder';
|
||||||
|
|
||||||
|
export interface EncoderSupportMap {
|
||||||
|
[key: string]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type EncoderState =
|
export type EncoderState =
|
||||||
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState | browserJPEG.EncoderState;
|
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState |
|
||||||
|
browserJPEG.EncoderState | browserWebP.EncoderState;
|
||||||
export type EncoderOptions =
|
export type EncoderOptions =
|
||||||
identity.EncodeOptions | mozJPEG.EncodeOptions | browserPNG.EncodeOptions |
|
identity.EncodeOptions | mozJPEG.EncodeOptions | browserPNG.EncodeOptions |
|
||||||
browserJPEG.EncodeOptions;
|
browserJPEG.EncodeOptions | browserWebP.EncodeOptions;
|
||||||
export type EncoderType = keyof typeof encoderMap;
|
export type EncoderType = keyof typeof encoderMap;
|
||||||
|
|
||||||
export const encoderMap = {
|
export const encoderMap = {
|
||||||
@@ -15,6 +21,20 @@ export const encoderMap = {
|
|||||||
[mozJPEG.type]: mozJPEG,
|
[mozJPEG.type]: mozJPEG,
|
||||||
[browserPNG.type]: browserPNG,
|
[browserPNG.type]: browserPNG,
|
||||||
[browserJPEG.type]: browserJPEG,
|
[browserJPEG.type]: browserJPEG,
|
||||||
|
[browserWebP.type]: browserWebP,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const encoders = Array.from(Object.values(encoderMap));
|
export const encoders = Array.from(Object.values(encoderMap));
|
||||||
|
|
||||||
|
/** Does this browser support a given encoder? Indexed by label */
|
||||||
|
export const encodersSupported = Promise.resolve().then(async () => {
|
||||||
|
const encodersSupported: EncoderSupportMap = {};
|
||||||
|
|
||||||
|
await Promise.all(encoders.map(async (encoder) => {
|
||||||
|
// If the encoder provides a featureTest, call it, otherwise assume supported.
|
||||||
|
const isSupported = !('featureTest' in encoder) || await encoder.featureTest();
|
||||||
|
encodersSupported[encoder.type] = isSupported;
|
||||||
|
}));
|
||||||
|
|
||||||
|
return encodersSupported;
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ import * as mozJPEG from '../../codecs/mozjpeg/encoder';
|
|||||||
import * as identity from '../../codecs/identity/encoder';
|
import * as identity from '../../codecs/identity/encoder';
|
||||||
import * as browserPNG from '../../codecs/browser-png/encoder';
|
import * as browserPNG from '../../codecs/browser-png/encoder';
|
||||||
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
||||||
import { EncoderState, EncoderType, EncoderOptions, encoderMap } from '../../codecs/encoders';
|
import * as browserWebP from '../../codecs/browser-webp/encoder';
|
||||||
|
import {
|
||||||
|
EncoderState,
|
||||||
|
EncoderType,
|
||||||
|
EncoderOptions,
|
||||||
|
encoderMap,
|
||||||
|
} from '../../codecs/encoders';
|
||||||
|
|
||||||
interface SourceImage {
|
interface SourceImage {
|
||||||
file: File;
|
file: File;
|
||||||
@@ -54,7 +60,8 @@ async function compressImage(
|
|||||||
case mozJPEG.type: return mozJPEG.encode(source.data, encodeData.options);
|
case mozJPEG.type: return mozJPEG.encode(source.data, encodeData.options);
|
||||||
case browserPNG.type: return browserPNG.encode(source.data, encodeData.options);
|
case browserPNG.type: return browserPNG.encode(source.data, encodeData.options);
|
||||||
case browserJPEG.type: return browserJPEG.encode(source.data, encodeData.options);
|
case browserJPEG.type: return browserJPEG.encode(source.data, encodeData.options);
|
||||||
default: throw Error(`Unexpected encoder name`);
|
case browserWebP.type: return browserWebP.encode(source.data, encodeData.options);
|
||||||
|
default: throw Error(`Unexpected encoder ${JSON.stringify(encodeData)}`);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -3,18 +3,28 @@ import * as style from './style.scss';
|
|||||||
import { bind } from '../../lib/util';
|
import { bind } from '../../lib/util';
|
||||||
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
|
||||||
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
|
||||||
|
import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options';
|
||||||
|
|
||||||
import { type as mozJPEGType } from '../../codecs/mozjpeg/encoder';
|
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
|
||||||
import { type as identityType } from '../../codecs/identity/encoder';
|
import * as identity from '../../codecs/identity/encoder';
|
||||||
import { type as browserPNGType } from '../../codecs/browser-png/encoder';
|
import * as browserPNG from '../../codecs/browser-png/encoder';
|
||||||
import { type as browserJPEGType } from '../../codecs/browser-jpeg/encoder';
|
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
|
||||||
import { EncoderState, EncoderType, EncoderOptions, encoders } from '../../codecs/encoders';
|
import * as browserWebP from '../../codecs/browser-webp/encoder';
|
||||||
|
import {
|
||||||
|
EncoderState,
|
||||||
|
EncoderType,
|
||||||
|
EncoderOptions,
|
||||||
|
encoders,
|
||||||
|
encodersSupported,
|
||||||
|
EncoderSupportMap,
|
||||||
|
} from '../../codecs/encoders';
|
||||||
|
|
||||||
const encoderOptionsComponentMap = {
|
const encoderOptionsComponentMap = {
|
||||||
[mozJPEGType]: MozJpegEncoderOptions,
|
[mozJPEG.type]: MozJpegEncoderOptions,
|
||||||
[identityType]: undefined,
|
[identity.type]: undefined,
|
||||||
[browserPNGType]: undefined,
|
[browserPNG.type]: undefined,
|
||||||
[browserJPEGType]: BrowserJPEGEncoderOptions,
|
[browserJPEG.type]: BrowserJPEGEncoderOptions,
|
||||||
|
[browserWebP.type]: BrowserWebPEncoderOptions,
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -24,11 +34,18 @@ interface Props {
|
|||||||
onOptionsChange(newOptions: EncoderOptions): void;
|
onOptionsChange(newOptions: EncoderOptions): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {}
|
interface State {
|
||||||
|
encoderSupportMap?: EncoderSupportMap;
|
||||||
|
}
|
||||||
|
|
||||||
export default class Options extends Component<Props, State> {
|
export default class Options extends Component<Props, State> {
|
||||||
typeSelect?: HTMLSelectElement;
|
typeSelect?: HTMLSelectElement;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
encodersSupported.then(encoderSupportMap => this.setState({ encoderSupportMap }));
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onTypeChange(event: Event) {
|
onTypeChange(event: Event) {
|
||||||
const el = event.currentTarget as HTMLSelectElement;
|
const el = event.currentTarget as HTMLSelectElement;
|
||||||
@@ -39,18 +56,22 @@ export default class Options extends Component<Props, State> {
|
|||||||
this.props.onTypeChange(type);
|
this.props.onTypeChange(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ class: className, encoderState, onOptionsChange }: Props) {
|
render({ class: className, encoderState, onOptionsChange }: Props, { encoderSupportMap }: State) {
|
||||||
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`${style.options}${className ? (' ' + className) : ''}`}>
|
<div class={`${style.options}${className ? (' ' + className) : ''}`}>
|
||||||
<label>
|
<label>
|
||||||
Mode:
|
Mode:
|
||||||
|
{encoderSupportMap ?
|
||||||
<select value={encoderState.type} onChange={this.onTypeChange}>
|
<select value={encoderState.type} onChange={this.onTypeChange}>
|
||||||
{encoders.map(encoder => (
|
{encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => (
|
||||||
<option value={encoder.type}>{encoder.label}</option>
|
<option value={encoder.type}>{encoder.label}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
:
|
||||||
|
<select><option>Loading…</option></select>
|
||||||
|
}
|
||||||
</label>
|
</label>
|
||||||
{EncoderOptionComponent &&
|
{EncoderOptionComponent &&
|
||||||
<EncoderOptionComponent
|
<EncoderOptionComponent
|
||||||
|
|||||||
Reference in New Issue
Block a user