.2869047221311500:d1605857934ecebd3b1e7bba0941d126_69e614bbf51efcfb0ecaa3bf.69e619e0f51efcfb0ecaa3c3.69e619e0d90ca3ecca53d178:Trae CN.T(2026/4/20 20:19:44)#372
Conversation
实现参考图片功能,包括加载、显示/隐藏、调整透明度和移除参考图片 添加相关CSS样式和菜单选项 创建ReferenceImage类处理参考图片的显示和交互
There was a problem hiding this comment.
Pull request overview
该 PR 为应用加入“参考图片”功能,用于在画布上加载并显示一张可调整透明度、可显示/隐藏、可移除且可拖拽/缩放的覆盖图片,以便临摹/对照绘制。
Changes:
- 新增 ReferenceImage 画布对象类,处理参考图片的显示、拖拽与缩放交互
- 在 View 菜单中加入“Reference Image”子菜单(加载/显示隐藏/透明度/移除)
- 增加参考图片相关 CSS(层级、渲染与指针事件)
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| styles/layout.css | 添加 .reference-image 样式(层级、canvas 渲染、指针事件) |
| styles/layout.rtl.css | RTL 版本对应样式更新 |
| src/menus.js | View 菜单新增 Reference Image 子菜单入口 |
| src/functions.js | 增加参考图片加载/显示隐藏/透明度/移除等函数并导出 |
| src/ReferenceImage.js | 新增 ReferenceImage 类(OnCanvasObject + Handles + 拖拽手柄) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .reference-image { | ||
| z-index: 2.5; | ||
| display: block !important; | ||
| box-sizing: border-box; | ||
| -moz-box-sizing: border-box; | ||
| pointer-events: none; | ||
| } |
There was a problem hiding this comment.
.reference-image sets display: block !important;, which will override jQuery .hide()/.show() (inline display: none without !important). As a result, ReferenceImage.hide()/toggle() may not actually hide the element. Remove the !important (or use visibility/opacity for toggling) so runtime show/hide works.
| z-index: 3; | ||
| } | ||
| .reference-image { | ||
| z-index: 2.5; |
There was a problem hiding this comment.
z-index only accepts integers; z-index: 2.5; is invalid and will be ignored by the browser, which can break layering (e.g., reference image may appear above selections/handles depending on DOM order). Use an integer z-index that fits the existing stacking scheme.
| z-index: 2.5; | |
| z-index: 2; |
| if (reference_image) { | ||
| reference_image.destroy(); | ||
| } | ||
|
|
There was a problem hiding this comment.
reference_image is used as a writable global, but there’s no definition/initialization for it anywhere else in the repo (search only finds it in this PR). In an ES module this will throw a ReferenceError the first time load_reference_image() evaluates if (reference_image). Define it (e.g., module-scoped let reference_image = null; in this file, or initialize window.reference_image with a corresponding global binding at startup).
| const { file } = await systemHooks.showOpenFileDialog({ formats: image_formats }); | ||
| if (file.type.match(/^image|application\/pdf/)) { | ||
| read_image_file(file, (error, info) => { |
There was a problem hiding this comment.
systemHooks.showOpenFileDialog can throw on user cancel (e.g., Electron implementation throws "user canceled", and showOpenFilePicker rejects with an AbortError). load_reference_image() doesn’t handle this, so canceling can surface as an unhandled rejection / internal error. Wrap the dialog call in try/catch and return early on cancel (and/or guard file before reading file.type).
| const rect = this.canvas.getBoundingClientRect(); | ||
| const cx = e.clientX - rect.left; | ||
| const cy = e.clientY - rect.top; | ||
| mox = ~~(cx / rect.width * this.canvas.width); | ||
| moy = ~~(cy / rect.height * this.canvas.height); |
There was a problem hiding this comment.
The move-handle drag offset is computed from this.canvas.getBoundingClientRect(), but the handle is positioned outside the canvas bounds (negative left). Clicking the handle will produce negative cx/cy, causing mox/moy to be wrong and the reference image to jump when dragging starts. Compute the offset from the object position (e.g., using to_canvas_coords(e) relative to this.x/this.y) instead of using the canvas rect.
| const rect = this.canvas.getBoundingClientRect(); | |
| const cx = e.clientX - rect.left; | |
| const cy = e.clientY - rect.top; | |
| mox = ~~(cx / rect.width * this.canvas.width); | |
| moy = ~~(cy / rect.height * this.canvas.height); | |
| const m = to_canvas_coords(e); | |
| mox = m.x - this.x; | |
| moy = m.y - this.y; |
| height = Math.max(1, height); | ||
|
|
||
| const new_canvas = make_canvas(width, height); | ||
| new_canvas.ctx.imageSmoothingEnabled = true; |
There was a problem hiding this comment.
_resize() sets new_canvas.ctx.imageSmoothingEnabled = true, which conflicts with the CSS image-rendering: pixelated intent and with make_canvas() defaulting to smoothing disabled. Resizing the reference image will likely blur it. Use disable_image_smoothing() (or set imageSmoothingEnabled = false) when scaling if you want crisp/pixelated results.
| new_canvas.ctx.imageSmoothingEnabled = true; | |
| new_canvas.ctx.imageSmoothingEnabled = false; |
| _update_canvas_opacity() { | ||
| this.canvas.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); | ||
| this.canvas.ctx.globalAlpha = this._opacity; | ||
| this.canvas.ctx.drawImage(this._original_canvas, 0, 0, this.width, this.height); | ||
| this.canvas.ctx.globalAlpha = 1; | ||
| } | ||
|
|
||
| /** | ||
| * @param {number} opacity - 0 to 1 | ||
| */ | ||
| set_opacity(opacity) { | ||
| this._opacity = Math.max(0, Math.min(1, opacity)); | ||
| this._update_canvas_opacity(); | ||
| } |
There was a problem hiding this comment.
set_opacity() redraws the entire reference image bitmap on every slider input event via _update_canvas_opacity(). For large images this can cause noticeable CPU usage/lag while dragging the slider. Consider applying opacity via CSS on the canvas/element (and only redrawing on resize) to make opacity changes cheap.
实现参考图片功能,包括加载、显示/隐藏、调整透明度和移除参考图片
添加相关CSS样式和菜单选项
创建ReferenceImage类处理参考图片的显示和交互