Skip to content

Instantly share code, notes, and snippets.

@petamoriken
Last active November 28, 2018 11:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petamoriken/718f71c84a9059cebfe3878fc73cf28f to your computer and use it in GitHub Desktop.
Save petamoriken/718f71c84a9059cebfe3878fc73cf28f to your computer and use it in GitHub Desktop.
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
Copy link
Author

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