Browse Source

单任务复核,显示裁切图

Michael Wang 4 years ago
parent
commit
f0da499a95
3 changed files with 178 additions and 26 deletions
  1. 175 24
      src/features/library/inspect/MarkBody.vue
  2. 2 2
      src/features/mark/MarkBody.vue
  3. 1 0
      src/types/index.ts

+ 175 - 24
src/features/library/inspect/MarkBody.vue

@@ -20,6 +20,9 @@
             :track-list="item.trackList"
             :special-tag-list="item.tagList"
             :original-image="item.originalImage"
+            :slice-image="item.sliceImage"
+            :dx="item.dx"
+            :dy="item.dy"
           />
           <hr class="image-seperator" />
         </div>
@@ -35,7 +38,11 @@ import filters from "@/filters";
 import MarkDrawTrack from "./MarkDrawTrack.vue";
 import { SpecialTag, Track } from "@/types";
 import { useTimers } from "@/setups/useTimers";
-import { loadImage } from "@/utils/utils";
+import {
+  getDataUrlForSliceConfig,
+  getDataUrlForSplitConfig,
+  loadImage,
+} from "@/utils/utils";
 import { dragImage } from "@/features/mark/use/draggable";
 
 interface SliceImage {
@@ -44,6 +51,11 @@ interface SliceImage {
   trackList: Array<Track>;
   tagList: Array<SpecialTag>;
   originalImage: HTMLImageElement;
+  sliceImage: HTMLImageElement;
+  dx: number;
+  dy: number;
+  accumTopHeight: number;
+  effectiveWidth: number;
 }
 // should not render twice at the same time
 let __lock = false;
@@ -57,29 +69,74 @@ export default defineComponent({
 
     const { addTimeout } = useTimers();
 
+    function hasSliceConfig() {
+      return store.currentTask?.sliceConfig?.length;
+    }
+
     let rendering = ref(false);
     let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
+    let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
+    let theFinalHeight = 0; // 最终宽度,用来定位轨迹在第几张图片,不包括image-seperator高度
+
+    async function getImageUsingDataUrl(
+      dataUrl: string
+    ): Promise<HTMLImageElement> {
+      return new Promise((resolve) => {
+        const image = new Image();
+        image.src = dataUrl;
+        image.onload = function () {
+          resolve(image);
+        };
+      });
+    }
 
-    async function processImage() {
+    async function processSliceConfig() {
       if (!store.currentTask) return;
 
       const images = [];
-      for (const url of store.currentTask.sliceUrls) {
-        const image = await loadImage(
-          filters.toCompleteUrlWithFileServer(store.setting.fileServer, url)
+      const urls = [];
+      // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
+      for (const sliceConfig of store.currentTask.sliceConfig) {
+        const url = filters.toCompleteUrlWithFileServer(
+          store.setting.fileServer,
+          store.currentTask.sliceUrls[sliceConfig.i - 1]
         );
+        const image = await loadImage(url);
         images.push(image);
+        urls.push(url);
+        if (sliceConfig.w === 0 && sliceConfig.h === 0) {
+          // 选择整图时,w/h 为0
+          sliceConfig.w = image.naturalWidth;
+          sliceConfig.h = image.naturalHeight;
+        }
       }
 
-      for (const url of store.currentTask.sliceUrls) {
-        const completeUrl = filters.toCompleteUrlWithFileServer(
+      theFinalHeight = store.currentTask.sliceConfig
+        .map((v) => v.h)
+        .reduce((acc, v) => (acc += v));
+      maxSliceWidth = Math.max(
+        ...store.currentTask.sliceConfig.map((v) => v.w)
+      );
+
+      // 用来保存sliceImage在整个图片容器中(不包括image-seperator)的高度范围
+      let accumTopHeight = 0;
+      let accumBottomHeight = 0;
+      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
+      for (const sliceConfig of store.currentTask.sliceConfig) {
+        accumBottomHeight += sliceConfig.h;
+        const url = filters.toCompleteUrlWithFileServer(
           store.setting.fileServer,
-          url
+          store.currentTask.sliceUrls[sliceConfig.i - 1]
         );
-
-        const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
+        const indexInSliceUrls = sliceConfig.i;
         const image = images[indexInSliceUrls - 1];
 
+        const dataUrl = getDataUrlForSliceConfig(
+          image,
+          sliceConfig,
+          maxSliceWidth,
+          url
+        );
         const trackLists = store.currentTask.questionList
           .map((q) => q.trackList)
           .reduce((acc, t) => {
@@ -89,18 +146,114 @@ export default defineComponent({
         const thisImageTrackList = trackLists.filter(
           (t) => t.offsetIndex === indexInSliceUrls
         );
-        const thisImageTagList = store.currentTask.specialTagList?.filter(
-          (t) => t.offsetIndex === indexInSliceUrls
-        );
+        const thisImageTagList = (
+          store.currentTask.specialTagList ?? []
+        ).filter((t) => t.offsetIndex === indexInSliceUrls);
 
-        sliceImagesWithTrackList.push({
-          url: completeUrl,
+        const sliceImage = await getImageUsingDataUrl(dataUrl);
+        tempSliceImagesWithTrackList.push({
+          url: dataUrl,
           indexInSliceUrls,
-          trackList: thisImageTrackList,
-          tagList: thisImageTagList,
+          // 通过positionY来定位是第几张slice的还原,并过滤出相应的track
+          trackList: thisImageTrackList.filter(
+            (t) =>
+              t.positionY >= accumTopHeight / theFinalHeight &&
+              t.positionY < accumBottomHeight / theFinalHeight
+          ),
+          tagList: thisImageTagList.filter(
+            (t) =>
+              t.positionY >= accumTopHeight / theFinalHeight &&
+              t.positionY < accumBottomHeight / theFinalHeight
+          ),
           originalImage: image,
+          sliceImage,
+          dx: sliceConfig.x,
+          dy: sliceConfig.y,
+          accumTopHeight,
+          effectiveWidth: sliceConfig.w,
         });
+        accumTopHeight = accumBottomHeight;
+      }
+      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
+    }
+
+    async function processSplitConfig() {
+      if (!store.currentTask) return;
+
+      const images = [];
+      for (const url of store.currentTask.sliceUrls) {
+        const image = await loadImage(
+          filters.toCompleteUrlWithFileServer(store.setting.fileServer, 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;
+
+      theFinalHeight =
+        splitConfigPairs.length *
+        images.reduce((acc, v) => (acc += v.naturalHeight), 0);
+
+      let accumTopHeight = 0;
+      let accumBottomHeight = 0;
+      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
+      for (const url of store.currentTask.sliceUrls) {
+        for (const config of splitConfigPairs) {
+          const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
+          const image = images[indexInSliceUrls - 1];
+
+          accumBottomHeight += image.naturalHeight;
+
+          const dataUrl = getDataUrlForSplitConfig(
+            image,
+            config,
+            maxSliceWidth,
+            url
+          );
+
+          const trackLists = store.currentTask.questionList
+            .map((q) => q.trackList)
+            .reduce((acc, t) => {
+              acc = acc.concat(t);
+              return acc;
+            }, [] as Array<Track>);
+          const thisImageTrackList = trackLists.filter(
+            (t) => t.offsetIndex === indexInSliceUrls
+          );
+          const thisImageTagList = (
+            store.currentTask.specialTagList ?? []
+          ).filter((t) => t.offsetIndex === indexInSliceUrls);
+          const sliceImage = await getImageUsingDataUrl(dataUrl);
+          tempSliceImagesWithTrackList.push({
+            url: dataUrl,
+            indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
+            trackList: thisImageTrackList.filter(
+              (t) =>
+                t.positionY >= accumTopHeight / theFinalHeight &&
+                t.positionY < accumBottomHeight / theFinalHeight
+            ),
+            tagList: thisImageTagList.filter(
+              (t) =>
+                t.positionY >= accumTopHeight / theFinalHeight &&
+                t.positionY < accumBottomHeight / theFinalHeight
+            ),
+            originalImage: image,
+            sliceImage,
+            dx: image.naturalWidth * config[0],
+            dy: 0,
+            accumTopHeight,
+            effectiveWidth: image.naturalWidth * config[1],
+          });
+          accumTopHeight = accumBottomHeight;
+        }
+      }
+      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
     }
     const renderPaperAndMark = async () => {
       if (__lock) {
@@ -124,7 +277,11 @@ export default defineComponent({
 
       try {
         rendering.value = true;
-        await processImage();
+        if (hasSliceConfig()) {
+          await processSliceConfig();
+        } else {
+          await processSplitConfig();
+        }
       } catch (error) {
         sliceImagesWithTrackList.splice(0);
         console.log("render error ", error);
@@ -182,12 +339,6 @@ export default defineComponent({
   background-size: 8px 8px;
   background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
     linear-gradient(to bottom, transparent 4px, #e7e7e7 4px);
-
-  cursor: grab;
-  user-select: none;
-}
-.grabbing {
-  cursor: grabbing;
 }
 .mark-body-container img {
   width: 100%;

+ 2 - 2
src/features/mark/MarkBody.vue

@@ -222,9 +222,9 @@ export default defineComponent({
         images.push(image);
       }
 
-      const splitConfigPairs = (store.setting.splitConfig
+      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]>;
+        .filter((v) => v) as unknown as Array<[number, number]>;
 
       const maxSplitConfig = Math.max(...store.setting.splitConfig);
       maxSliceWidth =

+ 1 - 0
src/types/index.ts

@@ -184,6 +184,7 @@ export interface InspectStore {
       "answer.paper.scale": number;
       "score.board.collapse": boolean;
     };
+    splitConfig: Array<number>; //使用裁切整图时的裁切配置 [0,1]|[0,0.3,0.25,0.55],
   };
   status: {
     totalCount: number; //总数量