Last active
November 28, 2018 11:16
-
-
Save petamoriken/718f71c84a9059cebfe3878fc73cf28f to your computer and use it in GitHub Desktop.
Create Blob like window.createImageBitmap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 }; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
sw
,sh
が元の画像よりも大きい時を考慮する。