Minor fix

This commit is contained in:
Raykosan
2026-06-19 17:32:07 +03:00
parent 8a21ae1811
commit 5661eb49c6
4 changed files with 111 additions and 21 deletions
+4 -2
View File
@@ -200,8 +200,10 @@ class RSCollage:
fv = bool(data.get("flip_v"))
op = float(data.get("opacity") or opacity)
ft = data.get("feather_type") or feather_type
br = int(data.get("blur_radius") or blur_radius)
bh = int(data.get("blur_hardness") or blur_hardness)
br_raw = data.get("blur_radius")
bh_raw = data.get("blur_hardness")
br = int(br_raw) if br_raw is not None else blur_radius
bh = int(bh_raw) if bh_raw is not None else blur_hardness
fcx = float(data.get("feather_center_x") or 0.5)
fcy = float(data.get("feather_center_y") or 0.5)
+16 -1
View File
@@ -1,3 +1,18 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2025-2026 Raykosan (RaykoStudio)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import time
import json
@@ -269,4 +284,4 @@ if SERVER_AVAILABLE:
return web.Response(status=500, text=str(e))
NODE_CLASS_MAPPINGS = {"RaykoIntermediateSplineMask": RaykoIntermediateSplineMask}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoIntermediateSplineMask": " RS Intermediate Spline Mask"}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoIntermediateSplineMask": "🦊 RS Intermediate Spline Mask"}
+91 -18
View File
@@ -67,6 +67,14 @@ app.registerExtension({
this.previewMaxSize = 512;
this.pendingEditorData = null;
// Helper function to sync widget values
const syncWidgetValue = (widgetName, value) => {
const widget = this.widgets?.find(w => w.name === widgetName);
if (widget) {
widget.value = value;
}
};
// Hide default widgets
["opacity", "feather_type", "blur_radius", "blur_hardness"].forEach(n => {
const w = this.widgets?.find(w => w.name === n);
@@ -74,10 +82,10 @@ app.registerExtension({
});
// Add custom widgets
this.addWidget("slider", "opacity", 1.0, v => { this.opacity = v; this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 1, step: 0.01 });
this.addWidget("combo", "feather_type", "None", v => { this.featherType = v; this.previewDirty = true; this.setDirtyCanvas(true); }, { values: ["None", "Radial Blur In", "Radial Blur Out", "Ellipse Blur In", "Ellipse Blur Out"] });
this.addWidget("slider", "blur_radius", 50, v => { this.blurRadius = v; this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 });
this.addWidget("slider", "blur_hardness", 0, v => { this.blurHardness = v; this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 });
this.addWidget("slider", "opacity", 1.0, v => { this.opacity = v; syncWidgetValue("opacity", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 1, step: 0.01 });
this.addWidget("combo", "feather_type", "None", v => { this.featherType = v; syncWidgetValue("feather_type", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { values: ["None", "Radial Blur In", "Radial Blur Out", "Ellipse Blur In", "Ellipse Blur Out"] });
this.addWidget("slider", "blur_radius", 50, v => { this.blurRadius = v; syncWidgetValue("blur_radius", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 });
this.addWidget("slider", "blur_hardness", 0, v => { this.blurHardness = v; syncWidgetValue("blur_hardness", v); this.previewDirty = true; this.setDirtyCanvas(true); }, { min: 0, max: 100, step: 1 });
// Create Overlay DOM
if (!this.overlayContainer) {
@@ -140,9 +148,16 @@ app.registerExtension({
const sync = (key, type) => {
const el = this.overlayInputs[key];
const handler = () => {
if (key === 'opacity') this.opacity = parseFloat(el.value) / 100;
else if (type === 'float') this[key] = parseFloat(el.value);
else this[key] = parseInt(el.value);
if (key === 'opacity') {
this.opacity = parseFloat(el.value) / 100;
syncWidgetValue("opacity", this.opacity);
} else if (type === 'float') {
this[key] = parseFloat(el.value);
} else {
this[key] = parseInt(el.value);
if (key === 'blurRadius') syncWidgetValue("blur_radius", this[key]);
else if (key === 'blurHardness') syncWidgetValue("blur_hardness", this[key]);
}
this.previewDirty = true;
this.setDirtyCanvas(true);
};
@@ -150,7 +165,12 @@ app.registerExtension({
el.addEventListener('change', handler);
};
sync('opacity', 'float');
this.overlayInputs.featherType.addEventListener('change', () => { this.featherType = this.overlayInputs.featherType.value; this.previewDirty = true; this.setDirtyCanvas(true); });
this.overlayInputs.featherType.addEventListener('change', () => {
this.featherType = this.overlayInputs.featherType.value;
syncWidgetValue("feather_type", this.featherType);
this.previewDirty = true;
this.setDirtyCanvas(true);
});
sync('blurRadius', 'int');
sync('blurHardness', 'int');
@@ -414,10 +434,17 @@ app.registerExtension({
this.featherCenter = { x: 0.5, y: 0.5 };
this.previewDirty = true;
["opacity", "feather_type", "blur_radius", "blur_hardness"].forEach(n => {
const w = this.widgets?.find(w => w.name === n);
if (w) w.value = this[n];
});
// Sync widget values when opening editor
const syncWidgetValue = (widgetName, value) => {
const widget = this.widgets?.find(w => w.name === widgetName);
if (widget) {
widget.value = value;
}
};
syncWidgetValue("opacity", this.opacity);
syncWidgetValue("feather_type", this.featherType);
syncWidgetValue("blur_radius", this.blurRadius);
syncWidgetValue("blur_hardness", this.blurHardness);
this.realBackground = { width: data.bg_width, height: data.bg_height };
this.realOverlay = { width: data.ov_width, height: data.ov_height };
@@ -550,18 +577,57 @@ app.registerExtension({
nodeType.prototype.computeScreenHandles = function(rectX, rectY, useScale, useOffsetX, useOffsetY) {
const hw = this.overlay.width / 2, hh = this.overlay.height / 2, rot = this.overlay.rotation * Math.PI / 180;
const cos = Math.cos(rot), sin = Math.sin(rot), fx = this.overlay.flipH ? -1 : 1, fy = this.overlay.flipV ? -1 : 1;
const handles = { 'scale-tl': [-hw, -hh], 'scale-tr': [hw, -hh], 'scale-bl': [-hw, hh], 'scale-br': [hw, hh], 'scale-t': [0, -hh], 'scale-b': [0, hh], 'scale-l': [-hw, 0], 'scale-r': [hw, 0], 'rotate': [0, -hh - 40], 'feather-center': [(this.featherCenter.x - 0.5) * this.overlay.width, (this.featherCenter.y - 0.5) * this.overlay.height] };
const handles = {
'scale-tl': [-hw, -hh],
'scale-tr': [hw, -hh],
'scale-bl': [-hw, hh],
'scale-br': [hw, hh],
'scale-t': [0, -hh],
'scale-b': [0, hh],
'scale-l': [-hw, 0],
'scale-r': [hw, 0],
'rotate': [0, -hh - 40]
};
const screenHandles = {};
for (const [name, loc] of Object.entries(handles)) {
const rx = loc[0] * cos - loc[1] * sin, ry = loc[0] * sin + loc[1] * cos;
screenHandles[name] = { x: rectX + useOffsetX + (this.overlay.x + rx * fx) * useScale, y: rectY + useOffsetY + (this.overlay.y + ry * fy) * useScale };
}
const fCxLocal = (this.featherCenter.x - 0.5) * this.overlay.width;
const fCyLocal = (this.featherCenter.y - 0.5) * this.overlay.height;
const fRx = fCxLocal * cos - fCyLocal * sin;
const fRy = fCxLocal * sin + fCyLocal * cos;
screenHandles['feather-center'] = { x: rectX + useOffsetX + (this.overlay.x + fRx * fx) * useScale, y: rectY + useOffsetY + (this.overlay.y + fRy * fy) * useScale };
return screenHandles;
};
nodeType.prototype.sendTransforms = async function() {
const payload = { id: String(this.id), transforms: this.getRealTransform(), opacity: this.opacity, feather_type: this.featherType, blur_radius: this.blurRadius, blur_hardness: this.blurHardness, feather_center_x: this.featherCenter.x, feather_center_y: this.featherCenter.y };
try { await api.fetchApi("/rayko/rs_collage", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }); this.isEditing = false; this.setDirtyCanvas(true); } catch(e) {}
// Инвертируем координаты центра размытия при флипе,
// т.к. в Python feather применяется к уже отражённому изображению
const fcx = this.overlay.flipH ? 1.0 - this.featherCenter.x : this.featherCenter.x;
const fcy = this.overlay.flipV ? 1.0 - this.featherCenter.y : this.featherCenter.y;
const payload = {
id: String(this.id),
transforms: this.getRealTransform(),
opacity: this.opacity,
feather_type: this.featherType,
blur_radius: this.blurRadius,
blur_hardness: this.blurHardness,
feather_center_x: fcx,
feather_center_y: fcy
};
try {
await api.fetchApi("/rayko/rs_collage", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
this.isEditing = false;
this.setDirtyCanvas(true);
} catch(e) {}
};
nodeType.prototype.cancelEditing = async function() {
@@ -596,7 +662,6 @@ app.registerExtension({
// Node foreground drawing (ONLY for normal mode)
nodeType.prototype.onDrawForeground = function(ctx) {
// Если включён Advanced Mode, нода НЕ рисуется. Это убирает "фантом".
if (this.advancedMode) {
ctx.clearRect(0, 0, this.size[0], this.size[1]);
return;
@@ -643,7 +708,6 @@ app.registerExtension({
ctx.fillStyle = "#888"; ctx.font = "12px Arial"; ctx.fillText("▶ Run queue to start", rectX + cSize / 2 - 65, rectY + cSize / 2);
}
// Normal mode buttons
const toggleBtnW = 150, toggleBtnH = 24, toggleBtnX = (this.size[0] - toggleBtnW) / 2, toggleBtnY = 20;
ctx.fillStyle = "#2a2a2a"; ctx.strokeStyle = "#2196F3"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(toggleBtnX + 6, toggleBtnY); ctx.lineTo(toggleBtnX + toggleBtnW - 6, toggleBtnY);
ctx.quadraticCurveTo(toggleBtnX + toggleBtnW, toggleBtnY, toggleBtnX + toggleBtnW, toggleBtnY + 6); ctx.lineTo(toggleBtnX + toggleBtnW, toggleBtnY + toggleBtnH - 6);
@@ -723,7 +787,16 @@ app.registerExtension({
case 'rotate': { const cx = this.overlay.x, cy = this.overlay.y; const sA = Math.atan2(this.dragState.startMouseY - cy, this.dragState.startMouseX - cx); const cA = Math.atan2(worldMy - cy, worldMx - cx); this.overlay.rotation = this.dragState.startRotation + (cA - sA) * 180 / Math.PI; break; }
case 'scale-br': case 'scale-bl': case 'scale-tr': case 'scale-tl': { const cD = Math.hypot(worldMx - this.overlay.x, worldMy - this.overlay.y); const sD = this.dragState.startDist || 1; const sc = Math.max(0.05, cD / sD); this.overlay.width = Math.max(40, this.dragState.startW * sc); this.overlay.height = Math.max(40, this.dragState.startH * sc); this.overlay.x = this.dragState.startX; this.overlay.y = this.dragState.startY; break; }
case 'scale-r': case 'scale-l': case 'scale-b': case 'scale-t': { const aR = this.dragState.startRotation * Math.PI / 180; const c = Math.cos(aR), s = Math.sin(aR); const lDx = dx * c + dy * s, lDy = -dx * s + dy * c; let fW = this.dragState.startW, fH = this.dragState.startH; if(this.dragType === 'scale-r') fW += lDx; else if(this.dragType === 'scale-l') fW -= lDx; else if(this.dragType === 'scale-b') fH += lDy; else fH -= lDy; if(fW < 40) fW = 40; if(fH < 40) fH = 40; this.overlay.width = fW; this.overlay.height = fH; this.overlay.x = this.dragState.startX; this.overlay.y = this.dragState.startY; break; }
case 'feather-center': { const rD = -this.dragState.startRotation * Math.PI / 180; const c = Math.cos(rD), s = Math.sin(rD); const rdx = dx * c - dy * s, rdy = dx * s + dy * c; this.featherCenter.x = Math.max(0, Math.min(1, this.dragState.featherStartX + rdx / this.dragState.startW)); this.featherCenter.y = Math.max(0, Math.min(1, this.dragState.featherStartY + rdy / this.dragState.startH)); break; }
case 'feather-center': {
const rD = -this.dragState.startRotation * Math.PI / 180;
const c = Math.cos(rD), s = Math.sin(rD);
const rdx = dx * c - dy * s, rdy = dx * s + dy * c;
const flipX = this.overlay.flipH ? -1 : 1;
const flipY = this.overlay.flipV ? -1 : 1;
this.featherCenter.x = Math.max(0, Math.min(1, this.dragState.featherStartX + (rdx * flipX) / this.dragState.startW));
this.featherCenter.y = Math.max(0, Math.min(1, this.dragState.featherStartY + (rdy * flipY) / this.dragState.startH));
break;
}
}
this.previewDirty = true; this.setDirtyCanvas(true);
};