|
@@ -1,6 +1,3 @@
|
|
|
-import { store } from "@/store/app";
|
|
|
-import { PictureSlice, Task } from "@/types";
|
|
|
-
|
|
|
// 打开cache后,会造成没有 vue devtools 时,canvas缓存错误,暂时不知道原因
|
|
|
// 通过回看的测试,打开回看,再关闭回看,稍等一会儿再打开回看,确实可以看到该缓存时缓存了,该丢弃时丢弃了
|
|
|
|
|
@@ -26,378 +23,6 @@ export async function loadImage(url: string): Promise<HTMLImageElement> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-// 存放裁切图的ObjectUrls
|
|
|
-let objectUrlMap = new Map<string, string>();
|
|
|
-const OBJECT_URLS_MAP_MAX_SIZE =
|
|
|
- window.APP_OPTIONS?.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');
|
|
|
- throw new Error("canvas ctx 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: Blob = await new Promise((res) => {
|
|
|
- canvas.toBlob((b) => res(b));
|
|
|
- });
|
|
|
- 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);
|
|
|
- // 为了避免部分图片还没显示就被revoke了,这里做一个延迟revoke
|
|
|
- // 此处有个瑕疵,缩略图的显示与试卷不是同时显示,是有可能被清除了的,只能让用户刷新了。 => 见下面的fix
|
|
|
- for (const u of toRelease) {
|
|
|
- // 如果当前图片仍在引用 objectUrl , 则将其放入缓存中
|
|
|
- if (document.querySelector(`img[src="${u[1]}"]`)) {
|
|
|
- ary.push(u);
|
|
|
- } else {
|
|
|
- // console.log("revoke ", u[1]);
|
|
|
- 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');
|
|
|
- throw new Error("canvas ctx 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: Blob = await new Promise((res) => {
|
|
|
- canvas.toBlob((b) => res(b));
|
|
|
- });
|
|
|
- const dataurl = URL.createObjectURL(blob);
|
|
|
- cacheFIFO();
|
|
|
-
|
|
|
- objectUrlMap.set(key, dataurl);
|
|
|
- return dataurl;
|
|
|
-}
|
|
|
-
|
|
|
-export async function getDataUrlForCoverConfig(
|
|
|
- image: HTMLImageElement,
|
|
|
- configs: PictureSlice[]
|
|
|
-) {
|
|
|
- const key = `${image.src}-slice`;
|
|
|
- if (objectUrlMap.get(key)) {
|
|
|
- return objectUrlMap.get(key);
|
|
|
- }
|
|
|
-
|
|
|
- const canvas = document.createElement("canvas");
|
|
|
- canvas.width = image.naturalWidth;
|
|
|
- canvas.height = image.naturalHeight;
|
|
|
- const ctx = canvas.getContext("2d");
|
|
|
- if (!ctx) {
|
|
|
- console.log('canvas.getContext("2d") error');
|
|
|
- throw new Error("canvas ctx error");
|
|
|
- }
|
|
|
- ctx.drawImage(image, 0, 0);
|
|
|
- ctx.fillStyle = "#ffffff";
|
|
|
- configs.forEach((config) => {
|
|
|
- ctx.fillRect(config.x, config.y, config.w, config.h);
|
|
|
- });
|
|
|
-
|
|
|
- const blob: Blob = await new Promise((res) => {
|
|
|
- canvas.toBlob((b) => res(b));
|
|
|
- });
|
|
|
- const dataurl = URL.createObjectURL(blob);
|
|
|
- cacheFIFO();
|
|
|
- objectUrlMap.set(key, dataurl);
|
|
|
- return dataurl;
|
|
|
-}
|
|
|
-
|
|
|
-export async function preDrawImage(_currentTask: Task | undefined) {
|
|
|
- // console.log("preDrawImage=>curTask:", _currentTask);
|
|
|
-
|
|
|
- if (!_currentTask?.taskId) return;
|
|
|
-
|
|
|
- let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
|
|
|
-
|
|
|
- // const hasSliceConfig = store.currentTask?.sliceConfig?.length;
|
|
|
- const hasSliceConfig = _currentTask?.sliceConfig?.length;
|
|
|
-
|
|
|
- const images = [];
|
|
|
-
|
|
|
- if (hasSliceConfig) {
|
|
|
- // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
|
|
|
- const sliceNum = _currentTask.sliceUrls.length;
|
|
|
- if (_currentTask.sliceConfig.some((v) => v.i > sliceNum)) {
|
|
|
- console.warn("裁切图设置的数量小于该学生的总图片数量");
|
|
|
- }
|
|
|
- _currentTask.sliceConfig = _currentTask.sliceConfig.filter(
|
|
|
- (v) => v.i <= sliceNum
|
|
|
- );
|
|
|
- for (const sliceConfig of _currentTask.sliceConfig) {
|
|
|
- const url = _currentTask.sliceUrls[sliceConfig.i - 1];
|
|
|
- const image = await loadImage(url);
|
|
|
- images[sliceConfig.i] = image;
|
|
|
- const { x, y, w, h } = sliceConfig;
|
|
|
- x < 0 && (sliceConfig.x = 0);
|
|
|
- y < 0 && (sliceConfig.y = 0);
|
|
|
- if (sliceConfig.w === 0 && sliceConfig.h === 0) {
|
|
|
- // 选择整图时,w/h 为0
|
|
|
- sliceConfig.w = image.naturalWidth;
|
|
|
- sliceConfig.h = image.naturalHeight;
|
|
|
- }
|
|
|
- if (x <= 1 && y <= 1 && sliceConfig.w <= 1 && sliceConfig.h <= 1) {
|
|
|
- sliceConfig.x = image.naturalWidth * x;
|
|
|
- sliceConfig.y = image.naturalHeight * y;
|
|
|
- sliceConfig.w = image.naturalWidth * w;
|
|
|
- sliceConfig.h = image.naturalHeight * h;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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];
|
|
|
-
|
|
|
- try {
|
|
|
- await getDataUrlForSliceConfig(image, sliceConfig, maxSliceWidth, url);
|
|
|
- } catch (error) {
|
|
|
- console.log("preDrawImage failed: ", error);
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- for (const url of _currentTask.sliceUrls) {
|
|
|
- const image = await loadImage(url);
|
|
|
- images.push(image);
|
|
|
- }
|
|
|
-
|
|
|
- const splitConfigPairs = store.setting.splitConfig.reduce<
|
|
|
- [number, number][]
|
|
|
- >((a, v, index) => {
|
|
|
- index % 2 === 0 ? a.push([v, -1]) : (a.at(-1)![1] = v);
|
|
|
- return a;
|
|
|
- }, []);
|
|
|
-
|
|
|
- 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];
|
|
|
-
|
|
|
- try {
|
|
|
- await getDataUrlForSplitConfig(image, config, maxSliceWidth, url);
|
|
|
- } catch (error) {
|
|
|
- console.log("preDrawImage failed: ", error);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export async function processSliceUrls(_currentTask: Task | undefined) {
|
|
|
- if (!_currentTask?.taskId) return;
|
|
|
-
|
|
|
- const getNum = (num) => Math.max(Math.min(1, num), 0);
|
|
|
-
|
|
|
- const sheetUrls = _currentTask.sheetUrls || [];
|
|
|
- const sheetConfig = (store.setting.sheetConfig || []).map((item) => {
|
|
|
- return { ...item };
|
|
|
- });
|
|
|
-
|
|
|
- const urls = [];
|
|
|
- for (let i = 0; i < sheetUrls.length; i++) {
|
|
|
- const url = sheetUrls[i];
|
|
|
- const configs = sheetConfig.filter((item) => item.i === i + 1);
|
|
|
- if (!configs.length) {
|
|
|
- urls[i] = url;
|
|
|
- continue;
|
|
|
- }
|
|
|
- const image = await loadImage(url);
|
|
|
- configs.forEach((item) => {
|
|
|
- item.x = image.naturalWidth * getNum(item.x);
|
|
|
- item.y = image.naturalHeight * getNum(item.y);
|
|
|
- item.w = image.naturalWidth * getNum(item.w);
|
|
|
- item.h = image.naturalHeight * getNum(item.h);
|
|
|
- });
|
|
|
-
|
|
|
- urls[i] = await getDataUrlForCoverConfig(image, configs);
|
|
|
- }
|
|
|
- return urls;
|
|
|
-}
|
|
|
-
|
|
|
-export async function preDrawImageHistory(_currentTask: Task | undefined) {
|
|
|
- console.log("preDrawImageHistory=>curTask:", _currentTask);
|
|
|
-
|
|
|
- if (!_currentTask?.taskId) return;
|
|
|
-
|
|
|
- let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
|
|
|
-
|
|
|
- // const hasSliceConfig = store.currentTask?.sliceConfig?.length;
|
|
|
- const hasSliceConfig = _currentTask?.sliceConfig?.length;
|
|
|
- // _currentTask.sheetUrls = ["/1-1.jpg", "/1-2.jpg"];
|
|
|
- _currentTask.sliceUrls = await processSliceUrls(_currentTask);
|
|
|
-
|
|
|
- const images = [];
|
|
|
-
|
|
|
- if (hasSliceConfig) {
|
|
|
- // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
|
|
|
- const sliceNum = _currentTask.sliceUrls.length;
|
|
|
- if (_currentTask.sliceConfig.some((v) => v.i > sliceNum)) {
|
|
|
- console.warn("裁切图设置的数量小于该学生的总图片数量");
|
|
|
- }
|
|
|
- _currentTask.sliceConfig = _currentTask.sliceConfig.filter(
|
|
|
- (v) => v.i <= sliceNum
|
|
|
- );
|
|
|
- for (const sliceConfig of _currentTask.sliceConfig) {
|
|
|
- const url = _currentTask.sliceUrls[sliceConfig.i - 1];
|
|
|
- const image = await loadImage(url);
|
|
|
- images[sliceConfig.i] = image;
|
|
|
- const { x, y, w, h } = sliceConfig;
|
|
|
- x < 0 && (sliceConfig.x = 0);
|
|
|
- y < 0 && (sliceConfig.y = 0);
|
|
|
- if (sliceConfig.w === 0 && sliceConfig.h === 0) {
|
|
|
- // 选择整图时,w/h 为0
|
|
|
- sliceConfig.w = image.naturalWidth;
|
|
|
- sliceConfig.h = image.naturalHeight;
|
|
|
- }
|
|
|
- if (x <= 1 && y <= 1 && sliceConfig.w <= 1 && sliceConfig.h <= 1) {
|
|
|
- sliceConfig.x = image.naturalWidth * x;
|
|
|
- sliceConfig.y = image.naturalHeight * y;
|
|
|
- sliceConfig.w = image.naturalWidth * w;
|
|
|
- sliceConfig.h = image.naturalHeight * h;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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];
|
|
|
-
|
|
|
- try {
|
|
|
- await getDataUrlForSliceConfig(image, sliceConfig, maxSliceWidth, url);
|
|
|
- } catch (error) {
|
|
|
- console.log("preDrawImage failed: ", error);
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- for (const url of _currentTask.sliceUrls) {
|
|
|
- const image = await loadImage(url);
|
|
|
- images.push(image);
|
|
|
- }
|
|
|
-
|
|
|
- const splitConfigPairs = store.setting.splitConfig.reduce<
|
|
|
- [number, number][]
|
|
|
- >((a, v, index) => {
|
|
|
- index % 2 === 0 ? a.push([v, -1]) : (a.at(-1)![1] = v);
|
|
|
- return a;
|
|
|
- }, []);
|
|
|
-
|
|
|
- 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];
|
|
|
-
|
|
|
- try {
|
|
|
- await getDataUrlForSplitConfig(image, config, maxSliceWidth, url);
|
|
|
- } catch (error) {
|
|
|
- console.log("preDrawImage failed: ", error);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export function addFileServerPrefixToTask(rawTask: Task): Task {
|
|
|
- const newTask = JSON.parse(JSON.stringify(rawTask)) as Task;
|
|
|
- return newTask;
|
|
|
-}
|
|
|
-
|
|
|
-export function addHeaderTrackColorAttr(headerTrack: any): any {
|
|
|
- return headerTrack.map((item: any) => {
|
|
|
- item.color = "green";
|
|
|
- return item;
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* 获取随机code,默认获取16位
|
|
|
* @param {Number} len 推荐8的倍数
|