当然可以。思路是:assetPool 里优先存 ImageBitmap(用于预览渲染更快),同时保留原始 Blob(用于导出/再次像素处理)
然后 store 里仍然只存 key。

下面给你一套“能直接用”的示范(纯内存,不用 IndexedDB)。


1) 内存资产池:同时存 Blob + ImageBitmap

// assetPool.ts
export type AssetRecord = {
  blob: Blob;
  bitmap: ImageBitmap; // 用于快速渲染
  width: number;
  height: number;
};

const g = globalThis as any;
const pool: Map<string, AssetRecord> =
  g.__imgAssetPool ?? (g.__imgAssetPool = new Map());

export function putAsset(blob: Blob): Promise<string> {
  return new Promise(function (resolve, reject) {
    (async function () {
      try {
        const bitmap = await createImageBitmap(blob);
        const key = crypto.randomUUID();
        pool.set(key, {
          blob: blob,
          bitmap: bitmap,
          width: bitmap.width,
          height: bitmap.height,
        });
        resolve(key);
      } catch (err) {
        reject(err);
      }
    })();
  });
}

export function getAsset(key: string): AssetRecord | undefined {
  return pool.get(key);
}

export function deleteAsset(key: string): void {
  const rec = pool.get(key);
  if (rec) {
    // ImageBitmap 需要主动释放
    rec.bitmap.close();
  }
  pool.delete(key);
}

export function keys(): string[] {
  return Array.from(pool.keys());
}

✅ 重点:

  • createImageBitmap(blob):异步解码,通常比 <img> 解码更可控
  • bitmap.close():释放显存/内存(很重要,不然会涨)

2) React 中:用 key 拿到 ImageBitmap(用于画布/预览)

// useAssetBitmap.ts
import { useMemo } from "react";
import { getAsset } from "./assetPool";

export function useAssetBitmap(assetKey: string | null): ImageBitmap | null {
  return useMemo(function () {
    if (!assetKey) return null;
    const rec = getAsset(assetKey);
    if (!rec) return null;
    return rec.bitmap;
  }, [assetKey]);
}

这里不用 state / effect,是因为 bitmap 已经在 pool 里准备好了。


3) 在 Canvas2D 里画 ImageBitmap(最直接)

import React, { useEffect, useRef } from "react";
import { useAssetBitmap } from "./useAssetBitmap";

export function BitmapCanvas(props: { assetKey: string | null }) {
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const bitmap = useAssetBitmap(props.assetKey);

  useEffect(function () {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    if (!ctx) return;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    if (!bitmap) return;

    // 示例:把图按 contain 画进 800x600 画布
    const cw = canvas.width;
    const ch = canvas.height;
    const scale = Math.min(cw / bitmap.width, ch / bitmap.height);
    const dw = bitmap.width * scale;
    const dh = bitmap.height * scale;
    const dx = (cw - dw) / 2;
    const dy = (ch - dh) / 2;

    ctx.drawImage(bitmap, dx, dy, dw, dh);
  }, [bitmap]);

  return <canvas ref={canvasRef} width={800} height={600} />;
}

✅ 这就是 ImageBitmap 的爽点:ctx.drawImage(bitmap, ...) 直接画,解码成本已经付过了。


4) 在 Konva 里用 ImageBitmap(你会用得到)

Konva 的 Image 节点的 image 属性可以接收 CanvasImageSource,而 ImageBitmap 就是其中一种。你可以直接传它(前提是版本/浏览器支持没问题)。

示例(思路级):

import { Image as KonvaImage } from "react-konva";
import { useAssetBitmap } from "./useAssetBitmap";

function RasterLayer(props: { assetKey: string | null; x: number; y: number; w: number; h: number }) {
  const bitmap = useAssetBitmap(props.assetKey);
  if (!bitmap) return null;
  return (
    <KonvaImage
      image={bitmap as any}
      x={props.x}
      y={props.y}
      width={props.w}
      height={props.h}
      listening={false}
    />
  );
}

如果你遇到某些浏览器/konva 版本对 ImageBitmap 支持不稳定,就退回到 HTMLImageElement + objectURL 的方式,但大多数现代环境是 OK 的。


5) 你做像素处理时:继续用 Blob(更通用)

虽然预览用 bitmap,但处理一般建议从 blob 来,因为:

  • 你可能要 worker / offscreen
  • 你可能要重新编码输出
  • blob 更易传输与存储

拿 blob:

import { getAsset } from "./assetPool";

export function getBlob(assetKey: string): Blob | null {
  const rec = getAsset(assetKey);
  return rec ? rec.blob : null;
}

6) 内存回收(必须做)

当你撤销/新提交时,删掉不用的 key:

import { deleteAsset, keys } from "./assetPool";

export function gcAssets(keepKeys: Set<string>): void {
  const all = keys();
  for (const k of all) {
    if (!keepKeys.has(k)) {
      deleteAsset(k); // 会 close bitmap
    }
  }
}

你可以怎么用在你的 store 流程里

  • putAsset(blob) -> key(异步)
  • store 里 currentKey = key
  • 预览组件用 useAssetBitmap(currentKey) 快速显示
  • commit 产出新 blob,再 putAsset 存进去,history 只存 key

如果你愿意,我还能顺手补一段:commit 时如何把 canvas 结果编码成 webp blob(带质量参数),以及“避免 UI 卡顿”的 worker/offscreen 版本。