import { store } from "@/features/mark/store"; import { PictureSlice, Task } from "@/types"; // TODO: 打开cache后,会造成没有 vue devtools 时,canvas缓存错误,暂时不知道原因 // 通过回看的测试,打开回看,再关闭回看,稍等一会儿再打开回看,确实可以看到该缓存时缓存了,该丢弃时丢弃了 // 把store.currentTask当做 weakRef ,当它不存在时,就丢弃它所有的图片 const weakedMapImages = new WeakMap>(); /** * 异步获取图片 * @param url 完整的图片路径 * @returns Promise */ export async function loadImage(url: string): Promise { // if (store.currentTask && weakedMapImages.get(store.currentTask)) { // const imagesCache = weakedMapImages.get(store.currentTask); // if (imagesCache) { // // console.log("cached image", url); // const image = imagesCache.get(url); // if (image) return image; // } // } // else loading image return new Promise((resolve, reject) => { const image = new Image(); image.setAttribute("crossorigin", "anonymous"); image.src = url; image.onload = () => { // if (store.currentTask) { // let imagesCache = weakedMapImages.get(store.currentTask); // if (!imagesCache) { // imagesCache = new Map(); // weakedMapImages.set(store.currentTask, imagesCache); // } // imagesCache.set(url, image); // } resolve(image); }; image.onerror = reject; }); } // 存放裁切图的ObjectUrls let objectUrlMap = new Map(); const OBJECT_URLS_MAP_MAX_SIZE = 100; export async function getDataUrlForSliceConfig( image: HTMLImageElement, sliceConfig: PictureSlice, maxSliceWidth: number, urlForCache: string ) { const { i, x, y, w, h } = sliceConfig; const key = `${urlForCache}-${i}-${x}-${y}-${w}-${h}`; if (objectUrlMap.get(key)) { console.log("cached slice objectUrl"); return objectUrlMap.get(key); } const canvas = document.createElement("canvas"); canvas.width = Math.max(sliceConfig.w, maxSliceWidth); canvas.height = sliceConfig.h; const ctx = canvas.getContext("2d"); if (!ctx) { console.log('canvas.getContext("2d") error'); } // drawImage 画图软件透明色 ctx?.drawImage( image, sliceConfig.x, sliceConfig.y, sliceConfig.w, sliceConfig.h, 0, 0, sliceConfig.w, sliceConfig.h ); // console.log(image, canvas.height, sliceConfig, ctx); // console.log(canvas.toDataURL()); // 如果用toBlob,则产生异步,而且URL.createObjectURL还会需要手动释放 // const dataurl = canvas.toDataURL(); const blob = await new Promise((res) => { canvas.toBlob(res); }); const dataurl = URL.createObjectURL(blob); cacheFIFO(); objectUrlMap.set(key, dataurl); return dataurl; } // 清理缓存的过时数据(清除头10张),First in first out function cacheFIFO() { if (objectUrlMap.size > OBJECT_URLS_MAP_MAX_SIZE) { const ary = [...objectUrlMap.entries()]; const toRelease = ary.splice(0, 10); for (const u of toRelease) { URL.revokeObjectURL(u[1]); } objectUrlMap = new Map(ary); } } export async function getDataUrlForSplitConfig( image: HTMLImageElement, config: [number, number], maxSliceWidth: number, urlForCache: string ) { const [start, end] = config; const key = `${urlForCache}-${start}-${end}`; if (objectUrlMap.get(key)) { console.log("cached split objectUrl"); return objectUrlMap.get(key); } const width = image.naturalWidth * (end - start); const canvas = document.createElement("canvas"); canvas.width = Math.max(width, maxSliceWidth); canvas.height = image.naturalHeight; const ctx = canvas.getContext("2d"); if (!ctx) { console.log('canvas.getContext("2d") error'); } // drawImage 画图软件透明色 ctx?.drawImage( image, image.naturalWidth * start, 0, image.naturalWidth * end, image.naturalHeight, 0, 0, image.naturalWidth * end, image.naturalHeight ); // 如果用toBlob,则产生异步,而且URL.createObjectURL还会需要手动释放 // const dataurl = canvas.toDataURL(); const blob = await new Promise((res) => { canvas.toBlob(res); }); const dataurl = URL.createObjectURL(blob); cacheFIFO(); objectUrlMap.set(key, dataurl); return dataurl; } export async function preDrawImage(_currentTask: Task) { if (!_currentTask?.libraryId) return; let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准 const hasSliceConfig = store.currentTask?.sliceConfig?.length; const images = []; if (hasSliceConfig) { // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度 for (const sliceConfig of _currentTask.sliceConfig) { const url = _currentTask.sliceUrls[sliceConfig.i - 1]; const image = await loadImage(url); images[sliceConfig.i] = image; if (sliceConfig.w === 0 && sliceConfig.h === 0) { // 选择整图时,w/h 为0 sliceConfig.w = image.naturalWidth; sliceConfig.h = image.naturalHeight; } } maxSliceWidth = Math.max(..._currentTask.sliceConfig.map((v) => v.w)); // 用来保存sliceImage在整个图片容器中(不包括image-seperator)的高度范围 for (const sliceConfig of _currentTask.sliceConfig) { const url = _currentTask.sliceUrls[sliceConfig.i - 1]; const image = images[sliceConfig.i]; (await getDataUrlForSliceConfig( image, sliceConfig, maxSliceWidth, url )) as string; } } else { for (const url of _currentTask.sliceUrls) { const image = await loadImage(url); images.push(image); } const splitConfigPairs = store.setting.splitConfig .map((v, index, ary) => (index % 2 === 0 ? [v, ary[index + 1]] : false)) .filter((v) => v) as unknown as Array<[number, number]>; const maxSplitConfig = Math.max(...store.setting.splitConfig); maxSliceWidth = Math.max(...images.map((v) => v.naturalWidth)) * maxSplitConfig; for (const url of _currentTask.sliceUrls) { for (const config of splitConfigPairs) { const indexInSliceUrls = _currentTask.sliceUrls.indexOf(url) + 1; const image = images[indexInSliceUrls - 1]; (await getDataUrlForSplitConfig( image, config, maxSliceWidth, url )) as string; } } } }