diff --git a/src/codecs/decoders.ts b/src/codecs/decoders.ts index e8bee398..01a4ff36 100644 --- a/src/codecs/decoders.ts +++ b/src/codecs/decoders.ts @@ -1,11 +1,11 @@ -import * as wasmWebP from './webp/decoder'; -import * as browserWebP from './browser-webp/decoder'; +import * as wasmWebp from './webp/decoder'; +import * as browserWebp from './webp/decoder'; import { createImageBitmapPolyfill, sniffMimeType } from '../lib/util'; export interface Decoder { name: string; - decode(file: File): Promise; + decode(blob: Blob): Promise; isSupported(): Promise; canHandleMimeType(mimeType: string): boolean; } @@ -13,8 +13,8 @@ export interface Decoder { // We load all decoders and filter out the unsupported ones. export const decodersPromise: Promise = Promise.all( [ - browserWebP, - wasmWebP, + browserWebp, + wasmWebp, ] .map(async (decoder) => { if (await decoder.isSupported()) { @@ -26,33 +26,22 @@ export const decodersPromise: Promise = Promise.all( // values here. ).then(list => list.filter(item => !!item)) as any as Promise; -export async function findDecoder(file: File): Promise { +export async function findDecodersByMimeType(mimeType: string): Promise { const decoders = await decodersPromise; - const mimeType = await sniffMimeType(file); - if (!mimeType) { - return; - } - return decoders.find(decoder => decoder.canHandleMimeType(mimeType)); + return decoders.filter(decoder => decoder.canHandleMimeType(mimeType)); } -const nativelySupportedMimeTypes = [ - 'image/jpeg', - 'image/png', - 'image/gif', -]; - -export async function decodeFile(file: File): Promise { - const mimeType = await sniffMimeType(file); - if (!mimeType) { - throw new Error('Could not determine mime type'); +export async function decodeImage(blob: Blob): Promise { + const mimeType = await sniffMimeType(blob); + const decoders = await findDecodersByMimeType(mimeType); + if (decoders.length <= 0) { + // If we can’t find a decoder, hailmary with createImageBitmap + return createImageBitmapPolyfill(blob); } - if (nativelySupportedMimeTypes.includes(mimeType)) { - return createImageBitmapPolyfill(file); + for (const decoder of decoders) { + try { + return await decoder.decode(blob); + } catch { } } - const decoder = await findDecoder(file); - if (!decoder) { - throw new Error(`Can’t decode files with mime type ${mimeType}`); - } - console.log(`Going with ${decoder.name}`); - return decoder.decode(file); + throw new Error('No decoder could decode image'); } diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index d11f83d5..ba53e53f 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -26,7 +26,7 @@ import { encoderMap, } from '../../codecs/encoders'; -import { decodeFile } from '../../codecs/decoders'; +import { decodeImage } from '../../codecs/decoders'; interface SourceImage { file: File; @@ -180,7 +180,7 @@ export default class App extends Component { async updateFile(file: File) { this.setState({ loading: true }); try { - const bmp = await decodeFile(file); + const bmp = await decodeImage(file); // compute the corresponding ImageData once since it only changes when the file changes: const data = await bitmapToImageData(bmp); diff --git a/src/lib/util.ts b/src/lib/util.ts index c3a6f5a4..4af40c39 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -128,16 +128,18 @@ const magicNumberToMimeType = new Map([ [/^RIFF....WEBPVP8 /, 'image/webp'], ]); -export async function sniffMimeType(blob: Blob): Promise { - const firstChunk = await blobToArrayBuffer(blob.slice(0, 1024)); - const firstChunkString = Array.from( - new Uint8Array(firstChunk)).map(v => String.fromCodePoint(v), - ).join(''); +export async function sniffMimeType(blob: Blob): Promise { + const firstChunk = await blobToArrayBuffer(blob.slice(0, 16)); + const firstChunkString = + Array.from(new Uint8Array(firstChunk)) + .map(v => String.fromCodePoint(v)) + .join(''); for (const [detector, mimeType] of magicNumberToMimeType.entries()) { if (detector.test(firstChunkString)) { return mimeType; } } + return ''; } export function createImageBitmapPolyfill(blob: Blob): Promise {