mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-12 00:37:19 +00:00
Remove circular dependency
This commit is contained in:
@@ -5,7 +5,7 @@ import './custom-els/PinchZoom';
|
|||||||
import './custom-els/TwoUp';
|
import './custom-els/TwoUp';
|
||||||
import * as style from './style.css';
|
import * as style from './style.css';
|
||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
import { shallowEqual, drawDataToCanvas } from '../../util';
|
import { shallowEqual } from '../../util';
|
||||||
import {
|
import {
|
||||||
ToggleBackgroundIcon,
|
ToggleBackgroundIcon,
|
||||||
AddIcon,
|
AddIcon,
|
||||||
@@ -18,6 +18,7 @@ import type { PreprocessorState } from '../../feature-meta';
|
|||||||
import { cleanSet } from '../../util/clean-modify';
|
import { cleanSet } from '../../util/clean-modify';
|
||||||
import type { SourceImage } from '../../Compress';
|
import type { SourceImage } from '../../Compress';
|
||||||
import { linkRef } from 'shared/prerendered-app/util';
|
import { linkRef } from 'shared/prerendered-app/util';
|
||||||
|
import { drawDataToCanvas } from 'client/lazy-app/util/canvas';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
source?: SourceImage;
|
source?: SourceImage;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import * as style from './style.css';
|
|||||||
import 'add-css:./style.css';
|
import 'add-css:./style.css';
|
||||||
import {
|
import {
|
||||||
blobToImg,
|
blobToImg,
|
||||||
drawableToImageData,
|
|
||||||
blobToText,
|
blobToText,
|
||||||
builtinDecode,
|
builtinDecode,
|
||||||
sniffMimeType,
|
sniffMimeType,
|
||||||
@@ -33,6 +32,7 @@ import WorkerBridge from '../worker-bridge';
|
|||||||
import { resize } from 'features/processors/resize/client';
|
import { resize } from 'features/processors/resize/client';
|
||||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||||
import { generateCliInvocation } from '../util/cli';
|
import { generateCliInvocation } from '../util/cli';
|
||||||
|
import { drawableToImageData } from '../util/canvas';
|
||||||
|
|
||||||
export type OutputType = EncoderType | 'identity';
|
export type OutputType = EncoderType | 'identity';
|
||||||
|
|
||||||
|
|||||||
154
src/client/lazy-app/util/canvas.ts
Normal file
154
src/client/lazy-app/util/canvas.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as WebCodecs from '../util/web-codecs';
|
import * as WebCodecs from '../util/web-codecs';
|
||||||
|
import { drawableToImageData } from './canvas';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two objects, returning a boolean indicating if
|
* Compare two objects, returning a boolean indicating if
|
||||||
@@ -23,62 +24,6 @@ export function shallowEqual(one: any, two: any) {
|
|||||||
return true;
|
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> {
|
async function decodeImage(url: string): Promise<HTMLImageElement> {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.decoding = 'async';
|
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(
|
export async function builtinDecode(
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
blob: Blob,
|
blob: Blob,
|
||||||
@@ -259,39 +153,6 @@ export async function builtinDecode(
|
|||||||
return drawableToImageData(drawable);
|
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 field An HTMLInputElement, but the casting is done here to tidy up onChange.
|
||||||
* @param defaultVal Value to return if 'field' doesn't exist.
|
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { drawableToImageData } from 'client/lazy-app/util';
|
import { drawableToImageData } from '../canvas';
|
||||||
|
|
||||||
const hasImageDecoder = typeof ImageDecoder !== 'undefined';
|
const hasImageDecoder = typeof ImageDecoder !== 'undefined';
|
||||||
export async function isTypeSupported(mimeType: string): Promise<boolean> {
|
export async function isTypeSupported(mimeType: string): Promise<boolean> {
|
||||||
|
|||||||
@@ -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 WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { EncodeOptions, mimeType } from '../shared/meta';
|
import { EncodeOptions, mimeType } from '../shared/meta';
|
||||||
|
|
||||||
|
|||||||
@@ -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 WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { qualityOption } from 'features/client-utils';
|
import { qualityOption } from 'features/client-utils';
|
||||||
import { mimeType, EncodeOptions } from '../shared/meta';
|
import { mimeType, EncodeOptions } from '../shared/meta';
|
||||||
|
|||||||
@@ -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 WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { EncodeOptions, mimeType } from '../shared/meta';
|
import { EncodeOptions, mimeType } from '../shared/meta';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { canvasEncode } from 'client/lazy-app/util/canvas';
|
||||||
import {
|
import {
|
||||||
canvasEncode,
|
|
||||||
abortable,
|
abortable,
|
||||||
blobToArrayBuffer,
|
blobToArrayBuffer,
|
||||||
inputFieldChecked,
|
inputFieldChecked,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
builtinResize,
|
builtinResize,
|
||||||
BuiltinResizeMethod,
|
BuiltinResizeMethod,
|
||||||
drawableToImageData,
|
drawableToImageData,
|
||||||
} from 'client/lazy-app/util';
|
} from 'client/lazy-app/util/canvas';
|
||||||
import {
|
import {
|
||||||
BrowserResizeOptions,
|
BrowserResizeOptions,
|
||||||
VectorResizeOptions,
|
VectorResizeOptions,
|
||||||
|
|||||||
Reference in New Issue
Block a user