dsh-comfyui-rp 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/CHANGELOG.md +6 -0
- package/lib/api.js +15 -0
- package/lib/client.js +50 -13
- package/lib/meta.js +1 -1
- package/package.json +1 -1
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# 更新记录
|
|
2
2
|
|
|
3
|
+
## 0.2.4
|
|
4
|
+
|
|
5
|
+
- **纯黑图误判修复**:判定前先看体积——真·纯黑 PNG 只有 20 多 KB,超过 **120 KB** 的图直接不参与判定。之前有正常图(约 1 MB)在「原地替换后立刻复查」时被判成黑图,导致同一张图被连续替换 3 次。
|
|
6
|
+
- **判黑要两次都黑**:第一次判黑后会隔 0.9 秒、绕过浏览器缓存再验一次,两次都黑才自动替换,避免一次绘制的偶发全黑。
|
|
7
|
+
- **判定可追溯**:每次判定都会把统计(体积、暗像素比例、平均亮度、亮点比例、不透明覆盖)写进 Host 日志,「日志」页能直接看到;出现误判时能一眼定位是哪条规则触发。
|
|
8
|
+
|
|
3
9
|
## 0.2.3
|
|
4
10
|
|
|
5
11
|
- **自动补图可以随时停下**:补图进行中,图片框左下菜单的按钮会变成「终止」(之前因为补图是内部任务,菜单里根本看不到它),点一下清空队列并中止正在跑的那一单;全屏和工作台的「终止」也一样。右侧栏顶部还有一条「停止自动补图」。下一次新任务开始时自动复位。
|
package/lib/api.js
CHANGED
|
@@ -1293,6 +1293,21 @@ export function createApi({ store, jobs, hub, getConfig, ctx, sc, routePath, ver
|
|
|
1293
1293
|
}));
|
|
1294
1294
|
}
|
|
1295
1295
|
|
|
1296
|
+
if (action === 'black-check' && method === 'POST') {
|
|
1297
|
+
const b = body || {};
|
|
1298
|
+
const stats = [
|
|
1299
|
+
b.darkRatio !== undefined ? `暗 ${(Number(b.darkRatio) * 100).toFixed(2)}%` : '',
|
|
1300
|
+
b.meanLum !== undefined ? `平均亮度 ${Number(b.meanLum).toFixed(1)}` : '',
|
|
1301
|
+
b.brightRatio !== undefined ? `亮点 ${(Number(b.brightRatio) * 100).toFixed(2)}%` : '',
|
|
1302
|
+
b.coverage !== undefined ? `不透明 ${Math.round(Number(b.coverage) * 100)}%` : '',
|
|
1303
|
+
].filter(Boolean).join(' · ');
|
|
1304
|
+
const skippedText = b.skipped === 'bytes' ? '跳过(体积太大,不可能是纯黑)' : (b.skipped ? `跳过(${b.skipped})` : '');
|
|
1305
|
+
const head = b.black === true ? '判为纯黑图' : (skippedText || '不是黑图');
|
|
1306
|
+
hub.pushLog(b.black === true ? 'warn' : 'info',
|
|
1307
|
+
`黑图检测:${head} · ${b.fileName || b.imageId || '?'}${b.bytes ? ` · ${b.bytes} B` : ''}${stats ? ` · ${stats}` : ''}`);
|
|
1308
|
+
return sendJson(res, 200, ok({ logged: true }));
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1296
1311
|
if (action === 'logs' && method === 'GET') {
|
|
1297
1312
|
return sendJson(res, 200, ok({ logs: hub.logs.slice(-300) }));
|
|
1298
1313
|
}
|
package/lib/client.js
CHANGED
|
@@ -447,6 +447,7 @@ window.__ModuleLoader__.load({
|
|
|
447
447
|
jobs: function (limit) { return req("GET", "jobs?limit=" + (limit || 30)); },
|
|
448
448
|
job: function (id) { return req("GET", "jobs/" + encodeURIComponent(id)); },
|
|
449
449
|
abort: function (id) { return req("POST", "jobs/" + encodeURIComponent(id) + "/abort", {}); },
|
|
450
|
+
blackCheck: function (body) { return req("POST", "black-check", body || {}); },
|
|
450
451
|
delJob: function (id) { return req("DELETE", "jobs/" + encodeURIComponent(id)); },
|
|
451
452
|
clearJobs: function (keepActive) { return req("POST", "jobs/clear", { keepActive: keepActive !== false }); },
|
|
452
453
|
images: function (q) { return req("GET", "images" + (q ? "?" + q : "")); },
|
|
@@ -3752,29 +3753,62 @@ window.__ModuleLoader__.load({
|
|
|
3752
3753
|
&& darkRatio >= 0.995 && meanLum <= 8 && brightRatio < 0.001;
|
|
3753
3754
|
return { black: black, darkRatio: darkRatio, brightRatio: brightRatio, meanLum: meanLum, coverage: coverage, opaque: opaque };
|
|
3754
3755
|
}
|
|
3755
|
-
|
|
3756
|
+
// 纯黑图 PNG 压缩后极小(832×1216 的真黑图 ≈ 25 KB),超过这个体积不可能是纯黑。
|
|
3757
|
+
// 这条是硬保险:就算画布绘制 / 解码出了岔子,也不会把一张有内容的图当成黑图。
|
|
3758
|
+
var BLACK_MAX_BYTES = 120000;
|
|
3759
|
+
// 返回 judgeNearlyBlack 的统计对象;bust=true 时加个 nc 参数绕过浏览器缓存(确认二次判定用)。
|
|
3760
|
+
var isNearlyBlack = function (url, bust) {
|
|
3756
3761
|
return new Promise(function (resolve) {
|
|
3757
|
-
if (!url || typeof document === "undefined" || typeof Image === "undefined") return resolve(false);
|
|
3762
|
+
if (!url || typeof document === "undefined" || typeof Image === "undefined") return resolve({ black: false });
|
|
3758
3763
|
var probe = new Image();
|
|
3759
3764
|
probe.onload = function () {
|
|
3760
3765
|
try {
|
|
3761
|
-
if (!probe.naturalWidth || !probe.naturalHeight) return resolve(false);
|
|
3766
|
+
if (!probe.naturalWidth || !probe.naturalHeight) return resolve({ black: false });
|
|
3762
3767
|
var w = Math.max(1, Math.min(256, probe.naturalWidth));
|
|
3763
3768
|
var h = Math.max(1, Math.min(256, probe.naturalHeight));
|
|
3764
3769
|
var canvas = document.createElement("canvas");
|
|
3765
3770
|
canvas.width = w;
|
|
3766
3771
|
canvas.height = h;
|
|
3767
3772
|
var g = canvas.getContext("2d");
|
|
3768
|
-
if (!g) return resolve(false);
|
|
3773
|
+
if (!g) return resolve({ black: false });
|
|
3769
3774
|
g.drawImage(probe, 0, 0, w, h);
|
|
3770
3775
|
var d = g.getImageData(0, 0, w, h).data;
|
|
3771
|
-
resolve(judgeNearlyBlack(d)
|
|
3772
|
-
} catch (e) { resolve(false); }
|
|
3776
|
+
resolve(judgeNearlyBlack(d));
|
|
3777
|
+
} catch (e) { resolve({ black: false }); }
|
|
3773
3778
|
};
|
|
3774
|
-
probe.onerror = function () { resolve(false); };
|
|
3775
|
-
probe.src = url;
|
|
3779
|
+
probe.onerror = function () { resolve({ black: false }); };
|
|
3780
|
+
probe.src = url + (bust ? (url.indexOf("?") >= 0 ? "&" : "?") + "nc=" + Date.now() : "");
|
|
3776
3781
|
});
|
|
3777
3782
|
};
|
|
3783
|
+
// 判定入口:先看体积,再验像素;判黑要**两次都黑**才算数(第二次绕过缓存)。
|
|
3784
|
+
var probeBlackImage = function (im) {
|
|
3785
|
+
var url = im && im.url ? String(im.url) : "";
|
|
3786
|
+
var bytes = im && im.bytes ? Number(im.bytes) : 0;
|
|
3787
|
+
if (!url) return Promise.resolve({ black: false, skipped: "no-url" });
|
|
3788
|
+
if (bytes > BLACK_MAX_BYTES) return Promise.resolve({ black: false, skipped: "bytes", bytes: bytes, url: url });
|
|
3789
|
+
return isNearlyBlack(url).then(function (first) {
|
|
3790
|
+
if (!first.black) return { black: false, stats: first, url: url, bytes: bytes };
|
|
3791
|
+
return new Promise(function (resolve) { setTimeout(resolve, 900); })
|
|
3792
|
+
.then(function () { return isNearlyBlack(url, true); })
|
|
3793
|
+
.then(function (second) {
|
|
3794
|
+
return { black: !!second.black, stats: second, first: first, url: url, bytes: bytes, confirmed: true };
|
|
3795
|
+
});
|
|
3796
|
+
});
|
|
3797
|
+
};
|
|
3798
|
+
// 把判定结果写进 Host 日志(日志页可见),误判时能直接看到当时的统计。
|
|
3799
|
+
var reportBlackCheck = function (im, verdict) {
|
|
3800
|
+
try {
|
|
3801
|
+
var s = verdict && verdict.stats ? verdict.stats : {};
|
|
3802
|
+
api.blackCheck({
|
|
3803
|
+
imageId: im && im.id ? String(im.id) : "",
|
|
3804
|
+
fileName: im && im.fileName ? String(im.fileName) : "",
|
|
3805
|
+
bytes: verdict && verdict.bytes ? verdict.bytes : (im && im.bytes) || 0,
|
|
3806
|
+
black: !!(verdict && verdict.black),
|
|
3807
|
+
skipped: (verdict && verdict.skipped) || "",
|
|
3808
|
+
darkRatio: s.darkRatio, meanLum: s.meanLum, brightRatio: s.brightRatio, coverage: s.coverage,
|
|
3809
|
+
}).catch(function () { /* 诊断日志失败不影响主流程 */ });
|
|
3810
|
+
} catch (e) { /* ignore */ }
|
|
3811
|
+
};
|
|
3778
3812
|
var blackQueueRef = useRef([]);
|
|
3779
3813
|
// 自动补图可以随时叫停:队列清空 + 正在跑的那单 abort,并且这一轮不再自动补。
|
|
3780
3814
|
// blackStopRef 在下一轮新任务完成时复位,避免「点过一次停止后就再也不补」。
|
|
@@ -3815,7 +3849,8 @@ window.__ModuleLoader__.load({
|
|
|
3815
3849
|
if (blackStopRef.current) return;
|
|
3816
3850
|
blackQueueRef.current.push({
|
|
3817
3851
|
id: String(im.id), jobId: im.jobId || (sourceJob && sourceJob.id) || "",
|
|
3818
|
-
setIndex: im.setIndex, url: im.url,
|
|
3852
|
+
setIndex: im.setIndex, url: im.url, bytes: im.bytes || 0,
|
|
3853
|
+
attempt: attempt || 1, key: key || "", label: label || "",
|
|
3819
3854
|
});
|
|
3820
3855
|
syncBlackPending();
|
|
3821
3856
|
};
|
|
@@ -3826,8 +3861,9 @@ window.__ModuleLoader__.load({
|
|
|
3826
3861
|
if (blackStopRef.current) { blackQueueRef.current = []; syncBlackPending(); return; }
|
|
3827
3862
|
blackBusyRef.current = true;
|
|
3828
3863
|
syncBlackPending();
|
|
3829
|
-
|
|
3830
|
-
|
|
3864
|
+
probeBlackImage(next).then(function (verdict) {
|
|
3865
|
+
reportBlackCheck(next, verdict);
|
|
3866
|
+
if (!verdict.black) return null;
|
|
3831
3867
|
if (next.attempt === 1) {
|
|
3832
3868
|
var nk = String(next.jobId || next.id || "");
|
|
3833
3869
|
if (!blackNotifiedRef.current.has(nk)) {
|
|
@@ -3881,8 +3917,9 @@ window.__ModuleLoader__.load({
|
|
|
3881
3917
|
if (!retried || !retried.id || !retried.url) return;
|
|
3882
3918
|
var slotKey = blackSlotKey(sourceJob, retried);
|
|
3883
3919
|
var attempt = Math.max(1, Number(blackRetryRef.current[slotKey]) || 1);
|
|
3884
|
-
|
|
3885
|
-
|
|
3920
|
+
probeBlackImage(retried).then(function (verdict) {
|
|
3921
|
+
reportBlackCheck(retried, verdict);
|
|
3922
|
+
if (!verdict.black) {
|
|
3886
3923
|
delete blackRetryRef.current[slotKey];
|
|
3887
3924
|
notify("黑图已替换为正常图", "ok");
|
|
3888
3925
|
// 替换已经落到图片上、也确认不是黑图了,最后再报出图完成。
|
package/lib/meta.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const PACKAGE_NAME = 'dsh-comfyui-rp';
|
|
8
8
|
export const PLUGIN_NAME = 'dsh-comfyui-rp';
|
|
9
|
-
export const VERSION = '0.2.
|
|
9
|
+
export const VERSION = '0.2.4';
|
|
10
10
|
|
|
11
11
|
/** 对外路由前缀。不能带尾斜杠(dsh-host-webserver 的 prefix 匹配规则)。 */
|
|
12
12
|
export const ROUTE_PATH = '/__airp_comfy';
|
package/package.json
CHANGED