Instantly share code, notes, and snippets.

Embed
What would you like to do?
Create Blob like window.createImageBitmap
type ImageBlobOptions = ImageBitmapOptions & { type?: string, quality?: number };
export declare function createImageBlob(source: ImageBitmapSource, options?: ImageBlobOptions): Promise<Blob>;
export declare function createImageBlob(source: ImageBitmapSource, sx: number, sy: number, sw: number, sh: number, options?: ImageBlobOptions): Promise<Blob>;
// Chrome Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=838108
const isImageBitmapRenderingContextUsable = (async () => {
if (window.createImageBitmap === undefined || window.ImageBitmapRenderingContext === undefined) {
return false;
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d");
const imageData = ctx.createImageData(1, 1);
imageData.data[3] = 255; // alpha
let blobURL;
{
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const bitmapCtx = canvas.getContext("bitmaprenderer");
if (bitmapCtx === null) {
return false;
}
const bitmap = await createImageBitmap(imageData);
bitmapCtx.transferFromImageBitmap(bitmap);
const blob = await new Promise((resolve) => canvas.toBlob(resolve));
blobURL = URL.createObjectURL(blob);
}
const image = new Image();
image.src = blobURL;
await new Promise((resolve) => image.addEventListener("load", resolve, { once: true }));
ctx.drawImage(image, 0, 0);
URL.revokeObjectURL(blobURL);
return ctx.getImageData(0, 0, 1, 1).data[3] !== 0;
})();
export async function createImageBlob(source, ...args) {
const options = (args.length >= 4 ? args[4] : args[0]) || {};
const canvas = document.createElement("canvas");
// ImageBitmap
const bitmap = null;
if (window.createImageBitmap !== undefined) {
// Firefox Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1335594
try {
bitmap = await createImageBitmap(source, ...args);
} catch {}
}
// use ImageBitmapRenderingContext if possible
if (bitmap !== null && await isImageBitmapRenderingContextUsable) {
canvas.width = bitmap.width;
canvas.height = bitmap.height;
const bitmapCtx = canvas.getContext("bitmaprenderer");
bitmapCtx.transferFromImageBitmap(bitmap);
return new Promise((resolve) => canvas.toBlob(resolve, options.type, options.quality));
}
const ctx = canvas.getContext("2d");
// use ImageBitmap if possible
if (bitmap !== null) {
canvas.width = bitmap.width;
canvas.height = bitmap.height;
ctx.drawImage(bitmap, 0, 0);
return new Promise((resolve) => canvas.toBlob(resolve, options.type, options.quality));
}
// fallback...
// resizeWidth, resizeHeight
let width = options.resizeWidth || null;
let height = options.resizeHeight || null;
// sw, sh
if (args.length >= 4) {
width = width || args[2];
height = height || args[3];
}
let blobURL = null;
if (source instanceof Blob) {
const image = new Image();
const url = URL.createObjectURL(source);
image.src = url;
await new Promise((resolve, reject) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", () => reject(new Error("Invalid Blob Image")), { once: true });
});
if (image.naturalWidth === 0 || image.naturalHeight === 0) {
URL.revokeObjectURL(url);
throw new Error("Blob Image has no intrinsic dimensions");
}
source = image; // overwrite source
width = width || image.naturalWidth;
height = height || image.naturalHeight;
blobURL = url;
} else if (source instanceof ImageData) {
if ((width === null || width === source.width) && (height === null || height === source.height)) {
({ width, height } = source);
} else {
// wrapped by canvas
const canvas = document.createElement("canvas");
canvas.width = source.width;
canvas.height = source.height;
const ctx = canvas.getContext("2d");
ctx.putImageData(source, 0, 0);
source = canvas; // overwrite source
width = width || source.width;
height = height || source.height;
}
} else {
const size = await getImageSize(source);
width = width || size.width;
height = height || size.height;
}
canvas.width = width;
canvas.height = height;
// resizeQuality
if (options.resizeQuality != null) {
const resizeQuality = options.resizeQuality;
if (resizeQuality === "pixelated") {
ctx.imageSmoothingEnabled = false;
} else {
ctx.imageSmoothingQuality = resizeQuality;
}
}
// imageOrientation
if (options.imageOrientation === "flipY") {
ctx.translate(0, height);
ctx.scale(1, -1);
}
if (args.length >= 4) {
ctx.drawImage(source, ...args.slice(0, 4), 0, 0, width, height);
} else {
ctx.drawImage(source, 0, 0, width, height);
}
if (blobURL !== null) {
URL.revokeObjectURL(blobURL);
}
return new Promise((resolve) => canvas.toBlob(resolve, options.type, options.quality));
}
async function getImageSize(source) {
// Image (HTMLImageElement)
if (source instanceof Image) {
if (source.currentSrc === "") {
throw new Error("source (HTMLImageElement) is empty");
}
if (!source.complete) {
await new Promise((resolve, reject) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", () => reject(new Error("Invalid HTMLImageElement")), { once: true });
});
}
if (source.naturalWidth === 0 || source.naturalHeight === 0) {
throw new Error("source (HTMLImageElement) has no intrinsic dimensions");
}
return { width: source.naturalWidth, height: source.naturalHeight };
}
// SVGImageElement
if (source instanceof SVGImageElement) {
const image = new Image();
const src = source.getAttributeNS("http://www.w3.org/1999/xlink", "href");
if (src === "") {
throw new Error("source (SVGImageElement) is empty");
}
image.src = src;
await new Promise((resolve, reject) => {
image.addEventListener("load", resolve, { once: true });
image.addEventListener("error", () => reject(new Error("Invalid SVGImageElement")), { once: true });
});
if (image.naturalWidth === 0 || image.naturalHeight === 0) {
throw new Error("source (SVGImageElement) has no intrinsic dimensions");
}
return { width: image.naturalWidth, height: image.naturalHeight };
}
// HTMLVideoElement
if (source instanceof HTMLVideoElement) {
if (source.currentSrc === "") {
throw new Error("source (HTMLVideoElement) is empty");
}
if (source.readyState < source.HAVE_METADATA) {
await new Promise((resolve, reject) => {
source.addEventListener("loadedmetadata", resolve, { once: true });
source.addEventListener("error", () => reject(source.error), { once: true });
});
}
return { width: source.videoWidth, height: source.videoHeight };
}
// HTMLCanvasElement, OffscreenCanvas
return { width: source.width, height: source.height };
}
@petamoriken

This comment has been minimized.

Owner

petamoriken commented Nov 28, 2018

sw, sh が元の画像よりも大きい時を考慮する。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment