UNPKG

@tldraw/utils

Version:

tldraw infinite canvas SDK (private utilities).

245 lines (244 loc) • 8.38 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var media_exports = {}; __export(media_exports, { DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES: () => DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES, DEFAULT_SUPPORTED_IMAGE_TYPES: () => DEFAULT_SUPPORTED_IMAGE_TYPES, DEFAULT_SUPPORTED_MEDIA_TYPES: () => DEFAULT_SUPPORTED_MEDIA_TYPES, DEFAULT_SUPPORTED_MEDIA_TYPE_LIST: () => DEFAULT_SUPPORTED_MEDIA_TYPE_LIST, DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES: () => DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES, DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES: () => DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES, DEFAULT_SUPPORT_VIDEO_TYPES: () => DEFAULT_SUPPORT_VIDEO_TYPES, MediaHelpers: () => MediaHelpers }); module.exports = __toCommonJS(media_exports); var import_control = require("../control"); var import_network = require("../network"); var import_apng = require("./apng"); var import_avif = require("./avif"); var import_gif = require("./gif"); var import_png = require("./png"); var import_webp = require("./webp"); const DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES = Object.freeze(["image/svg+xml"]); const DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES = Object.freeze([ "image/jpeg", "image/png", "image/webp" ]); const DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES = Object.freeze([ "image/gif", "image/apng", "image/avif" ]); const DEFAULT_SUPPORTED_IMAGE_TYPES = Object.freeze([ ...DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES, ...DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES, ...DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES ]); const DEFAULT_SUPPORT_VIDEO_TYPES = Object.freeze([ "video/mp4", "video/webm", "video/quicktime" ]); const DEFAULT_SUPPORTED_MEDIA_TYPES = Object.freeze([ ...DEFAULT_SUPPORTED_IMAGE_TYPES, ...DEFAULT_SUPPORT_VIDEO_TYPES ]); const DEFAULT_SUPPORTED_MEDIA_TYPE_LIST = DEFAULT_SUPPORTED_MEDIA_TYPES.join(","); class MediaHelpers { /** * Load a video from a url. * @public */ static loadVideo(src) { return new Promise((resolve, reject) => { const video = document.createElement("video"); video.onloadeddata = () => resolve(video); video.onerror = (e) => { console.error(e); reject(new Error("Could not load video")); }; video.crossOrigin = "anonymous"; video.src = src; }); } static async getVideoFrameAsDataUrl(video, time = 0) { const promise = (0, import_control.promiseWithResolve)(); let didSetTime = false; const onReadyStateChanged = () => { if (!didSetTime) { if (video.readyState >= video.HAVE_METADATA) { didSetTime = true; video.currentTime = time; } else { return; } } if (video.readyState >= video.HAVE_CURRENT_DATA) { const canvas = document.createElement("canvas"); canvas.width = video.videoWidth; canvas.height = video.videoHeight; const ctx = canvas.getContext("2d"); if (!ctx) { throw new Error("Could not get 2d context"); } ctx.drawImage(video, 0, 0); promise.resolve(canvas.toDataURL()); } }; const onError = (e) => { console.error(e); promise.reject(new Error("Could not get video frame")); }; video.addEventListener("loadedmetadata", onReadyStateChanged); video.addEventListener("loadeddata", onReadyStateChanged); video.addEventListener("canplay", onReadyStateChanged); video.addEventListener("seeked", onReadyStateChanged); video.addEventListener("error", onError); video.addEventListener("stalled", onError); onReadyStateChanged(); try { return await promise; } finally { video.removeEventListener("loadedmetadata", onReadyStateChanged); video.removeEventListener("loadeddata", onReadyStateChanged); video.removeEventListener("canplay", onReadyStateChanged); video.removeEventListener("seeked", onReadyStateChanged); video.removeEventListener("error", onError); video.removeEventListener("stalled", onError); } } /** * Load an image from a url. * @public */ static getImageAndDimensions(src) { return new Promise((resolve, reject) => { const img = (0, import_network.Image)(); img.onload = () => { let dimensions; if (img.naturalWidth) { dimensions = { w: img.naturalWidth, h: img.naturalHeight }; } else { document.body.appendChild(img); dimensions = { w: img.clientWidth, h: img.clientHeight }; document.body.removeChild(img); } resolve({ ...dimensions, image: img }); }; img.onerror = (e) => { console.error(e); reject(new Error("Could not load image")); }; img.crossOrigin = "anonymous"; img.referrerPolicy = "strict-origin-when-cross-origin"; img.style.visibility = "hidden"; img.style.position = "absolute"; img.style.opacity = "0"; img.style.zIndex = "-9999"; img.src = src; }); } /** * Get the size of a video blob * * @param blob - A SharedBlob containing the video * @public */ static async getVideoSize(blob) { return MediaHelpers.usingObjectURL(blob, async (url) => { const video = await MediaHelpers.loadVideo(url); return { w: video.videoWidth, h: video.videoHeight }; }); } /** * Get the size of an image blob * * @param blob - A Blob containing the image. * @public */ static async getImageSize(blob) { const { w, h } = await MediaHelpers.usingObjectURL(blob, MediaHelpers.getImageAndDimensions); try { if (blob.type === "image/png") { const view = new DataView(await blob.arrayBuffer()); if (import_png.PngHelpers.isPng(view, 0)) { const physChunk = import_png.PngHelpers.findChunk(view, "pHYs"); if (physChunk) { const physData = import_png.PngHelpers.parsePhys(view, physChunk.dataOffset); if (physData.unit === 1 && physData.ppux === physData.ppuy) { const pixelsPerMeter = 72 / 0.0254; const pixelRatio = Math.max(physData.ppux / pixelsPerMeter, 1); return { w: Math.round(w / pixelRatio), h: Math.round(h / pixelRatio) }; } } } } } catch (err) { console.error(err); return { w, h }; } return { w, h }; } static async isAnimated(file) { if (file.type === "image/gif") { return (0, import_gif.isGifAnimated)(await file.arrayBuffer()); } if (file.type === "image/avif") { return (0, import_avif.isAvifAnimated)(await file.arrayBuffer()); } if (file.type === "image/webp") { return (0, import_webp.isWebpAnimated)(await file.arrayBuffer()); } if (file.type === "image/apng") { return (0, import_apng.isApngAnimated)(await file.arrayBuffer()); } return false; } static isAnimatedImageType(mimeType) { return DEFAULT_SUPPORTED_ANIMATED_IMAGE_TYPES.includes(mimeType || ""); } static isStaticImageType(mimeType) { return DEFAULT_SUPPORTED_STATIC_IMAGE_TYPES.includes(mimeType || ""); } static isVectorImageType(mimeType) { return DEFAULT_SUPPORTED_VECTOR_IMAGE_TYPES.includes(mimeType || ""); } static isImageType(mimeType) { return DEFAULT_SUPPORTED_IMAGE_TYPES.includes(mimeType || ""); } static async usingObjectURL(blob, fn) { const url = URL.createObjectURL(blob); try { return await fn(url); } finally { URL.revokeObjectURL(url); } } } //# sourceMappingURL=media.js.map