给插画里的丝袜加真实的针织纹理(无摩尔纹)。移植自 silvermoong/stocking-texture-tool(MIT)。
当前版本:v1.2.0 —— 下载压缩包 · 更新内容见下面「更新日志」
要改代码,先看
STRUCTURE.md—— 哪个文件干什么、动之前要知道什么、本版本还有哪些没做。
原作者是 silvermoong / stocking-texture-tool(MIT)。本仓库做的是把那个独立工具整合成 ComfyUI 原生节点,整合过程中顺手改了几处。
原版的算法和编辑界面一行没动(upstream/ 就是原版的 stocking/ 包,panel/ 是原版页面),改动集中在"怎么接到 ComfyUI 里"和下面这些。
用法上最大的区别:它接在「保存图像」之后。 保存图片的节点照常把原图存下来,本节点接在它后面,把加了纹理的成品再交出去——所以原图和成品各有一份。它自己也是 OUTPUT_NODE,不接保存节点、直接站在那个位置同样能跑(成品会写进 output/stocking/)。
v1.2.0(当前)
- 修「保存」写出的不是成品(本整合版的问题)。写盘那条路原来自己走一套缓存的场景和一套参数,和成品预览的渲染不是同一条——缓存一过期,落进
output/stocking/的就不是预览条上那张,最坏情况把没加纹理的原图当成成品存了下去。现在保存和预览共用同一个渲染函数(docstore.render),写下去的和屏幕上那张逐像素相同。 - 没纹理可出时直接拒绝保存,并把原因说出来,不再把原图改名成成品写下去。
- 成品几乎等于原图时给提示。 密度调得太高时,防摩尔纹会把纹理淡化掉——实测密度 132 只剩 1% 的像素有变化,密度 90 是 4%,所以状态栏在低于 1.5% 时会提示把密度调到 100 左右,而不是让人对着一张"没变化"的成品发呆。
- 按钮名称统一成「保存」。原来成品那格叫「保存到 output」,两格名字不一样容易看错;存到哪写在悬浮提示里(处理前 = 下载到电脑,成品 = 写进
output/stocking/)。
v1.1.0 —— 首个发布版
相对原版加了什么
| 内容 | |
|---|---|
| 1. 画笔平滑 | 给笔刷加了一套平滑系统:手抖的走向线 / 隔开线会被抹顺,线的两端不动。预览画的和提交上去的是同一条线,所以松手时不会跳。 |
| 2. 材质区分:丝袜 / 丝质 | 原版只叫"丝袜材质",没考虑别的丝质物品——它在丝袜边缘那一圈做了弯折,放到裙子、手套、袖子上就有点出戏。现在选部位时可以指定材质:「丝质」不画边缘的弯折。入口在添加选区那个「…」里面。 |
| 3. 图片锁定 | 加了一个 🔒 锁定图片:锁住之后视图不再跟着拖动 / 缩放,安心画细节不会把画面拖跑。 |
修掉的问题
这三条都只存在于本整合版,原版没有——原版没有 ComfyUI 节点、没有 socket,也没有上面新加的平滑和材质区分。列在这里是因为它们影响过这个版本的使用、现在已经修好,不是原版的 bug 清单。
| 内容 | |
|---|---|
| 1. 不开 Nodes 2.0 时节点异常 | 经典画布下节点会变成一条"拉不回来"的超长竖条:widget 的高度被写成节点高度的函数,而渲染器每帧又按 widget 高度把节点撑高——正反馈,每帧长 92px。现在 widget 只拿走剩余空间(见 STRUCTURE.md §4.6)。 |
| 2. 改了材质重新渲染,结果不进成品区域 | 根因是成品渲染的参数来源错了:它从默认值起步,界面存在文档里的样式 / 斜度 / 亮点根本没被读到——所以界面里换了材质、预览也变了,成品和 socket 却一动不动。现在只有一份真相(文档的 look),界面、成品、socket 全读它。 |
| 3. 平滑不作用于「走向」和「隔开」 | 原来那套只是稳定器——改变的是"画点什么时候移动",线本身的形状一点没变,所以对这两类要交给解算器的几何线等于没用。现在它们在提交前真的会被按弧长抹顺一次。 |
实现细节、踩过的坑、以及每一轮的来龙去脉都在 STRUCTURE.md §5。
一个原生 ComfyUI 节点。采样器的图像直接接进来,加过纹理的图像直接接出去——它就是链条末端的那个节点,替代「保存图像」。不用导图、不用手动搬图、结果不落盘也能继续往下走。
节点下方就是原版工具的编辑界面(点选 / 画笔 / 橡皮 / 切开、走向 / 隔开 / 镜像、6 种样式、密度 / 斜度 / 强度、亮点、预设、撤销重做、颜色排除、标出问题……),完整照搬,一个都不少。工作流的分辨率变了,界面里的图也跟着变,编辑结果就是 socket 上输出的那张图。
原版的界面是约 2700 行 canvas 代码——命中测试、求解状态、轮廓、快捷键——已经调好、能用的。上一版节点自己重写了一个简化版,结果画笔、魔术棒、引导线、纹理质量全错,最后还得手动把图拖进去。所以这里改成:算法层照搬原版的 stocking/ 包(放在 upstream/),界面用 iframe 挂原版页面,节点自己只做两件原版页面做不到的事——把 socket 上的图交给界面、把界面的结果送回 socket。
节点里没有一行自己的编辑逻辑,所以节点和原版工具不可能走偏。
三种方式,效果一样,选顺手的:
| 方式 | 怎么做 | 适合 |
|---|---|---|
| git clone | git clone https://github.com/MORANMAX/ComfyUI-StockingTexture.git(放进 custom_nodes/),以后 git pull 就能更新 |
想跟着更新 |
| 压缩包,解压即用 | 下载 v1.2.0 的 zip,解压到 custom_nodes/,得到 custom_nodes/ComfyUI-StockingTexture/ |
不想装 git,或者要固定一个能用的版本 |
| 仓库页 Download ZIP | 仓库首页 Code → Download ZIP | 同上,但要的是最新代码而不是 v1.2.0 |
三种都要重启 ComfyUI。压缩包和仓库里都不含模型(共约 450MB)——模型文件太大(GitHub 单文件上限 100MB,SAM 那个是 358MB),而且它们是第三方权重、有各自的许可,所以不随仓库分发。模型按下面的表自己下、自己放。
首次使用需要两个模型(工具会自动在本节点 models/ 目录里找,找不到会在日志里打印路径):
| 模型 | 放在哪 | 从哪下 |
|---|---|---|
sam_vit_b_01ec64.pth(358 MB,必需) |
本节点的 models/ 目录。也可以就留在浏览器的下载目录 ~/Downloads/(工具会去那儿找),或在 %LOCALAPPDATA%\StockingTexture\settings.json 里用 sam_checkpoint 指到任何位置 |
SAM 官方(点开就是下载) |
Depth-Anything-V2-Small-hf/(95 MB,可选) |
本节点 models/Depth-Anything-V2-Small-hf/,整个文件夹。不放也能跑:会自动用 Hugging Face 缓存(第一次联网下) |
HuggingFace 下 config.json、preprocessor_config.json、model.safetensors 三个文件 |
依赖:opencv-python、numpy、torch、transformers、segment-anything、Pillow。
ComfyUI 基本都自带,只缺 segment-anything:
<ComfyUI的python> -m pip install segment-anything
psd_tools是可选依赖:只在「导出丝袜引导.psd」时用到。没装时该按钮会给出提示,其它功能不受影响。
- 采样器 / 加载图片 → 本节点
image输入。 - 节点下方的界面自动载入这张图,不用点「载入」。工作流跑过一次之后,界面就在编辑工作流里的那张图。
- 在「引导」页:选部位、画笔 / 橡皮 / 点选修,用「走向」画横纹。
- 切到「纹理」页拉滑块,实时看整体和细节。
- 本节点的
image输出继续接下游(预览、保存、下一个采样器都行)。开着保存到output_save时,成品也会写进output/stocking/,和「保存图像」一样。
| 按钮 | 作用 |
|---|---|
| 自动选择丝袜 | 预处理:SAM + 深度模型找出丝袜在哪,填成部位,只选部位、不画走向线——选完由你精修 |
| 取用节点图片 | 把节点最近收到的图载入界面(还没跑工作流时用) |
| 原图放大 | 放大看没处理过的原图:滚轮缩放、拖动平移、双击或「适应窗口」复位、100% 看实际像素、Esc 关闭 |
| 全屏 | 编辑界面铺满整个标签页;Esc 退出 |
| 送入下游(已停用,置灰) | 原本是把「成品」交给下一个节点。现在按钮不可用,代码保留,日常留住成品用预览条里的「保存」 |
「送入下游」为什么收起来了:socket 上的图只有工作流执行的时候才会更新,所以"把成品交给下游"这件事绕不开排队——而排队就是重新采样一轮,代价和按一次「执行」一样,甚至等于凭空多生成一轮图。它确实修好了"下游拿到编辑之前的图"(服务端先按界面设置渲染、带签名锁定,apply 直接把那张交出去),但为了交一张图多跑一轮采样不划算。
所以这个按钮置灰、逻辑整段留着:web/stocking.js 里的 PUSH_AVAILABLE 改回 true 就恢复,/doc/{id}/push 路由和 routes.push_doc / take_pushed 也都在。日常要留住成品,用下面预览条里的「保存」——只渲染和写盘,不进队列、不重新采样。
日后重新启用时仍要注意:执行还是"执行一次工作流",只是走缓存,上游没变时采样器不重跑。如果采样器的种子设的是「每次随机」,执行会先换种子,于是整张图重新采样、编辑自然对不上了 —— 想让成品原样往下走,把种子固定住(
fixed)。
界面下面还有一条 预览 条,左右并排两块:左边 处理前(节点收到的那张图,也就是你在编辑的那张),右边 成品(加了纹理之后的样子;点它的「保存」就写进 output/stocking/)。两块摆在一起,是为了让"这一笔改动到底有没有效果"变成看一眼的事,而不用猜。
| 按钮 | 作用 |
|---|---|
| (缩略图) | 点一下就是「放大」 |
| 刷新 | 按当前样式 / 密度 / 强度重画一次 |
| 放大 | 滚轮缩放、拖动平移、双击适应窗口、100% 看实际像素、Esc 关闭 |
| 保存(处理前) | 把原图按原始尺寸下载到你的电脑(<文档名>-原图.png) |
| 保存(成品) | 按界面当前设置渲染一次成品,直接写进 ComfyUI 的 output/stocking/<前缀>.png:不执行工作流、不重新采样。同名文件覆盖;成品放大窗口顶栏也有同一个按钮 |
| 收起 | 收起预览条,把地方还给编辑界面 |
「保存」走的是和面板「导出 → 导出成品图」同一条路(POST /api/doc/{id}/export),所以落到 output/stocking/ 的那张和从导出菜单出来的是同一个文件、同样的取色和尺寸,部位漏画走向线时也会照样给出提示。区别只在入口:这里点一下就走,不用选。节点自己执行时写的文件带时间戳(<前缀>_20261021-124500_00.png),所以反复点「保存」只会覆盖它自己那一张,不会冲掉节点的产物。
两块里的图都是缩略图(服务端缩的),只有「放大」和保存才取原尺寸 —— 原图常有几千像素,在节点里解码一张全尺寸 PNG 会让整块界面发卡。
编辑区与预览条之间有一条分隔条:上下拖就分配高度,双击恢复默认;把节点拉小,条会自己收窄,编辑区始终留 220px。编辑界面右下角还有一个拖动把手,拖它就是改节点大小(和拉节点右下角一样),页面会跟着重排。
预览条自己会跟着更新:跑完工作流、在界面里改完图、改节点上的样式 / 密度 / 强度,都会在约 1 秒后重画一次。第一次出现时节点会自动长高一点,所以编辑区不会被挤小。
节点里不会再显示第二张成品图。 ComfyUI 有 IMAGE 输出的节点,本来会在节点里再画一遍这张图(占地方、还带尺寸标注),而它和预览条里的是同一张;这个节点把那份关掉了。队列 / 历史里的缩略图照样有 —— 那是 ui.images,是故意留的。
勾上节点的 自动选择_auto 再排队,则连走向线也自动铺一条——那是无人值守时要出纹理的状态。界面里的「自动选择丝袜」按钮不会替你画走向线。
| 参数 | 范围 | 说明 |
|---|---|---|
样式_style |
6 种 | 细线 / 针织 / 线圈 / 斜单线 / 加濑风 / 油光 |
密度_density |
60–160 | 横纹疏密。小图上过快会为防摩尔纹自动减弱(界面里能看到减弱了多少) |
强度_strength |
0–200 | 纹理有多明显 |
自动选择_auto |
开 / 关 | 排队时自动找丝袜并铺一条走向线,无人值守也能出纹理 |
保存到output_save |
开 / 关 | 成品写进 output/stocking/ |
文件名前缀 |
文本 | 写盘文件名的前缀 |
斜度 / 亮点 / 预设 / 颜色排除等更细的参数都在节点界面里调(和原版完全一致),节点的 widget 只放最常用的三项。
节点的 样式 / 密度 / 强度 widget 和界面里那三个滑杆是同一个设置:两边都写进文档的 look(apply 和「成品」都只读它),改哪边另一边都会跟上——改 widget 会把值写进文档并让界面重读,改界面会让 widget 跟着变。所以不存在"widget 说细线、画面是油光"这种两条腿走路的情况。
原版界面本身就有全部画面,节点不另外造,只在下面补了那个「成品」栏:
- 原生成图:节点最下方的「成品」栏,或者点它放大。ComfyUI 自带的节点预览被编辑界面挤掉了,这里才是看成品的地方。
- 输入原图:节点上的「原图放大」。
- 点选图:「引导」页的 canvas——涂部位、画走向线的地方。
- 完成效果图:「纹理」页——「整体」可左右拖动对比原图 / 效果,「细节」是 100% / 200% 放大,空格按住看原图。要落盘有三条等价的路:预览条成品的「保存」、成品放大窗口顶栏的同名按钮、面板的「导出 → 导出成品图」——都是同一张、同一个文件(
output/stocking/)。
「原图放大」和「纹理」页的「细节」是两件事,别混:前者放大的是输入(用来核对部位选得对不对),后者放大的是加了纹理的结果。
「笔刷」滑杆下面有一条「平滑」滑杆(0–100,默认 0):
- 0 —— 笔迹完全跟手,和以前的版本一模一样。
- 调大 —— 笔迹像被一根绳子牵着跟在指针后面,绳长按笔刷大小算。比绳长还小的手抖会被整个吞掉,小拐角被笔刷半径抹圆;线条仍在原来画的位置上,不会被"平均"到别处。
对画笔、橡皮、切开、走向、隔开都生效——这几个都是手画一条线,都会遇到同一个问题。松手时笔画会画到指针位置。
走向线和隔开线还有第二条:线的形状本身也会被抹顺。 上面那条绳子只改变"画点什么时候动",不改变线的形状——慢慢画(画引导线基本都是慢慢画)时,手抖会原样进到几何里,所以以前拉这条滑杆对走向 / 隔开像是没作用。现在松手前那段折线会过一次按弧长计算的平滑(窗口按像素算,不看点数和手速),画的时候看到的线就是用同一套平滑画出来的,松手不会跳。线的两端保持不动:走向线的两端决定横纹方向,隔开线得接到轮廓上。平滑 0 时一个点都不动,和旧行为逐点一致。
界面的 HTTP 接口挂在 /stocking/api/... 下(不改用 /api/,免得和别的自定义节点打架)。前端用一个小 shim 把 /api/ 映射过去,所以 app.js / look.js 是原版文件、一行没改。
原版页面本身从 /stocking/panel/... 提供(routes.panel_file),不在 WEB_DIRECTORY 里——原因见下面第 1 条。
关键路由:/stocking/api/doc(当前文档状态)、/doc/{id}/art|regions|strokes|look|render/{crop,fit,check}|result|push|undo|redo|export|coverage、/doc/{id}/regions/{rid}/material(换材质:丝袜 / 丝质)、/open/socket、/auto、/look/presets、/lang、/ping;页面:/stocking/panel/index.html。
/doc/{id}/look 有 PUT 和 PATCH 两种:PUT 是整份设置(界面滑杆用),PATCH 只改传过去的几项、其余保留(节点 widget 用——整份写会把界面里调过的斜度 / 亮点冲回默认)。
/doc/{id}/result 是预览条的成品格用的:按文档自己的 look(就是界面上的设置)渲染整张图,也就是 socket 上那张;?w= 在服务端缩成缩略图。它渲染前会等部位求解完(上限 20 秒),否则会拿到"刚画的地方还没纹理"的图。处理前格用的是 /doc/{id}/art,同样收 ?w=。
/doc/{id}/push 是「送入下游」用的(按钮已停用,路由保留):渲染成品、记住它(带签名),nodes.apply 在下一次执行时原样交出并清掉。签名 = 文档 id + look + 求解版本,任何一样变了就当没记住。
/doc/{id}/export 是「保存」和面板「导出」共用的写盘口:kind 取 png / cutout / guides,name 做文件名前缀,写到 ComfyUI 的 output/stocking/,返回 {file, folder, notes}。不传 params 时按文档自己的 look 渲染——所以预览条那块「成品」和写下去的文件是同一张。它不排队,这是它和 /push 的根本区别。
不想开浏览器就能验证整条链路:
<ComfyUI的python> custom_nodes/ComfyUI-StockingTexture/e2e_check.py <输入图.png>
它会跑 图 → Document → 自动选择 → 走向线 → 求解 → 渲染,并报告成品与输入的差异百分比,输出 e2e_out.png。
另外三个(第六轮加的,都不需要起 ComfyUI):
<ComfyUI的python> custom_nodes/ComfyUI-StockingTexture/nodes_check.py [输入图]
<ComfyUI的python> custom_nodes/ComfyUI-StockingTexture/routes_check.py [输入图]
node custom_nodes/ComfyUI-StockingTexture/smooth_check.mjs
nodes_check.py 验「成品用界面的设置」「送入下游锁定 / 交一次 / 编辑后失效」「新文档从 widget 播种」;
routes_check.py 用真 aiohttp 打 /push、PATCH /look、/result、/export(后者连文件是否真落在
output/stocking/、尺寸对不对、名字有没有用传进去的前缀一起验);smooth_check.mjs 把界面里的 smoothLine
原文抽出来量抖动和耗时。
- 原版页面不能放在
WEB_DIRECTORY里。 ComfyUI 会把WEB_DIRECTORY下所有.js(含子目录)当作 ComfyUI 扩展,交给主页面import。原版页面的app.js/look.js/shim.js不是扩展:它们要的是只存在于 iframe 里的#view、#empty,还会装全局按键监听。一旦被主页面加载,整个前端启动就死在半路——/api/object_info永远不会被请求,一个节点定义都到不了浏览器,画布画得出来但界面全空,任何节点都加不进去(不只是本节点)。 所以页面放在节点根目录的panel/,由routes.panel_file在/stocking/panel/...下提供。把panel/挪回web/会立刻重现这个问题。 scene_from_doc的disparity必须传 callable,不能传数组。 原版look.Scene存下它并在每次渲染时self._disparity()调用(这样晚到的深度图也能用上)。传数组会让 Scene 试图调用数组,第一次渲染就崩。- SAM 和深度模型不能同时构造。 在这个 torch 构建(2.12+cu130)上,两个线程一起
nn.Module(...).to('cuda')会让 SAM 的权重变成半精度,第一次点选就报Input type (struct c10::Half) and bias type (float) should be the same。upstream/_loads.py里那把锁就是为这个加的,不要删。
另外两条前端坑(改节点界面时容易踩):
img.src不走fetch。 原版页面取图片用的是new Image(); img.src = '/api/...',不是fetch,所以只覆盖window.fetch是不够的。漏掉它,原图、部位掩码、覆盖范围图三个都会打到 ComfyUI 自己的/api/上拿 404 —— 页面看起来是活的(工具列表、部位列表、缩放百分比都来自 fetch 的状态,全都正确),只有画布是空的。shim.js里改了HTMLImageElement.prototype.src的 setter 来兜住它。- 内联
style="display:flex"会盖掉hidden属性对应的display:none。所以查看层这类浮层要同时管hidden和style.display,只设hidden会让它一直显示在画面上。
| 轮次 | 内容 |
|---|---|
| 1 | 画笔平滑 / 预览条(当时的「成品」单栏)/ 成品放大保存 / 右下角拖动改大小 |
| 2 | 预览条改成处理前 / 成品左右并排;修平滑「显示与实际不符」 |
| 3 | 选区材质开关(丝袜 / 丝质):丝质不画边缘那圈折钩 |
| 4 | 并排真正生效(根因:stocking.css 从来没被加载过);编辑区与预览条之间的分隔条;两张格子改走服务端缩略图、拒收 ComfyUI 自己那份成品图(界面发卡的来源) |
| 5 | 修掉"节点变成一条很长的竖条、拉不回来"(只在别人的机器上出现):原因是面板 widget 的高度被写成了节点高度的函数,而经典画布渲染器每帧都会按 widget 高度把节点撑高 —— 正反馈,每帧 +92px。改成让 widget 拿走剩余空间(详见 STRUCTURE.md §4.6) |
| 6 | 成品 / socket 改用界面上的设置(原来 docstore.render 从默认值起步,界面调的样式根本进不了成品 —— 换材质后成品不动就是这个);「送入下游」按钮;平滑作用于走向线 / 隔开线的形状;节点 widget 与界面滑杆双向同步 |
| 7 | 「送入下游」停用(置灰,代码保留,PUSH_AVAILABLE 一个开关);「成品」的保存改成「保存」:按界面设置渲染后直接写进 output/stocking/,不排队、不重新采样 |
| 8 | 修「保存」写出的不是成品:/export 原来自己走 _scene_for 的缓存 Scene + _base_look 取参数,和成品预览的 GET /result(docstore.render)不是同一条渲染路径,缓存一过期就会把没加纹理的「处理前」写进 output/stocking/。现在这条路也走 docstore.render,落盘的和预览条里那张逐像素相同;真没纹理可出时直接报错,不再把原图当成品写下去。按钮名称统一成「保存」 |
最近一轮的改动怎么点一遍、每个功能的实现细节,见 STRUCTURE.md §5 / §6。
- 面板里"求解中"的状态可能要等下一次操作才更新。 原版靠事件流推送;节点里事件流是哑的,这一行状态会慢一拍。横纹本身不受影响。
panel/app.js相对原版不再逐字一致(多了画笔平滑)。别拿上游的static/app.js直接覆盖。
节点变成一条很长的竖条,怎么拉都拉不回来(别人的机器上才会遇到) 本整合版早期(v1.0 之前)的 bug,已修。只有关闭了「Nodes 2.0」(设置 → Comfy → Modern Node Design)的 ComfyUI 才会中: 那个模式下前端每帧都按 widget 高度重算节点高度,而旧代码里"面板 widget 要占多高"恰好又等于"节点现在多高",于是节点每帧长高 92px,一路长成竖条。
已在真机复现并验证(ComfyUI 前端 1.53.10,经典画布 + 真
drawNode):修复版连续 121 帧高度恒为900(0 增长); 同条件把面板 widget 换回旧写法,900 → 994 → 2874 → 6634 → 12274,约 +94px/帧,与离线复刻的 +92px/帧吻合。
- 升到 v1.0 或更高版本即可(
web/stocking.js已修)。 - 换之前想把已经撑坏的节点救回来:选中节点,在浏览器控制台(F12)执行
app.graph._nodes.filter(n => n.type === 'StockingTexture').forEach(n => n.setSize([960, 900])); app.graph.setDirtyCanvas(true, true)然后重新保存一次工作流(否则下次打开还是旧的尺寸)。 - 不改代码的临时办法:设置里把「Nodes 2.0」打开(那正是作者机器上的状态,所以本地一直看不出问题)。