From 40f8acb623bf5e779c77077b020e8d33fcebe796 Mon Sep 17 00:00:00 2001 From: Jz0ojiang Date: Sat, 5 Sep 2026 18:15:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=B6=85=E8=BF=87=E7=BA=B9=E7=90=86?= =?UTF-8?q?=E4=B8=8A=E9=99=90=E7=9A=84=E5=9B=BE=E5=8A=A0=E6=BB=A4=E9=95=9C?= =?UTF-8?q?=E5=90=8E=E7=BC=BA=E5=8F=B3=E8=BE=B9=E5=92=8C=E4=B8=8B=E8=BE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fabric 的 WebGL 滤镜后端建的是一张 config.textureSize 见方(v7 默认 4096)的 GL 画布,copyGLTo2D 却按**图片自然尺寸**去拷源矩形。图片比 4096 大时源矩形超出 GL 画布边界,超出的部分画不出来。 实测:6000x3150 的图加亮度滤镜后只剩左边 683/1078 = 0.683 的宽度, 正好是 4096/6000 = 0.6827。 调大 textureSize 不是办法——GL 画布是正方形且一次性分配,8192 就是 268 MB, 本机 GPU 上限 16384 是 1 GB,而且更大的图照样溢出。改成导入时把长边压到 运行时的 config.textureSize:这个工具导出 1200x630 量级的封面,4096 的长边 远超需要,成品里看不出差别,还顺带省内存和导出耗时。 --- src/editor/imageLimits.ts | 61 +++++++++++++++++++++++++++++++++++++++ src/editor/useEditor.ts | 6 ++++ 2 files changed, 67 insertions(+) create mode 100644 src/editor/imageLimits.ts diff --git a/src/editor/imageLimits.ts b/src/editor/imageLimits.ts new file mode 100644 index 0000000..7c969a2 --- /dev/null +++ b/src/editor/imageLimits.ts @@ -0,0 +1,61 @@ +import { config } from "fabric"; + +/** + * 把过大的位图缩到 fabric 滤镜后端处理得了的尺寸。 + * + * ## 为什么需要这个 + * + * fabric 的 WebGL 滤镜后端建的是一张 `config.textureSize × config.textureSize` + * 的 GL 画布(v7 默认 4096)。滤镜跑完之后 `copyGLTo2D` 是这么把结果拷回去的: + * + * ```js + * ctx.drawImage(glCanvas, 0, sourceY, targetCanvas.width, targetCanvas.height, …) + * ``` + * + * `targetCanvas` 的尺寸是**图片的自然尺寸**。图片比 4096 大的话,这个源矩形 + * 超出 GL 画布的边界,超出的部分**画不出来**——图片右边和下边直接缺一块。 + * + * 2026-09-05 实测:一张 6000×3150 的图加上「亮度」滤镜后,只剩左边 + * 683/1078 = 0.683 的宽度,正好是 4096/6000 = 0.6827。缺的就是超出纹理的那截。 + * + * ## 为什么是缩图而不是调大 textureSize + * + * GL 画布是**正方形**且按 tileSize 一次性分配。调到 8192 就是 8192²×4 = 268 MB, + * 调到本机 GPU 上限 16384 是 1 GB,而且再大的图照样会溢出。 + * 这个工具导出的是 1200×630 这个量级的封面,4096 的长边已经远超需要, + * 缩下来在成品里看不出任何差别,还顺带省了内存和导出耗时。 + * + * ## 边界 + * + * `config.textureSize` 不是常量:GPU 的 `MAX_TEXTURE_SIZE` 小于它时 fabric 会 + * 退回 Canvas2D 后端(那条路径没有这个限制)。所以这里读运行时的值,不写死 4096。 + */ +export function imageSizeLimit(): number { + return config.textureSize; +} + +/** + * 需要缩就返回一张缩好的 canvas,不需要就原样返回。 + * + * 用 `HTMLCanvasElement` 而不是再造一个 ``:fabric 的 `_originalElement` + * 接受任何 `CanvasImageSource`,而 canvas 是同步就绪的,不用再等一次 onload。 + */ +export function capImageSize( + el: HTMLImageElement | HTMLCanvasElement, +): HTMLImageElement | HTMLCanvasElement { + const w = el instanceof HTMLImageElement ? el.naturalWidth || el.width : el.width; + const h = el instanceof HTMLImageElement ? el.naturalHeight || el.height : el.height; + const limit = imageSizeLimit(); + if (w <= limit && h <= limit) return el; + + const scale = limit / Math.max(w, h); + const out = document.createElement("canvas"); + out.width = Math.max(1, Math.round(w * scale)); + out.height = Math.max(1, Math.round(h * scale)); + const ctx = out.getContext("2d"); + if (!ctx) return el; + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = "high"; + ctx.drawImage(el, 0, 0, out.width, out.height); + return out; +} diff --git a/src/editor/useEditor.ts b/src/editor/useEditor.ts index ff785bb..919a378 100644 --- a/src/editor/useEditor.ts +++ b/src/editor/useEditor.ts @@ -13,6 +13,7 @@ import { import { ref, shallowRef } from "vue"; import type { CanvasSize, LayerKind, SelectionKind, ShapeKind, ToolId } from "@/types"; import { DEFAULT_SIZE } from "./canvasSize"; +import { capImageSize } from "./imageLimits"; /** * 编辑器核心:一个模块级单例。 @@ -250,11 +251,16 @@ export function applyLocked(obj: FabricObject, locked: boolean) { /** * 装一张图进画布并铺满(object-fit: cover 的等效)。 * 背景性质的图沉到最底层,否则会盖住先加进来的文字。 + * + * 进来先过一道 `capImageSize`:超过 fabric 纹理上限的图加了滤镜会缺右边和下边, + * 原因和取舍见 `imageLimits.ts`。 */ async function addImage(src: string, opts: { toBack?: boolean } = {}): Promise { const c = canvas.value; if (!c) throw new Error("画布还没挂载"); const img = await FabricImage.fromURL(src, { crossOrigin: "anonymous" }); + const capped = capImageSize(img.getElement() as HTMLImageElement); + if (capped !== img.getElement()) img.setElement(capped); const scale = Math.max(size.value.width / img.width, size.value.height / img.height); img.set({ scaleX: scale,