Forráskód Böngészése

缓存图片,避免重新获取和裁切

Michael Wang 4 éve
szülő
commit
8df656ee1f
3 módosított fájl, 153 hozzáadás és 58 törlés
  1. 16 56
      src/features/mark/MarkBody.vue
  2. 1 1
      src/types/index.ts
  3. 136 1
      src/utils/utils.ts

+ 16 - 56
src/features/mark/MarkBody.vue

@@ -65,7 +65,11 @@ import filters from "@/filters";
 import MarkDrawTrack from "./MarkDrawTrack.vue";
 import { ModeEnum, Track } from "@/types";
 import { useTimers } from "@/setups/useTimers";
-import { loadImage } from "@/utils/utils";
+import {
+  getDataUrlForSliceConfig,
+  getDataUrlForSplitConfig,
+  loadImage,
+} from "@/utils/utils";
 import { groupBy, sortBy } from "lodash";
 // @ts-ignore
 import CustomCursor from "custom-cursor.js";
@@ -110,10 +114,6 @@ export default defineComponent({
 
       if (!markResult || !store.currentTask) return;
 
-      // TODO: 图片加载出错,自动加载下一个任务
-      // for (const url of store.currentTask.sliceUrls) {
-      //   await loadImage(filters.toCompleteUrl(url));
-      // }
       // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
       for (const sliceConfig of store.currentTask.sliceConfig) {
         const url = filters.toCompleteUrl(
@@ -143,36 +143,18 @@ export default defineComponent({
         );
         const image = await loadImage(url);
 
-        const canvas = document.createElement("canvas");
-        // canvas.width = sliceConfig.w;
-        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(
+        const dataUrl = getDataUrlForSliceConfig(
           image,
-          sliceConfig.x,
-          sliceConfig.y,
-          sliceConfig.w,
-          sliceConfig.h,
-          0,
-          0,
-          sliceConfig.w,
-          sliceConfig.h
+          sliceConfig,
+          maxSliceWidth,
+          url
         );
-        // console.log(image, canvas.height, sliceConfig, ctx);
-        // console.log(canvas.toDataURL());
+
         const thisImageTrackList = markResult.trackList.filter(
           (v) => v.offsetIndex === sliceConfig.i
         );
-
-        const dataUrl = canvas.toDataURL();
         const sliceImage = new Image();
         sliceImage.src = dataUrl;
-        // sliceConfig.x + sliceConfig.w
         sliceImagesWithTrackList.push({
           url: dataUrl,
           indexInSliceUrls: sliceConfig.i,
@@ -228,28 +210,12 @@ export default defineComponent({
 
           accumBottomHeight += image.naturalHeight;
 
-          const width = image.naturalWidth * (config[1] - config[0]);
-          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(
+          const dataUrl = getDataUrlForSplitConfig(
             image,
-            image.naturalWidth * config[0],
-            0,
-            image.naturalWidth * config[1],
-            image.naturalHeight,
-            0,
-            0,
-            image.naturalWidth * config[1],
-            image.naturalHeight
+            config,
+            maxSliceWidth,
+            url
           );
-          // console.log(image, canvas.height, sliceConfig, ctx);
-          // console.log(canvas.toDataURL());
 
           const thisImageTrackList = markResult.trackList.filter(
             (t) =>
@@ -257,12 +223,10 @@ export default defineComponent({
               (store.currentTask &&
                 store.currentTask.sliceUrls.indexOf(url) + 1)
           );
-
-          const dataUrl = canvas.toDataURL();
           const sliceImage = new Image();
           sliceImage.src = dataUrl;
           sliceImagesWithTrackList.push({
-            url: canvas.toDataURL(),
+            url: dataUrl,
             indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
             trackList: thisImageTrackList.filter(
               (t) =>
@@ -300,11 +264,6 @@ export default defineComponent({
         __lock = false;
         return;
       }
-      // console.log(markResult.trackList.length);
-      // if (markResult.trackList.length !== trackLen) {
-      //   sliceImagesWithTrackList.splice(0);
-      //   trackLen = markResult.trackList.length;
-      // }
 
       // reset sliceImagesWithTrackList ,当切换任务时,要重新绘制图片和轨迹
       // if (_studentId !== store.currentTask.studentId) {
@@ -323,6 +282,7 @@ export default defineComponent({
       } catch (error) {
         sliceImagesWithTrackList.splice(0);
         console.log("render error ", error);
+        // 图片加载出错,自动加载下一个任务
         emit("error");
       } finally {
         __lock = false;

+ 1 - 1
src/types/index.ts

@@ -131,7 +131,7 @@ interface SpecialTag {
   tagName: string; // 特殊标记的字符串,勾叉
 }
 
-interface PictureSlice {
+export interface PictureSlice {
   i: number;
   w: number;
   h: number;

+ 136 - 1
src/utils/utils.ts

@@ -1,14 +1,149 @@
+import { store } from "@/features/mark/store";
+import { PictureSlice } from "@/types";
+
+// 把store.currentTask当做 weakRef ,当它不存在时,就丢弃它所有的图片
+const weakedMapImages = new WeakMap<Object, Map<string, HTMLImageElement>>();
+
 /**
  * 异步获取图片
  * @param url 完整的图片路径
  * @returns Promise<HTMLImageElement>
  */
 export async function loadImage(url: string): Promise<HTMLImageElement> {
+  if (store.currentTask && weakedMapImages.get(store.currentTask)) {
+    const imagesCache = weakedMapImages.get(store.currentTask);
+    if (imagesCache) {
+      // console.log("cached image");
+      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 = () => resolve(image);
+    image.onload = () => {
+      if (store.currentTask) {
+        let imagesCache = weakedMapImages.get(store.currentTask);
+        if (!imagesCache) {
+          imagesCache = new Map<string, HTMLImageElement>();
+          weakedMapImages.set(store.currentTask, imagesCache);
+        }
+        imagesCache.set(url, image);
+      }
+      resolve(image);
+    };
     image.onerror = reject;
   });
 }
+
+// 存放当前task的切片图的dataur
+const weakedMapDataUrls = new WeakMap<Object, Map<string, string>>();
+export 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 (store.currentTask && weakedMapDataUrls.get(store.currentTask)) {
+    const dataUrlsCache = weakedMapDataUrls.get(store.currentTask);
+    if (dataUrlsCache) {
+      // console.log("cached canvas");
+      const image = dataUrlsCache.get(key);
+      if (image) return image;
+    }
+  }
+
+  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();
+  if (store.currentTask) {
+    let dataUrlsCache = weakedMapDataUrls.get(store.currentTask);
+    if (!dataUrlsCache) {
+      dataUrlsCache = new Map<string, string>();
+      weakedMapDataUrls.set(store.currentTask, dataUrlsCache);
+    }
+    dataUrlsCache.set(key, dataurl);
+  }
+
+  return dataurl;
+}
+
+export function getDataUrlForSplitConfig(
+  image: HTMLImageElement,
+  config: [number, number],
+  maxSliceWidth: number,
+  urlForCache: string
+) {
+  const [start, end] = config;
+  const key = `${urlForCache}-${start}-${end}`;
+  if (store.currentTask && weakedMapDataUrls.get(store.currentTask)) {
+    const dataUrlsCache = weakedMapDataUrls.get(store.currentTask);
+    if (dataUrlsCache) {
+      // console.log("cached canvas");
+      const image = dataUrlsCache.get(key);
+      if (image) return image;
+    }
+  }
+
+  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();
+  if (store.currentTask) {
+    let dataUrlsCache = weakedMapDataUrls.get(store.currentTask);
+    if (!dataUrlsCache) {
+      dataUrlsCache = new Map<string, string>();
+      weakedMapDataUrls.set(store.currentTask, dataUrlsCache);
+    }
+    dataUrlsCache.set(key, dataurl);
+  }
+
+  return dataurl;
+}