繁体   English   中英

如何在 React 中优化 canvas 绘图的撤消/重做

[英]How to optimize undo/redo for canvas drawing in react

我正在为响应中的医学 (.nii) 图像上的html-canvas 绘图实现撤消/重做功能( 使用此挂钩)。 这些图像是一系列图像,表示存储在 Uint8ClampedArray 中的切片。 该阵列通常约为 500(列)x 500(行)x 250(切片),换句话说,一个相当大的阵列。

我当前的解决方案只是在 mouseup 事件上从当前数组创建一个新的 Uint8ClampedArray,并将其添加到撤消/重做数组。 但是,这很慢,并且会在 mouseup 事件上产生明显的问题。 我正在考虑实施更复杂的撤消/重做,它只保存受影响的体素,而不是鼠标松开时的整个阵列,但在我超越自己之前,我想知道是否有更简单的方法来优化当前的解决方案?

这是我当前的代码:

// State that stores the array of voxels for the image series.
// This updates on every brush stroke
const canvasRef = useRef(undefined);
const initialArray = canvasRef?.current?.getContext("2d")?.getImageData(canvas.width, canvas.height);
const [currentArray, setCurrentArray] = useState<Uint8ClampedArray | undefined>(initialArray);

// undo & redo states
const {
  state,
  setState,
  resetState,
  index,
  lastIndex,
  goBack,
  goForward,
} = useUndoableState();

// Update currentArray on index change (undo/redo and draw)
useEffect(() => {
  setCurrentArray(state);
}, [index]);

// Activates on mouse movement combined with left-click on canvas
function handleDrawing(){
    // Logic for drawing onto the canvas
    // ...

    // Adds the stroke from the canvas onto the corresponding slice in the array-state
    const newArray = addCanvasStrokeToArrayState(imageData, slice);
    setCurrentArray(newArray);
}

function handleMouseUp() {
   // This causes a hiccup every time the current state of the array is saved to the undoable array
   setState(Uint8ClampedArray.from(currentArray));
}

这是撤消/重做挂钩的代码:

export default function useUndoableState(init?: TypedArray | undefined) {
  const historySize = 10; // How many states to store at max
  const [states, setStates] = useState([init]); // Used to store history of all states
  const [index, setIndex] = useState<number>(0); // Index of current state within `states`
  const state = useMemo(() => states[index], [states, index]); // Current state

  const setState = (value: TypedArray) => {
    // remove oldest state if history size is exceeded
    let startIndex = 0;
    if (states.length >= historySize) {
      startIndex = 1;
    }

    const copy = states.slice(startIndex, index + 1); // This removes all future (redo) states after current index
    copy.push(value);
    setStates(copy);
    setIndex(copy.length - 1);
  };
  // Clear all state history
  const resetState = (init: TypedArray) => {
    setIndex(0);
    setStates([init]);
  };
  // Allows you to go back (undo) N steps
  const goBack = (steps = 1) => {
    setIndex(Math.max(0, index - steps));
  };
  // Allows you to go forward (redo) N steps
  const goForward = (steps = 1) => {
    setIndex(Math.min(states.length - 1, index + steps));
  };
  return {
    state,
    setState,
    resetState,
    index,
    lastIndex: states.length - 1,
    goBack,
    goForward,
  };
}

以下是三种撤消方法及其性能。

首先,这个小提琴包含一个基线,通过调用绘制 function 10,000 次,在我的计算机上提供 0.0018 毫秒的平均绘制时间。

这个小提琴既调用绘制 function 并将调用记录存储在历史数组中,在我的计算机上绘制和存储 function 调用的平均时间为 0.002 毫秒,这非常接近基线时间。

history.push({function: drawFunction, parameters: [i]});
drawFunction(i);

然后可以将历史重播到某个点。 在我的计算机上,重放 10,000 个历史记录项目需要 400 毫秒,但这会因执行的操作数量而异。

for (let i = 0; i < history.length - 1; i++) {
  history[i].function(...history[i].parameters);
}

这个小提琴在每次调用 draw function 之前存储 ImageData 对象,存储 ImageData 和调用 draw function 的平均时间为 7 ms,比仅调用 draw function 慢大约 3,800 倍。

history.push(context.getImageData(0, 0, 500, 500));
if (history.length > historyLimit) {
  history.splice(0, 1);
}

将存储的图像数据提取回 canvas 只需要上面的时间大约 0.002 ms,这比重新运行一切都快。

最后,这个小提琴演示了在每次调用绘制 function 之前使用 createPattern 将 canvas 的当前 state 存储为模式,在我的计算机上存储模式和绘制的平均时间为 0.3 毫秒,比基线慢 167 但是比使用 getImageData 快 23 倍。

history.push(context.createPattern(canvas, 'no-repeat'));

然后可以像这样拉回canvas,在我的电脑上速度太快了,无法测量:

context.fillStyle = history[history.length - 1];
context.fillRect(0, 0, 500, 500);

尽管 createPattern 相当快,但它确实有 memory 的开销。 该示例执行 20 次运行,每次运行调用 1,000 次,后面的运行时间可能是前面运行的两倍。

最佳方法可能是结合模式和 function 存储方法,偶尔存储模式以允许截断 function 调用列表。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM