Remove circular dependency

This commit is contained in:
Jake Archibald
2021-05-27 09:13:17 +01:00
parent 397193a5f5
commit 40c81ef782
10 changed files with 164 additions and 163 deletions

View File

@@ -5,7 +5,7 @@ import './custom-els/PinchZoom';
import './custom-els/TwoUp';
import * as style from './style.css';
import 'add-css:./style.css';
import { shallowEqual, drawDataToCanvas } from '../../util';
import { shallowEqual } from '../../util';
import {
ToggleBackgroundIcon,
AddIcon,
@@ -18,6 +18,7 @@ import type { PreprocessorState } from '../../feature-meta';
import { cleanSet } from '../../util/clean-modify';
import type { SourceImage } from '../../Compress';
import { linkRef } from 'shared/prerendered-app/util';
import { drawDataToCanvas } from 'client/lazy-app/util/canvas';
interface Props {
source?: SourceImage;

View File

@@ -4,7 +4,6 @@ import * as style from './style.css';
import 'add-css:./style.css';
import {
blobToImg,
drawableToImageData,
blobToText,
builtinDecode,
sniffMimeType,
@@ -33,6 +32,7 @@ import WorkerBridge from '../worker-bridge';
import { resize } from 'features/processors/resize/client';
import type SnackBarElement from 'shared/custom-els/snack-bar';
import { generateCliInvocation } from '../util/cli';
import { drawableToImageData } from '../util/canvas';
export type OutputType = EncoderType | 'identity';

View File

@@ -0,0 +1,154 @@
/** Replace the contents of a canvas with the given data */
export function drawDataToCanvas(canvas: HTMLCanvasElement, data: ImageData) {
const ctx = canvas.getContext('2d');
if (!ctx) throw Error('Canvas not initialized');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(data, 0, 0);
}
/**
* Encode some image data in a given format using the browser's encoders
*
* @param {ImageData} data
* @param {string} type A mime type, eg image/jpeg.
* @param {number} [quality] Between 0-1.
*/
export async function canvasEncode(
data: ImageData,
type: string,
quality?: number,
): Promise<Blob> {
const canvas = document.createElement('canvas');
canvas.width = data.width;
canvas.height = data.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw Error('Canvas not initialized');
ctx.putImageData(data, 0, 0);
let blob: Blob | null;
if ('toBlob' in canvas) {
blob = await new Promise<Blob | null>((r) =>
canvas.toBlob(r, type, quality),
);
} else {
// Welcome to Edge.
// TypeScript thinks `canvas` is 'never', so it needs casting.
const dataUrl = (canvas as HTMLCanvasElement).toDataURL(type, quality);
const result = /data:([^;]+);base64,(.*)$/.exec(dataUrl);
if (!result) throw Error('Data URL reading failed');
const outputType = result[1];
const binaryStr = atob(result[2]);
const data = new Uint8Array(binaryStr.length);
for (let i = 0; i < data.length; i += 1) {
data[i] = binaryStr.charCodeAt(i);
}
blob = new Blob([data], { type: outputType });
}
if (!blob) throw Error('Encoding failed');
return blob;
}
interface DrawableToImageDataOptions {
width?: number;
height?: number;
sx?: number;
sy?: number;
sw?: number;
sh?: number;
}
function getWidth(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayWidth' in drawable) {
return drawable.displayWidth;
}
return drawable.width;
}
function getHeight(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayHeight' in drawable) {
return drawable.displayHeight;
}
return drawable.height;
}
export function drawableToImageData(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
opts: DrawableToImageDataOptions = {},
): ImageData {
const {
width = getWidth(drawable),
height = getHeight(drawable),
sx = 0,
sy = 0,
sw = getWidth(drawable),
sh = getHeight(drawable),
} = opts;
// Make canvas same size as image
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// Draw image onto canvas
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Could not create canvas context');
ctx.drawImage(drawable, sx, sy, sw, sh, 0, 0, width, height);
return ctx.getImageData(0, 0, width, height);
}
export type BuiltinResizeMethod = 'pixelated' | 'low' | 'medium' | 'high';
export function builtinResize(
data: ImageData,
sx: number,
sy: number,
sw: number,
sh: number,
dw: number,
dh: number,
method: BuiltinResizeMethod,
): ImageData {
const canvasSource = document.createElement('canvas');
canvasSource.width = data.width;
canvasSource.height = data.height;
drawDataToCanvas(canvasSource, data);
const canvasDest = document.createElement('canvas');
canvasDest.width = dw;
canvasDest.height = dh;
const ctx = canvasDest.getContext('2d');
if (!ctx) throw new Error('Could not create canvas context');
if (method === 'pixelated') {
ctx.imageSmoothingEnabled = false;
} else {
ctx.imageSmoothingQuality = method;
}
ctx.drawImage(canvasSource, sx, sy, sw, sh, 0, 0, dw, dh);
return ctx.getImageData(0, 0, dw, dh);
}
/**
* Test whether <canvas> can encode to a particular type.
*/
export async function canvasEncodeTest(mimeType: string): Promise<boolean> {
try {
const blob = await canvasEncode(new ImageData(1, 1), mimeType);
// According to the spec, the blob should be null if the format isn't supported…
if (!blob) return false;
// …but Safari & Firefox fall back to PNG, so we need to check the mime type.
return blob.type === mimeType;
} catch (err) {
return false;
}
}

View File

@@ -12,6 +12,7 @@
*/
import * as WebCodecs from '../util/web-codecs';
import { drawableToImageData } from './canvas';
/**
* Compare two objects, returning a boolean indicating if
@@ -23,62 +24,6 @@ export function shallowEqual(one: any, two: any) {
return true;
}
/** Replace the contents of a canvas with the given data */
export function drawDataToCanvas(canvas: HTMLCanvasElement, data: ImageData) {
const ctx = canvas.getContext('2d');
if (!ctx) throw Error('Canvas not initialized');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.putImageData(data, 0, 0);
}
/**
* Encode some image data in a given format using the browser's encoders
*
* @param {ImageData} data
* @param {string} type A mime type, eg image/jpeg.
* @param {number} [quality] Between 0-1.
*/
export async function canvasEncode(
data: ImageData,
type: string,
quality?: number,
): Promise<Blob> {
const canvas = document.createElement('canvas');
canvas.width = data.width;
canvas.height = data.height;
const ctx = canvas.getContext('2d');
if (!ctx) throw Error('Canvas not initialized');
ctx.putImageData(data, 0, 0);
let blob: Blob | null;
if ('toBlob' in canvas) {
blob = await new Promise<Blob | null>((r) =>
canvas.toBlob(r, type, quality),
);
} else {
// Welcome to Edge.
// TypeScript thinks `canvas` is 'never', so it needs casting.
const dataUrl = (canvas as HTMLCanvasElement).toDataURL(type, quality);
const result = /data:([^;]+);base64,(.*)$/.exec(dataUrl);
if (!result) throw Error('Data URL reading failed');
const outputType = result[1];
const binaryStr = atob(result[2]);
const data = new Uint8Array(binaryStr.length);
for (let i = 0; i < data.length; i += 1) {
data[i] = binaryStr.charCodeAt(i);
}
blob = new Blob([data], { type: outputType });
}
if (!blob) throw Error('Encoding failed');
return blob;
}
async function decodeImage(url: string): Promise<HTMLImageElement> {
const img = new Image();
img.decoding = 'async';
@@ -186,57 +131,6 @@ export async function blobToImg(blob: Blob): Promise<HTMLImageElement> {
}
}
interface DrawableToImageDataOptions {
width?: number;
height?: number;
sx?: number;
sy?: number;
sw?: number;
sh?: number;
}
function getWidth(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayWidth' in drawable) {
return drawable.displayWidth;
}
return drawable.width;
}
function getHeight(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
): number {
if ('displayHeight' in drawable) {
return drawable.displayHeight;
}
return drawable.height;
}
export function drawableToImageData(
drawable: ImageBitmap | HTMLImageElement | VideoFrame,
opts: DrawableToImageDataOptions = {},
): ImageData {
const {
width = getWidth(drawable),
height = getHeight(drawable),
sx = 0,
sy = 0,
sw = getWidth(drawable),
sh = getHeight(drawable),
} = opts;
// Make canvas same size as image
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
// Draw image onto canvas
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('Could not create canvas context');
ctx.drawImage(drawable, sx, sy, sw, sh, 0, 0, width, height);
return ctx.getImageData(0, 0, width, height);
}
export async function builtinDecode(
signal: AbortSignal,
blob: Blob,
@@ -259,39 +153,6 @@ export async function builtinDecode(
return drawableToImageData(drawable);
}
export type BuiltinResizeMethod = 'pixelated' | 'low' | 'medium' | 'high';
export function builtinResize(
data: ImageData,
sx: number,
sy: number,
sw: number,
sh: number,
dw: number,
dh: number,
method: BuiltinResizeMethod,
): ImageData {
const canvasSource = document.createElement('canvas');
canvasSource.width = data.width;
canvasSource.height = data.height;
drawDataToCanvas(canvasSource, data);
const canvasDest = document.createElement('canvas');
canvasDest.width = dw;
canvasDest.height = dh;
const ctx = canvasDest.getContext('2d');
if (!ctx) throw new Error('Could not create canvas context');
if (method === 'pixelated') {
ctx.imageSmoothingEnabled = false;
} else {
ctx.imageSmoothingQuality = method;
}
ctx.drawImage(canvasSource, sx, sy, sw, sh, 0, 0, dw, dh);
return ctx.getImageData(0, 0, dw, dh);
}
/**
* @param field An HTMLInputElement, but the casting is done here to tidy up onChange.
* @param defaultVal Value to return if 'field' doesn't exist.
@@ -434,18 +295,3 @@ export async function abortable<T>(
}),
]);
}
/**
* Test whether <canvas> can encode to a particular type.
*/
export async function canvasEncodeTest(mimeType: string): Promise<boolean> {
try {
const blob = await canvasEncode(new ImageData(1, 1), mimeType);
// According to the spec, the blob should be null if the format isn't supported…
if (!blob) return false;
// …but Safari & Firefox fall back to PNG, so we need to check the mime type.
return blob.type === mimeType;
} catch (err) {
return false;
}
}

View File

@@ -1,4 +1,4 @@
import { drawableToImageData } from 'client/lazy-app/util';
import { drawableToImageData } from '../canvas';
const hasImageDecoder = typeof ImageDecoder !== 'undefined';
export async function isTypeSupported(mimeType: string): Promise<boolean> {

View File

@@ -1,4 +1,4 @@
import { canvasEncodeTest, canvasEncode } from 'client/lazy-app/util';
import { canvasEncodeTest, canvasEncode } from 'client/lazy-app/util/canvas';
import WorkerBridge from 'client/lazy-app/worker-bridge';
import { EncodeOptions, mimeType } from '../shared/meta';

View File

@@ -1,4 +1,4 @@
import { canvasEncode } from 'client/lazy-app/util';
import { canvasEncode } from 'client/lazy-app/util/canvas';
import WorkerBridge from 'client/lazy-app/worker-bridge';
import { qualityOption } from 'features/client-utils';
import { mimeType, EncodeOptions } from '../shared/meta';

View File

@@ -1,4 +1,4 @@
import { canvasEncode } from 'client/lazy-app/util';
import { canvasEncode } from 'client/lazy-app/util/canvas';
import WorkerBridge from 'client/lazy-app/worker-bridge';
import { EncodeOptions, mimeType } from '../shared/meta';

View File

@@ -1,5 +1,5 @@
import { canvasEncode } from 'client/lazy-app/util/canvas';
import {
canvasEncode,
abortable,
blobToArrayBuffer,
inputFieldChecked,

View File

@@ -2,7 +2,7 @@ import {
builtinResize,
BuiltinResizeMethod,
drawableToImageData,
} from 'client/lazy-app/util';
} from 'client/lazy-app/util/canvas';
import {
BrowserResizeOptions,
VectorResizeOptions,