Compare commits

..
2 Commits
Author SHA1 Message Date
adachi 71baccdacd Release version 2.1.1 2026-11-12 22:45:29 +08:00
adachi 6ace9fc662 Streamline workbench controls 2026-11-12 22:35:30 +08:00
14 changed files with 24 additions and 17 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog
## 2.1.1 · 2026-11-12
- Remove the duplicate lower Fit button in the standalone workspace; the top Fit control and the workflow comparison node's button remain available.
- Move the whole-image and masked-area choices directly below Generate in the workspace.
- Place color-paint controls in the existing lower toolbar for both editing modes.
## 2.1.0 · 2026-11-12
- Add an independent color-paint layer to Mask and Crop & Blend modes. Alt-click samples the visible image color, Ctrl + wheel changes brush size, and the painted image is used for inpainting.
+1
View File
@@ -312,6 +312,7 @@ See [verification notes](VERIFICATION.md) for tested configurations and limits,
| Versions | Highlights and fixes |
| --- | --- |
| 2.1.1 | Streamlined comparison Fit controls, moved repaint-area choices below Generate, and combined color-paint controls with the lower toolbar. |
| 2.1.0 | Color-paint layer, crop merge progress, workspace comparison zoom, reordered controls, collapsible prompts and GPL-3.0-only licensing. |
| 2.0.1 | Weak-network retry and cancellation, preview resource cleanup, rollback races, dense-mask memory reduction and safer malformed-workflow handling. |
| 2.0.0 | Standalone light/dark workbench, workflow discovery, editable generation, task progress, result recovery and unified template. |
+1
View File
@@ -312,6 +312,7 @@ node --experimental-default-type=module tests/test_compare_history.mjs
| 版本 | 主要功能和修复 |
| --- | --- |
| 2.1.1 | 精简对比适应按钮、将重绘画布选项移至生成按钮下方,并把彩色画笔并入左下角工具栏。 |
| 2.1.0 | 彩色画笔图层、裁切合并进度、工作台对比缩放、常用参数上移、提示词折叠及 GPL-3.0-only 许可证。 |
| 2.0.1 | 弱网重试与取消、预览资源清理、回退竞态、大蒙版内存优化和异常工作流校验。 |
| 2.0.0 | 独立黑白工作台、工作流发现、生成中编辑、进度、结果恢复和统一模板。 |
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "adachi-inpaint-workbench"
version = "2.1.0"
version = "2.1.1"
description = "Inline mask painting, crop blending and a standalone inpainting workbench for ComfyUI"
requires-python = ">=3.10"
license = { file = "LICENSE" }
+2 -2
View File
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
class Element {
constructor(tag){this.tag=tag;this.style={};this.dataset={};this.classList={toggle(){}};this.children=[];this.width=0;this.height=0;this.value='';}
append(...children){this.children.push(...children)} setAttribute(){} hasPointerCapture(){return false} setPointerCapture(){}
append(...children){this.children.push(...children)} prepend(...children){this.children.unshift(...children)} setAttribute(){} hasPointerCapture(){return false} setPointerCapture(){}
getContext(){return {save(){},restore(){},beginPath(){},rect(){},clip(){},arc(){},fill(){},moveTo(){},lineTo(){},stroke(){},translate(){},clearRect(){},drawImage(){},getImageData(){return {data:[12,34,56,255]}}};}
toDataURL(){return 'data:image/png;base64,paint';}
}
@@ -10,7 +10,7 @@ globalThis.Image=class {constructor(){this.width=16;this.height=12;} async decod
const {ColorPaint}=await import('../web/color_paint.js');
const editor={image:{width:16,height:12},ready:true,view:'overlay',brushSize:8,brushOpacity:100,stage:new Element('div'),root:new Element('div'),canvas:new Element('canvas'),options:{drawSource:()=>false},point:e=>({x:e.x,y:e.y}),pointerDown(){},pointerMove(){},pointerUp(){},schedule(){},status(){}};
const crop={mode:'mask',bar:new Element('div'),region:{locked:false}},widget={value:''};let changes=0;
const paint=new ColorPaint(editor,crop,widget,()=>changes++);paint.button.onclick({preventDefault(){},stopPropagation(){}});
const paint=new ColorPaint(editor,crop,widget,()=>changes++);assert.equal(crop.bar.children[0],paint.controls);paint.button.onclick({preventDefault(){},stopPropagation(){}});
const event={button:0,pointerId:1,x:2,y:3,preventDefault(){},stopPropagation(){}};
editor.pointerDown(event);assert.equal(editor.pointerId,1);editor.pointerUp(event);assert.equal(editor.pointerId,null);assert.match(widget.value,/data:image\/png/);assert.equal(changes,1);
const saved=widget.value;editor.pointerDown({...event,altKey:true});assert.equal(paint.color,'#0c2238');assert.equal(widget.value,saved);
+2 -3
View File
@@ -12,8 +12,7 @@ export class ColorPaint {
this.picker=document.createElement('input');this.picker.type='color';this.picker.value=this.color;this.picker.title=T('Brush color');this.picker.setAttribute('aria-label',this.picker.title);this.picker.oninput=()=>{this.color=this.picker.value;};
this.undoButton=document.createElement('button');this.undoButton.type='button';this.undoButton.textContent='↶';this.undoButton.title=T('Undo color painting');this.undoButton.onclick=()=>this.step(this.undo,this.redo);
this.redoButton=document.createElement('button');this.redoButton.type='button';this.redoButton.textContent='↷';this.redoButton.title=T('Redo color painting');this.redoButton.onclick=()=>this.step(this.redo,this.undo);
controls.append(this.button,this.picker,this.undoButton,this.redoButton);editor.stage.append(controls);this.controls=controls;
this.barObserver=new ResizeObserver(()=>editor.root.style.setProperty('--dd-color-offset',`${Math.ceil(crop.bar.getBoundingClientRect().height)+14}px`));this.barObserver.observe(crop.bar);
controls.append(this.button,this.picker,this.undoButton,this.redoButton);crop.bar.prepend(controls);this.controls=controls;
const draw=editor.options.drawSource,revision=editor.options.sceneRevision;
editor.options.drawSource=ctx=>{const done=draw?.(ctx);if(!done)ctx.drawImage(editor.image,0,0);if(this.layer.width&&!crop.previewOriginal)ctx.drawImage(this.layer,0,0);return true;};
editor.options.sceneRevision=()=>`${revision?.()||''}:paint:${this.revision}`;
@@ -35,7 +34,7 @@ export class ColorPaint {
remember(){this.undo.push(this.layer.toDataURL('image/png'));let bytes=this.undo.reduce((n,s)=>n+s.length*2,0);while(this.undo.length>1&&(this.undo.length>20||bytes>64*1024*1024))bytes-=this.undo.shift().length*2;}
async step(from,to){if(this.drawing||this.restoring||!from.length)return;this.restoring=true;const current=this.layer.toDataURL('image/png'),data=from.pop(),image=new Image();image.src=data;try{await image.decode();to.push(current);const ctx=this.layer.getContext('2d');ctx.clearRect(0,0,this.layer.width,this.layer.height);ctx.drawImage(image,0,0);this.revision++;this.flush();this.editor.schedule();this.sync();}catch(e){from.push(data);this.editor.status(e.message,true);}finally{this.restoring=false;}}
clear(){this.undo=[];this.redo=[];this.layer.width=this.layer.height=0;if(this.widget)this.widget.value='';this.revision++;this.sync();}
destroy(){this.barObserver.disconnect();this.layer.width=this.layer.height=0;this.controls.remove();}
destroy(){this.layer.width=this.layer.height=0;this.controls.remove();}
clearRegion(r){if(!this.layer.width)return;this.remember();this.redo=[];this.layer.getContext('2d').clearRect(r.x,r.y,r.width,r.height);this.revision++;this.flush();this.editor.schedule();this.sync();}
sync(){this.editor.stage.classList.toggle('dd-color-active',this.active);this.button.classList.toggle('dd-active',this.active);this.button.setAttribute('aria-pressed',String(this.active));this.picker.hidden=this.undoButton.hidden=this.redoButton.hidden=!this.active;this.undoButton.disabled=!this.undo.length;this.redoButton.disabled=!this.redo.length;this.editor.updateButtons?.();}
}
+1 -1
View File
@@ -34,7 +34,7 @@ export class ImageCompareView {
this.downloadButton.setAttribute("aria-label", this.downloadButton.title);
this.downloadButton.onclick = event => { event.preventDefault(); event.stopPropagation(); this.downloadLatest(); };
toolbar.append(this.downloadButton);
const fit = element("button", "", T("Fit")); fit.type = "button";
const fit = element("button", "dd-compare-fit", T("Fit")); fit.type = "button";
fit.onclick = event => { event.preventDefault(); event.stopPropagation(); this.fitView(); }; toolbar.append(fit);
this.stage = element("div", "dd-stage dd-compare-stage");
this.canvas = document.createElement("canvas"); this.canvas.tabIndex = 0;
+1 -1
View File
@@ -1,6 +1,6 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { ImageCompareView } from "./compare.js?v=2.1.0";
import { ImageCompareView } from "./compare.js?v=toolbar-layout-20261112";
import { readPreview } from "./preview_transport.js";
import { installComparisonRecovery } from "./result_recovery.js";
import {executionId,executionNode,graphNodeEntries} from "./workspace_flow.js";
+2 -1
View File
@@ -26,7 +26,7 @@
.dd-inpaint input[type="range"] { display: block; width: 100%; min-width: 0; height: 18px; margin: 3px 0 0; accent-color: #f3b06a; cursor: pointer; }
.dd-inpaint .dd-stage { position: relative; flex: 1 1 290px; min-height: 170px; overflow: hidden; border-top: 1px solid var(--dd-line); border-bottom: 1px solid var(--dd-line); background-color: #161a20; background-image: linear-gradient(45deg, #222831 25%, transparent 25%), linear-gradient(-45deg, #222831 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #222831 75%), linear-gradient(-45deg, transparent 75%, #222831 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0; }
.dd-inpaint .dd-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; cursor: crosshair; }
.dd-inpaint .dd-color-paint {position:absolute;left:8px;bottom:var(--dd-color-offset,45px);z-index:5;display:flex;gap:4px;align-items:center;padding:3px;border-radius:5px;background:#1b222bdd;}
.dd-inpaint .dd-color-paint {display:flex;flex:none;gap:3px;align-items:center;}
.dd-inpaint .dd-color-paint button {min-width:26px;padding:3px 6px;}
.dd-inpaint .dd-color-paint input[type=color] {width:26px;height:25px;padding:1px;border:0;background:transparent;cursor:pointer;}
.dd-inpaint .dd-color-paint [hidden] {display:none;}
@@ -102,6 +102,7 @@
.dd-comparer .dd-compare-stage { border: 0; }
.dd-comparer .dd-compare-toolbar { padding-top: 5px; padding-bottom: 2px; }
.dd-comparer .dd-compare-toolbar .dd-dimensions { margin-left: 5px; }
.adachi-workspace .dd-comparer .dd-compare-fit { display: none; }
.dd-comparer .dd-adoption { padding-top: 3px; padding-bottom: 5px; }
.dd-comparer .dd-compare-captions { position: absolute; top: 8px; left: 8px; right: 8px; display: flex; justify-content: space-between; pointer-events: none; }
.dd-comparer .dd-compare-captions span { background: #171d27cc; color: #f1e3cf; padding: 3px 7px; border-radius: 3px; font-size: 11px; }
+3 -3
View File
@@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { InlineMaskEditor } from "./editor.js?v=2.1.0";
import { installInpaintWorkbench } from "./inpaint_node.js?v=workspace-zoom-20261112";
import { installInpaintWorkbench } from "./inpaint_node.js?v=toolbar-layout-20261112";
import { bindWidgetToNodeWidth, createWidgetHost } from "./widget_layout.js?v=stable-host-20261112";
import { routeWheelOutsideImage } from "./wheel_routing.js";
import { InlineCropController } from "./inline_crop.js?v=2.1.0";
@@ -9,13 +9,13 @@ import { canStoreDraft, prepareInlineDraft, syncInlineWidgets } from "./draft_gu
import { T } from "./i18n.js";
import { removeLegacyFeedback } from "./legacy_feedback.js?v=0.6.4";
import { uploadImageWithProgress } from "./upload_transport.js?v=stability1";
import { ColorPaint } from "./color_paint.js";
import { ColorPaint } from "./color_paint.js?v=toolbar-layout-20261112";
import { createPaintDraftProtection } from "./paint_draft.js";
const styleId = "adachi-inpaint-style";
const stylesheet = document.getElementById(styleId) || document.createElement("link");
stylesheet.id = styleId; stylesheet.rel = "stylesheet";
stylesheet.href = new URL("./editor.css?v=color-paint-20261112", import.meta.url).href;
stylesheet.href = new URL("./editor.css?v=toolbar-layout-20261112", import.meta.url).href;
if (!stylesheet.isConnected) document.head.append(stylesheet);
function hide(widget) {
+1 -1
View File
@@ -1,5 +1,5 @@
import { app } from "../../scripts/app.js";
import { InpaintParameterPanel } from "./inpaint_panel.js?v=workspace-zoom-20261112";
import { InpaintParameterPanel } from "./inpaint_panel.js?v=toolbar-layout-20261112";
import { connectedEditor, connectedSamplers, relocateWidgets, syncCurrentDimensions } from "./parameter_bridge.js?v=0.8.2";
import { routeWheelOutsideImage } from "./wheel_routing.js";
import { T } from "./i18n.js";
+1 -1
View File
@@ -58,7 +58,7 @@ export class InpaintParameterPanel {
enum(parent, name, label, pills = false) {
const widget = this.widget(name); if (!widget) return;
label = T(label);
const wrapper = el("div", "dd-param-field"); wrapper.append(el("span", "dd-param-label", label));
const wrapper = el("div", "dd-param-field"); wrapper.dataset.adachiParam = name; wrapper.append(el("span", "dd-param-label", label));
let values = widget.options?.values; if (typeof values === "function") values = values();
if (!Array.isArray(values)) values = Object.keys(choices[name] || {});
if (!values.includes(widget.value)) values = [widget.value, ...values];
-1
View File
@@ -98,7 +98,6 @@ body.adachi-workspace-open > :not(.adachi-workspace):not(script):not(style):not(
.adachi-workspace .aw-canvas .dd-crop-bar button,.adachi-workspace .aw-canvas .dd-crop-bar select,.adachi-workspace .aw-canvas .dd-crop-bar input {
height:28px;min-height:28px;font-size:12px;color:var(--aw-text);
}
.adachi-workspace .aw-canvas .dd-color-paint {bottom:calc(var(--aw-history-height,40px) + var(--aw-crop-height,32px) + 24px);background:var(--aw-panel);border:1px solid var(--aw-line);box-shadow:0 2px 8px #0002;}
.adachi-workspace .aw-canvas .dd-files button {opacity:1}
.adachi-workspace .aw-canvas .dd-adoption {display:none}
.adachi-workspace .aw-canvas .dd-compare-stage {position:absolute;inset:0;min-height:0}
+2 -2
View File
@@ -72,7 +72,7 @@ export class Workspace {
// Move the existing controls, rather than copying widgets: linked inputs,
// source-size synchronization and sampler updates keep their native wiring.
this.sampler.adachiParameters.refresh();
for(const name of ['resolution','width','height','denoise']){
for(const name of ['area','resolution','width','height','denoise']){
const field=this.sampler.adachiParameters.body.querySelector(`:scope > [data-adachi-param="${name}"]`);
if(field)this.mount(field,this.quickParams);
}
@@ -219,6 +219,6 @@ export class Workspace {
close(){if(this.closed)return;this.closed=true;this.sessionController.abort();this.checkController?.abort();++this.checkVersion;clearInterval(this.timer);clearInterval(this.pollTimer);clearTimeout(this.checkTimer);this.stopSplitter?.();for(const dispose of this.disposers.reverse())dispose();if(this.history){this.history.onChange=null;this.history.onRestore=null;}if(this.taskProgress)this.taskProgress.onChange=()=>{};this.bindings?.destroy();for(const {element,marker,style}of this.mounts.reverse()){element.classList.remove('aw-embedded');const removed=(element===this.editor?.root&&(this.editor.destroyed||!this.editorNode?.graph))||(element===this.compare?.root&&!this.compareNode?.graph)||(element===this.sampler?.adachiWorkbench?.root&&!this.sampler?.graph);if(removed){marker.remove();element.remove();continue;}element.hidden=false;element.inert=false;if(marker.parentNode)marker.replaceWith(element);if(style===null)element.removeAttribute('style');else element.setAttribute('style',style);}this.root.remove();document.body.classList.remove('adachi-workspace-open');this.editor?.schedule();this.compare?.schedule();this.compare?.updateButton();currentWorkspace=null;}
}
app.registerExtension({name:'Adachi.InpaintWorkspace',setup(){
const css=document.createElement('link');css.rel='stylesheet';css.href=new URL('./workspace.css?v=2.1.0',import.meta.url).href;document.head.append(css);
const css=document.createElement('link');css.rel='stylesheet';css.href=new URL('./workspace.css?v=toolbar-layout-20261112',import.meta.url).href;document.head.append(css);
document.addEventListener('adachi-open-workspace',event=>{if(!currentWorkspace)currentWorkspace=new Workspace(event.detail?.sampler||null);});
}});