Skip to content

Commit

Permalink
Adding browser's webp encoder (#72)
Browse files Browse the repository at this point in the history
* Adding WebP (without feature detect in place)

* Adding WebP check

* Remove unused import
  • Loading branch information
jakearchibald committed Jul 2, 2018
1 parent a09ec26 commit 6872997
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 20 deletions.
23 changes: 23 additions & 0 deletions src/codecs/browser-webp/encoder.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 3 additions & 0 deletions src/codecs/browser-webp/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import qualityOption from '../generic/quality-option';

export default qualityOption({ min: 0, max: 1, step: 0 });
24 changes: 22 additions & 2 deletions src/codecs/encoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,39 @@ 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 = {
[identity.type]: identity,
[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;
});
11 changes: 9 additions & 2 deletions src/components/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)}`);
}
})();

Expand Down
53 changes: 37 additions & 16 deletions src/components/options/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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
Expand Down

0 comments on commit 6872997

Please sign in to comment.