Adding browser's webp encoder (#72)

* Adding WebP (without feature detect in place)

* Adding WebP check

* Remove unused import
This commit is contained in:
Jake Archibald
2018-07-02 15:15:18 +01:00
parent a09ec269b8
commit 68729979e3
5 changed files with 94 additions and 20 deletions

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

View File

@@ -0,0 +1,3 @@
import qualityOption from '../generic/quality-option';
export default qualityOption({ min: 0, max: 1, step: 0 });

View File

@@ -2,12 +2,18 @@ import * as mozJPEG from './mozjpeg/encoder';
import * as identity from './identity/encoder';
import * as browserPNG from './browser-png/encoder';
import * as browserJPEG from './browser-jpeg/encoder';
import * as browserWebP from './browser-webp/encoder';
export interface EncoderSupportMap {
[key: string]: boolean;
}
export type EncoderState =
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState | browserJPEG.EncoderState;
identity.EncoderState | mozJPEG.EncoderState | browserPNG.EncoderState |
browserJPEG.EncoderState | browserWebP.EncoderState;
export type EncoderOptions =
identity.EncodeOptions | mozJPEG.EncodeOptions | browserPNG.EncodeOptions |
browserJPEG.EncodeOptions;
browserJPEG.EncodeOptions | browserWebP.EncodeOptions;
export type EncoderType = keyof typeof encoderMap;
export const encoderMap = {
@@ -15,6 +21,20 @@ export const encoderMap = {
[mozJPEG.type]: mozJPEG,
[browserPNG.type]: browserPNG,
[browserJPEG.type]: browserJPEG,
[browserWebP.type]: browserWebP,
};
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;
});

View File

@@ -12,7 +12,13 @@ import * as mozJPEG from '../../codecs/mozjpeg/encoder';
import * as identity from '../../codecs/identity/encoder';
import * as browserPNG from '../../codecs/browser-png/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 {
file: File;
@@ -54,7 +60,8 @@ async function compressImage(
case mozJPEG.type: return mozJPEG.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);
default: throw Error(`Unexpected encoder name`);
case browserWebP.type: return browserWebP.encode(source.data, encodeData.options);
default: throw Error(`Unexpected encoder ${JSON.stringify(encodeData)}`);
}
})();

View File

@@ -3,18 +3,28 @@ import * as style from './style.scss';
import { bind } from '../../lib/util';
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options';
import { type as mozJPEGType } from '../../codecs/mozjpeg/encoder';
import { type as identityType } from '../../codecs/identity/encoder';
import { type as browserPNGType } from '../../codecs/browser-png/encoder';
import { type as browserJPEGType } from '../../codecs/browser-jpeg/encoder';
import { EncoderState, EncoderType, EncoderOptions, encoders } from '../../codecs/encoders';
import * as mozJPEG from '../../codecs/mozjpeg/encoder';
import * as identity from '../../codecs/identity/encoder';
import * as browserPNG from '../../codecs/browser-png/encoder';
import * as browserJPEG from '../../codecs/browser-jpeg/encoder';
import * as browserWebP from '../../codecs/browser-webp/encoder';
import {
EncoderState,
EncoderType,
EncoderOptions,
encoders,
encodersSupported,
EncoderSupportMap,
} from '../../codecs/encoders';
const encoderOptionsComponentMap = {
[mozJPEGType]: MozJpegEncoderOptions,
[identityType]: undefined,
[browserPNGType]: undefined,
[browserJPEGType]: BrowserJPEGEncoderOptions,
[mozJPEG.type]: MozJpegEncoderOptions,
[identity.type]: undefined,
[browserPNG.type]: undefined,
[browserJPEG.type]: BrowserJPEGEncoderOptions,
[browserWebP.type]: BrowserWebPEncoderOptions,
};
interface Props {
@@ -24,11 +34,18 @@ interface Props {
onOptionsChange(newOptions: EncoderOptions): void;
}
interface State {}
interface State {
encoderSupportMap?: EncoderSupportMap;
}
export default class Options extends Component<Props, State> {
typeSelect?: HTMLSelectElement;
constructor() {
super();
encodersSupported.then(encoderSupportMap => this.setState({ encoderSupportMap }));
}
@bind
onTypeChange(event: Event) {
const el = event.currentTarget as HTMLSelectElement;
@@ -39,18 +56,22 @@ export default class Options extends Component<Props, State> {
this.props.onTypeChange(type);
}
render({ class: className, encoderState, onOptionsChange }: Props) {
render({ class: className, encoderState, onOptionsChange }: Props, { encoderSupportMap }: State) {
const EncoderOptionComponent = encoderOptionsComponentMap[encoderState.type];
return (
<div class={`${style.options}${className ? (' ' + className) : ''}`}>
<label>
Mode:
<select value={encoderState.type} onChange={this.onTypeChange}>
{encoders.map(encoder => (
<option value={encoder.type}>{encoder.label}</option>
))}
</select>
{encoderSupportMap ?
<select value={encoderState.type} onChange={this.onTypeChange}>
{encoders.filter(encoder => encoderSupportMap[encoder.type]).map(encoder => (
<option value={encoder.type}>{encoder.label}</option>
))}
</select>
:
<select><option>Loading</option></select>
}
</label>
{EncoderOptionComponent &&
<EncoderOptionComponent