Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding browser's webp encoder #72

Merged
merged 3 commits into from
Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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