The Sharpen, Vibration and Clarity parameters have been added to the RS Image Adjustments node
This commit is contained in:
+62
-18
@@ -2,7 +2,7 @@
|
||||
# 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 not use th is file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -13,9 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import aiohttp
|
||||
import base64
|
||||
from io import BytesIO
|
||||
import os
|
||||
import torch
|
||||
import numpy as np
|
||||
@@ -27,6 +24,7 @@ from PIL import Image
|
||||
from collections import OrderedDict
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import aiohttp
|
||||
|
||||
class LRUCache:
|
||||
def __init__(self, max_size: int = 50):
|
||||
@@ -69,7 +67,6 @@ def apply_basic_adjustments(img_bgr, brightness, contrast, hue, saturation):
|
||||
if brightness != 0 or contrast != 0:
|
||||
alpha = 1.0 + (contrast / 100.0)
|
||||
beta = brightness
|
||||
|
||||
img_bgr = img_bgr.astype(np.float32)
|
||||
img_bgr = img_bgr * alpha + beta
|
||||
img_bgr = np.clip(img_bgr, 0, 255).astype(np.uint8)
|
||||
@@ -82,6 +79,44 @@ def apply_basic_adjustments(img_bgr, brightness, contrast, hue, saturation):
|
||||
|
||||
return img_bgr
|
||||
|
||||
def apply_sharpen(img_bgr, amount):
|
||||
if amount <= 0:
|
||||
return img_bgr
|
||||
|
||||
amount = min(amount, 200) / 100.0
|
||||
blurred = cv2.GaussianBlur(img_bgr, (0, 0), 3)
|
||||
sharpened = cv2.addWeighted(img_bgr, 1.0 + amount, blurred, -amount, 0)
|
||||
return sharpened
|
||||
|
||||
def apply_vibrance(img_bgr, amount):
|
||||
if amount == 0:
|
||||
return img_bgr
|
||||
|
||||
amount = amount / 100.0
|
||||
img_float = img_bgr.astype(np.float32) / 255.0
|
||||
|
||||
max_rgb = np.max(img_float, axis=2)
|
||||
avg_rgb = np.mean(img_float, axis=2)
|
||||
saturation = max_rgb - avg_rgb
|
||||
|
||||
mask = 1.0 - saturation
|
||||
|
||||
result = img_float.copy()
|
||||
for c in range(3):
|
||||
result[:, :, c] = img_float[:, :, c] + (img_float[:, :, c] - avg_rgb[:, :, np.newaxis][:, :, 0]) * amount * mask
|
||||
|
||||
result = np.clip(result * 255, 0, 255).astype(np.uint8)
|
||||
return result
|
||||
|
||||
def apply_clarity(img_bgr, amount):
|
||||
if amount == 0:
|
||||
return img_bgr
|
||||
|
||||
amount = amount / 100.0
|
||||
blurred = cv2.GaussianBlur(img_bgr, (0, 0), 30)
|
||||
clarified = cv2.addWeighted(img_bgr, 1.0 + amount, blurred, -amount, 0)
|
||||
return clarified
|
||||
|
||||
def parse_cube_lut(lut_path):
|
||||
try:
|
||||
mtime = os.path.getmtime(lut_path)
|
||||
@@ -109,11 +144,8 @@ def parse_cube_lut(lut_path):
|
||||
continue
|
||||
|
||||
lut_array = np.array(lut_data, dtype=np.float32).reshape((size, size, size, 3))
|
||||
|
||||
lut_array = np.transpose(lut_array, (2, 1, 0, 3))
|
||||
|
||||
LUT_CACHE.put(cache_key, lut_array)
|
||||
|
||||
return lut_array
|
||||
|
||||
def apply_lut(img_bgr, lut_path, intensity):
|
||||
@@ -168,7 +200,6 @@ def apply_lut(img_bgr, lut_path, intensity):
|
||||
)
|
||||
|
||||
result = np.clip(result, 0, 1)
|
||||
|
||||
result_bgr = result[:, :, ::-1]
|
||||
|
||||
if intensity < 1.0:
|
||||
@@ -182,7 +213,6 @@ def apply_levels(img_bgr, input_black, gamma, input_white):
|
||||
return img_bgr
|
||||
|
||||
img_float = img_bgr.astype(np.float32) / 255.0
|
||||
|
||||
in_min = input_black / 255.0
|
||||
in_max = input_white / 255.0
|
||||
in_range = max(0.001, in_max - in_min)
|
||||
@@ -201,7 +231,6 @@ def apply_exposure(img_bgr, exposure, offset):
|
||||
return img_bgr
|
||||
|
||||
img_float = img_bgr.astype(np.float32) / 255.0
|
||||
|
||||
multiplier = 2.0 ** (exposure / 100.0)
|
||||
img_float = img_float * multiplier + (offset / 100.0)
|
||||
|
||||
@@ -244,7 +273,7 @@ def apply_black_and_white(img_bgr, red, yellow, green, cyan, blue, magenta):
|
||||
img_float = img_bgr.astype(np.float32) / 255.0
|
||||
img_rgb = img_float[:, :, ::-1]
|
||||
img_hsv = cv2.cvtColor((img_rgb * 255).astype(np.uint8), cv2.COLOR_RGB2HSV)
|
||||
hue = img_hsv[:, :, 0].astype(np.float32) * 2.0 # 0..358
|
||||
hue = img_hsv[:, :, 0].astype(np.float32) * 2.0
|
||||
|
||||
w_red = red / 100.0
|
||||
w_yellow = yellow / 100.0
|
||||
@@ -316,11 +345,11 @@ def apply_selective_color(img_bgr, color_name, cyan, magenta, yellow, black):
|
||||
|
||||
img_float = img_bgr.astype(np.float32) / 255.0
|
||||
img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV).astype(np.float32)
|
||||
h = img_hsv[:, :, 0] # 0..179
|
||||
s = img_hsv[:, :, 1] # 0..255
|
||||
v = img_hsv[:, :, 2] # 0..255
|
||||
h = img_hsv[:, :, 0]
|
||||
s = img_hsv[:, :, 1]
|
||||
v = img_hsv[:, :, 2]
|
||||
|
||||
h_deg = h * 2.0 # 0..358
|
||||
h_deg = h * 2.0
|
||||
s_norm = s / 255.0
|
||||
v_norm = v / 255.0
|
||||
|
||||
@@ -339,7 +368,6 @@ def apply_selective_color(img_bgr, color_name, cyan, magenta, yellow, black):
|
||||
diff = np.minimum(diff, 360 - diff)
|
||||
mask = np.exp(-(diff ** 2) / (2 * sigma ** 2))
|
||||
elif color_name == "Whites":
|
||||
mask = (v_norm > 0.8) * (1 - s_norm)
|
||||
mask = np.exp(-((v_norm - 1.0)**2) / (2*0.1**2)) * np.exp(-(s_norm**2) / (2*0.2**2))
|
||||
elif color_name == "Neutrals":
|
||||
mask = np.exp(-((v_norm - 0.5)**2) / (2*0.15**2)) * np.exp(-(s_norm**2) / (2*0.2**2))
|
||||
@@ -380,10 +408,22 @@ def process_image(img_bgr, adjustments):
|
||||
|
||||
result_bgr = apply_basic_adjustments(img_bgr, brightness, contrast, hue, saturation)
|
||||
|
||||
sharpen = adjustments.get("sharpen", 0)
|
||||
if sharpen != 0:
|
||||
result_bgr = apply_sharpen(result_bgr, sharpen)
|
||||
|
||||
lut_path = adjustments.get("lut_path", "")
|
||||
lut_intensity = adjustments.get("lut_intensity", 100)
|
||||
if lut_path:
|
||||
result_bgr = apply_lut(result_bgr, lut_path, lut_intensity / 100.0)
|
||||
|
||||
vibrance = adjustments.get("vibrance", 0)
|
||||
if vibrance != 0:
|
||||
result_bgr = apply_vibrance(result_bgr, vibrance)
|
||||
|
||||
clarity = adjustments.get("clarity", 0)
|
||||
if clarity != 0:
|
||||
result_bgr = apply_clarity(result_bgr, clarity)
|
||||
|
||||
levels = adjustments.get("levels", {})
|
||||
result_bgr = apply_levels(
|
||||
@@ -606,6 +646,7 @@ class RS_ImageAdjustments:
|
||||
"image": ("IMAGE",),
|
||||
"brightness": ("INT", {"default": 0, "min": -100, "max": 100, "step": 1}),
|
||||
"contrast": ("INT", {"default": 0, "min": -100, "max": 100, "step": 1}),
|
||||
"sharpen": ("INT", {"default": 0, "min": 0, "max": 200, "step": 1}),
|
||||
"hue": ("INT", {"default": 0, "min": -180, "max": 180, "step": 1}),
|
||||
"saturation": ("INT", {"default": 0, "min": -100, "max": 100, "step": 1}),
|
||||
},
|
||||
@@ -621,7 +662,7 @@ class RS_ImageAdjustments:
|
||||
OUTPUT_NODE = True
|
||||
DESCRIPTION = "Interactive image adjustments with real-time preview"
|
||||
|
||||
def apply_adjustments(self, image, brightness, contrast, hue, saturation, unique_id=None, advanced_params=None):
|
||||
def apply_adjustments(self, image, brightness, contrast, sharpen, hue, saturation, unique_id=None, advanced_params=None):
|
||||
unique_id = str(unique_id) if unique_id is not None else "unknown"
|
||||
|
||||
if image.numel() == 0:
|
||||
@@ -656,6 +697,7 @@ class RS_ImageAdjustments:
|
||||
"timestamp": ts,
|
||||
"brightness": brightness,
|
||||
"contrast": contrast,
|
||||
"sharpen": sharpen,
|
||||
"hue": hue,
|
||||
"saturation": saturation,
|
||||
"advanced_params": advanced_params or "{}"
|
||||
@@ -686,6 +728,7 @@ class RS_ImageAdjustments:
|
||||
|
||||
brightness = adjustments.get("brightness", brightness)
|
||||
contrast = adjustments.get("contrast", contrast)
|
||||
sharpen = adjustments.get("sharpen", sharpen)
|
||||
hue = adjustments.get("hue", hue)
|
||||
saturation = adjustments.get("saturation", saturation)
|
||||
|
||||
@@ -693,6 +736,7 @@ class RS_ImageAdjustments:
|
||||
|
||||
adjustments["brightness"] = brightness
|
||||
adjustments["contrast"] = contrast
|
||||
adjustments["sharpen"] = sharpen
|
||||
adjustments["hue"] = hue
|
||||
adjustments["saturation"] = saturation
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
|
||||
REQUIRED_MODULES: Set[str] = set()
|
||||
|
||||
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
|
||||
__version__ = "0.32.2"
|
||||
__version__ = "0.32.3"
|
||||
|
||||
def load_modules():
|
||||
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ComfyUI_RaykoStudio",
|
||||
"version": "0.32.2",
|
||||
"version": "0.32.3",
|
||||
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
|
||||
"author": "Raykosan",
|
||||
"license": "Apache License 2.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ComfyUI_RaykoStudio"
|
||||
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
|
||||
version = "0.32.2"
|
||||
version = "0.32.3"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["torch", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
|
||||
|
||||
|
||||
+102
-77
@@ -4,12 +4,17 @@ import { api } from "../../../scripts/api.js";
|
||||
let activeNodeId = null;
|
||||
|
||||
const BASIC_PARAMS = ['brightness', 'contrast', 'hue', 'saturation'];
|
||||
const HIGHLIGHT_PARAMS = [...BASIC_PARAMS, 'sharpen'];
|
||||
const HIDDEN_WIDGETS = ['brightness', 'contrast', 'hue', 'saturation', 'sharpen'];
|
||||
|
||||
const DEFAULT_SLIDER_VALUES = {
|
||||
brightness: 0,
|
||||
contrast: 0,
|
||||
sharpen: 0,
|
||||
hue: 0,
|
||||
saturation: 0,
|
||||
vibrance: 0,
|
||||
clarity: 0,
|
||||
lut_intensity: 100,
|
||||
input_black: 0,
|
||||
gamma: 1.0,
|
||||
@@ -40,10 +45,6 @@ const DEFAULT_SLIDER_VALUES = {
|
||||
blue_red: 0,
|
||||
blue_green: 0,
|
||||
blue_blue: 100,
|
||||
selective_cyan: 0,
|
||||
selective_magenta: 0,
|
||||
selective_yellow: 0,
|
||||
selective_black: 0,
|
||||
sc_cyan: 0,
|
||||
sc_magenta: 0,
|
||||
sc_yellow: 0,
|
||||
@@ -54,8 +55,11 @@ const DEFAULT_SLIDER_VALUES = {
|
||||
const DEFAULT_ADJUSTMENTS = {
|
||||
brightness: 0,
|
||||
contrast: 0,
|
||||
sharpen: 0,
|
||||
hue: 0,
|
||||
saturation: 0,
|
||||
vibrance: 0,
|
||||
clarity: 0,
|
||||
lut_path: "",
|
||||
lut_intensity: 100,
|
||||
levels: { input_black: 0, gamma: 1.0, input_white: 255 },
|
||||
@@ -190,6 +194,8 @@ app.registerExtension({
|
||||
this.sidePanel = null;
|
||||
this.overlayInputs = {};
|
||||
this.sliderDisplays = {};
|
||||
this._basicParamLabels = {};
|
||||
this._allSectionsExpanded = false;
|
||||
|
||||
this.minWidth = 500;
|
||||
this.minHeight = 450;
|
||||
@@ -198,27 +204,18 @@ app.registerExtension({
|
||||
this.slidersY = 0;
|
||||
this.sliderRects = [];
|
||||
this.resetBtnRects = [];
|
||||
this.sliderHover = [false, false, false, false];
|
||||
this.resetBtnHover = [false, false, false, false];
|
||||
this.sliderHover = [false, false, false, false, false];
|
||||
this.resetBtnHover = [false, false, false, false, false];
|
||||
this.sliderDragging = -1;
|
||||
|
||||
this.btnApplyHover = false;
|
||||
this.btnCancelHover = false;
|
||||
this.btnAdvancedHover = false;
|
||||
this.btnResetAllHover = false;
|
||||
this._allSectionsExpanded = false;
|
||||
this._basicParamLabels = {};
|
||||
|
||||
this._syncingWidgets = false;
|
||||
|
||||
const syncWidgetValue = (widgetName, value) => {
|
||||
const widget = this.widgets?.find(w => w.name === widgetName);
|
||||
if (widget) {
|
||||
widget.value = value;
|
||||
}
|
||||
};
|
||||
|
||||
["brightness", "contrast", "hue", "saturation"].forEach(n => {
|
||||
BASIC_PARAMS.forEach(n => {
|
||||
const w = this.widgets?.find(w => w.name === n);
|
||||
if (w) {
|
||||
w.hidden = true;
|
||||
@@ -235,6 +232,25 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const serverParams = ['sharpen'];
|
||||
serverParams.forEach(n => {
|
||||
const w = this.widgets?.find(w => w.name === n);
|
||||
if (w) {
|
||||
w.hidden = true;
|
||||
w.computeSize = () => [0, 0];
|
||||
w.y = 0;
|
||||
const origCallback = w.callback;
|
||||
w.callback = (v) => {
|
||||
if (origCallback) origCallback.apply(w, arguments);
|
||||
if (!this._syncingWidgets) {
|
||||
this.adjustments[n] = v;
|
||||
this.setDirtyCanvas(true);
|
||||
this._scheduleHeavyRender();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const advWidget = this.widgets?.find(w => w.name === "advanced_params");
|
||||
if (advWidget) {
|
||||
@@ -272,7 +288,7 @@ app.registerExtension({
|
||||
|
||||
nodeType.prototype._hideNativeWidgets = function() {
|
||||
if (!this.widgets) return;
|
||||
BASIC_PARAMS.forEach(param => {
|
||||
HIDDEN_WIDGETS.forEach(param => {
|
||||
const widget = this.widgets.find(w => w.name === param);
|
||||
if (widget) {
|
||||
widget.hidden = true;
|
||||
@@ -288,7 +304,7 @@ app.registerExtension({
|
||||
if (!this.widgets) return;
|
||||
this._syncingWidgets = true;
|
||||
try {
|
||||
BASIC_PARAMS.forEach(param => {
|
||||
HIDDEN_WIDGETS.forEach(param => {
|
||||
const widget = this.widgets.find(w => w.name === param);
|
||||
if (widget && widget.value !== this.adjustments[param]) {
|
||||
widget.value = this.adjustments[param];
|
||||
@@ -310,22 +326,24 @@ app.registerExtension({
|
||||
this.sliderDisplays[key].textContent = String(this.adjustments[key]);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.overlayInputs.sharpen) {
|
||||
this.overlayInputs.sharpen.value = this.adjustments.sharpen || 0;
|
||||
}
|
||||
if (this.sliderDisplays.sharpen) {
|
||||
this.sliderDisplays.sharpen.textContent = String(this.adjustments.sharpen || 0);
|
||||
}
|
||||
};
|
||||
|
||||
nodeType.prototype._isParamModified = function(key, parentObj = this.adjustments) {
|
||||
const def = DEFAULT_SLIDER_VALUES[key];
|
||||
if (def === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (def === undefined) return false;
|
||||
|
||||
const val = parentObj[key];
|
||||
if (val === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (val === undefined) return false;
|
||||
|
||||
if (typeof def === 'number') {
|
||||
const isModified = Math.abs(val - def) > 0.001;
|
||||
return isModified;
|
||||
return Math.abs(val - def) > 0.001;
|
||||
}
|
||||
return val !== def;
|
||||
};
|
||||
@@ -333,39 +351,30 @@ app.registerExtension({
|
||||
nodeType.prototype._isSectionModified = function(sectionName) {
|
||||
const checks = {
|
||||
lut: () => (this.adjustments.lut_path && this.adjustments.lut_path !== "") || this._isParamModified('lut_intensity'),
|
||||
|
||||
advanced_tools: () => this._isParamModified('vibrance') || this._isParamModified('clarity'),
|
||||
levels: () => this._isParamModified('input_black', this.adjustments.levels) ||
|
||||
this._isParamModified('gamma', this.adjustments.levels) ||
|
||||
this._isParamModified('input_white', this.adjustments.levels),
|
||||
|
||||
exposure: () => this._isParamModified('exposure', this.adjustments.exposure) ||
|
||||
this._isParamModified('offset', this.adjustments.exposure),
|
||||
|
||||
color_balance: () => [
|
||||
'shadows_cyan_red', 'shadows_magenta_green', 'shadows_yellow_blue',
|
||||
'midtones_cyan_red', 'midtones_magenta_green', 'midtones_yellow_blue',
|
||||
'highlights_cyan_red', 'highlights_magenta_green', 'highlights_yellow_blue'
|
||||
].some(k => this._isParamModified(k, this.adjustments.color_balance)),
|
||||
|
||||
black_white: () => ['bw_red', 'bw_yellow', 'bw_green', 'bw_cyan', 'bw_blue', 'bw_magenta'].some(k => this._isParamModified(k, this.adjustments.black_white)),
|
||||
|
||||
channel_mixer: () => [
|
||||
'red_red', 'red_green', 'red_blue',
|
||||
'green_red', 'green_green', 'green_blue',
|
||||
'blue_red', 'blue_green', 'blue_blue'
|
||||
].some(k => this._isParamModified(k, this.adjustments.channel_mixer)),
|
||||
|
||||
selective_color: () => ['sc_cyan', 'sc_magenta', 'sc_yellow', 'sc_black'].some(k => this._isParamModified(k, this.adjustments.selective_color))
|
||||
};
|
||||
|
||||
return checks[sectionName] ? checks[sectionName]() : false;
|
||||
};
|
||||
|
||||
nodeType.prototype._updateSectionHeaders = function() {
|
||||
if (!this._sectionRegistry) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._sectionRegistry) return;
|
||||
this._sectionRegistry.forEach(entry => {
|
||||
const modified = this._isSectionModified(entry.name);
|
||||
if (entry.label) {
|
||||
@@ -375,7 +384,7 @@ app.registerExtension({
|
||||
};
|
||||
|
||||
nodeType.prototype._updateBasicParamLabels = function() {
|
||||
BASIC_PARAMS.forEach(key => {
|
||||
HIGHLIGHT_PARAMS.forEach(key => {
|
||||
const lbl = this._basicParamLabels[key];
|
||||
if (lbl) {
|
||||
const isModified = this._isParamModified(key);
|
||||
@@ -451,11 +460,9 @@ app.registerExtension({
|
||||
|
||||
this._updateSectionHeaders();
|
||||
this._updateBasicParamLabels();
|
||||
|
||||
this.previewImage = null;
|
||||
this.setDirtyCanvas(true);
|
||||
this._scheduleHeavyRender();
|
||||
|
||||
this._syncWidgetsFromAdjustments();
|
||||
};
|
||||
|
||||
@@ -488,12 +495,13 @@ app.registerExtension({
|
||||
return b;
|
||||
};
|
||||
|
||||
const btnNormalMode = makeBtn("🟢 NORMAL MODE", "#2196F3", () => { this._toggleAdvancedMode(); });
|
||||
const btnNormalMode = makeBtn(" NORMAL MODE", "#2196F3", () => { this._toggleAdvancedMode(); });
|
||||
buttonContainer.appendChild(btnNormalMode);
|
||||
const btnApply = makeBtn("✔️ APPLY", "#4CAF50", () => { this._sendAdjustments(); this._toggleAdvancedMode(); });
|
||||
buttonContainer.appendChild(btnApply);
|
||||
const btnCancel = makeBtn("❌ CANCEL", "#dc3545", () => { this._cancelEditing(); this._toggleAdvancedMode(); });
|
||||
const btnCancel = makeBtn(" CANCEL", "#dc3545", () => { this._cancelEditing(); this._toggleAdvancedMode(); });
|
||||
buttonContainer.appendChild(btnCancel);
|
||||
|
||||
const bottomRow = document.createElement('div');
|
||||
bottomRow.style.cssText = 'display:flex;gap:8px;width:100%;';
|
||||
|
||||
@@ -503,7 +511,6 @@ app.registerExtension({
|
||||
|
||||
this._expandCollapseBtn = makeBtn("▶ EXPAND ALL", "#888", () => {
|
||||
this._allSectionsExpanded = !this._allSectionsExpanded;
|
||||
|
||||
const display = this._allSectionsExpanded ? 'block' : 'none';
|
||||
const icon = this._allSectionsExpanded ? '▼' : '▶';
|
||||
const text = this._allSectionsExpanded ? '▼ COLLAPSE ALL' : '▶ EXPAND ALL';
|
||||
@@ -512,7 +519,6 @@ app.registerExtension({
|
||||
if (entry.content) entry.content.style.display = display;
|
||||
if (entry.chevron) entry.chevron.textContent = icon;
|
||||
});
|
||||
|
||||
this._expandCollapseBtn.textContent = text;
|
||||
});
|
||||
this._expandCollapseBtn.style.flex = '1';
|
||||
@@ -527,9 +533,11 @@ app.registerExtension({
|
||||
lbl.textContent = label;
|
||||
lbl.style.cssText = 'color:#aaa;font-size:11px;font-weight:600;';
|
||||
container.appendChild(lbl);
|
||||
if (BASIC_PARAMS.includes(key)) {
|
||||
|
||||
if (HIGHLIGHT_PARAMS.includes(key)) {
|
||||
this._basicParamLabels[key] = lbl;
|
||||
}
|
||||
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = 'display:flex;align-items:center;gap:8px;';
|
||||
const slider = document.createElement('input');
|
||||
@@ -573,10 +581,10 @@ app.registerExtension({
|
||||
const widget = this.widgets?.find(w => w.name === key);
|
||||
if (widget) widget.value = num;
|
||||
|
||||
this.previewImage = null;
|
||||
this.setDirtyCanvas(true);
|
||||
|
||||
if (BASIC_PARAMS.includes(key)) {
|
||||
this.previewImage = null;
|
||||
} else {
|
||||
this._scheduleHeavyRender();
|
||||
}
|
||||
@@ -621,7 +629,7 @@ app.registerExtension({
|
||||
};
|
||||
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.textContent = '🔄';
|
||||
resetBtn.textContent = '🔄️';
|
||||
resetBtn.style.cssText = 'width:28px;height:28px;background:#252525;color:#888;border:1px solid #444;border-radius:4px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;transition:0.15s;flex-shrink:0;';
|
||||
resetBtn.onmouseenter = () => { resetBtn.style.background = '#2a2a2a'; resetBtn.style.color = '#fff'; resetBtn.style.borderColor = '#4CAF50'; };
|
||||
resetBtn.onmouseleave = () => { resetBtn.style.background = '#252525'; resetBtn.style.color = '#888'; resetBtn.style.borderColor = '#444'; };
|
||||
@@ -635,7 +643,6 @@ app.registerExtension({
|
||||
this._updateBasicParamLabels();
|
||||
|
||||
this.setDirtyCanvas(true);
|
||||
|
||||
if (BASIC_PARAMS.includes(key)) {
|
||||
this.previewImage = null;
|
||||
}
|
||||
@@ -709,6 +716,7 @@ app.registerExtension({
|
||||
this.sidePanel.appendChild(div());
|
||||
this.sidePanel.appendChild(makeSlider("BRIGHTNESS", "brightness", -100, 100, 1));
|
||||
this.sidePanel.appendChild(makeSlider("CONTRAST", "contrast", -100, 100, 1));
|
||||
this.sidePanel.appendChild(makeSlider("SHARPEN", "sharpen", 0, 200, 1));
|
||||
this.sidePanel.appendChild(makeSlider("HUE", "hue", -180, 180, 1));
|
||||
this.sidePanel.appendChild(makeSlider("SATURATION", "saturation", -100, 100, 1));
|
||||
this.sidePanel.appendChild(div());
|
||||
@@ -731,7 +739,7 @@ app.registerExtension({
|
||||
browseBtn.onmouseleave = () => { browseBtn.style.background = '#252525'; browseBtn.style.color = '#aaa'; browseBtn.style.borderColor = '#444'; };
|
||||
browseBtn.onclick = (e) => { e.stopPropagation(); fileInput.click(); };
|
||||
const resetBtn = document.createElement('button');
|
||||
resetBtn.textContent = '🔄️';
|
||||
resetBtn.textContent = '🔄';
|
||||
resetBtn.style.cssText = 'width:28px;height:28px;background:#252525;color:#888;border:1px solid #444;border-radius:4px;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;transition:0.15s;flex-shrink:0;';
|
||||
resetBtn.onmouseenter = () => { resetBtn.style.background = '#2a2a2a'; resetBtn.style.color = '#fff'; resetBtn.style.borderColor = '#4CAF50'; };
|
||||
resetBtn.onmouseleave = () => { resetBtn.style.background = '#252525'; resetBtn.style.color = '#888'; resetBtn.style.borderColor = '#444'; };
|
||||
@@ -787,6 +795,12 @@ app.registerExtension({
|
||||
});
|
||||
this.sidePanel.appendChild(lutSection);
|
||||
|
||||
const advancedToolsSection = makeSection("ADVANCED TOOLS", "advanced_tools", (content) => {
|
||||
content.appendChild(makeSlider("VIBRANCE", "vibrance", -100, 100, 1));
|
||||
content.appendChild(makeSlider("CLARITY", "clarity", -100, 100, 1));
|
||||
});
|
||||
this.sidePanel.appendChild(advancedToolsSection);
|
||||
|
||||
const levelsSection = makeSection("LEVELS", "levels", (content) => {
|
||||
content.appendChild(makeSlider("INPUT BLACK", "input_black", 0, 255, 1, false, this.adjustments.levels));
|
||||
content.appendChild(makeSlider("GAMMA", "gamma", 0.1, 3.0, 0.01, true, this.adjustments.levels));
|
||||
@@ -947,7 +961,6 @@ app.registerExtension({
|
||||
}
|
||||
this.currentRenderAbortController = new AbortController();
|
||||
const signal = this.currentRenderAbortController.signal;
|
||||
this._isHeavyRenderPending = true;
|
||||
|
||||
const payload = {
|
||||
node_id: String(this.id),
|
||||
@@ -972,16 +985,12 @@ app.registerExtension({
|
||||
if (this._previewTimestamp === ts) {
|
||||
this.previewImage = img;
|
||||
this.setDirtyCanvas(true);
|
||||
this._isHeavyRenderPending = false;
|
||||
}
|
||||
};
|
||||
img.onerror = () => {
|
||||
this._isHeavyRenderPending = false;
|
||||
};
|
||||
img.onerror = () => {};
|
||||
img.src = `/view?filename=${data.preview_file}&type=temp&t=${ts}`;
|
||||
}
|
||||
} catch (e) {
|
||||
this._isHeavyRenderPending = false;
|
||||
if (e.name === 'AbortError') return;
|
||||
}
|
||||
};
|
||||
@@ -1076,16 +1085,27 @@ app.registerExtension({
|
||||
const data = this.pendingEditorData;
|
||||
this.adjustments.brightness = data.brightness || 0;
|
||||
this.adjustments.contrast = data.contrast || 0;
|
||||
this.adjustments.sharpen = data.sharpen || 0;
|
||||
this.adjustments.hue = data.hue || 0;
|
||||
this.adjustments.saturation = data.saturation || 0;
|
||||
|
||||
try {
|
||||
const adv = JSON.parse(data.advanced_params || "{}");
|
||||
Object.keys(adv).forEach(key => {
|
||||
if (this.adjustments[key] !== undefined) {
|
||||
this.adjustments[key] = adv[key];
|
||||
|
||||
const mergeRecursive = (target, source) => {
|
||||
for (let key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (source[key] !== null && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
if (!target[key] || typeof target[key] !== 'object') target[key] = {};
|
||||
mergeRecursive(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mergeRecursive(this.adjustments, adv);
|
||||
} catch (e) {}
|
||||
|
||||
this.realBackground = { width: data.bg_width, height: data.bg_height };
|
||||
@@ -1100,7 +1120,6 @@ app.registerExtension({
|
||||
this.backgroundImage = img;
|
||||
this.isLoading = false;
|
||||
this.isEditing = true;
|
||||
this._initWebSocket();
|
||||
|
||||
this._tempCanvas.width = img.width;
|
||||
this._tempCanvas.height = img.height;
|
||||
@@ -1112,6 +1131,7 @@ app.registerExtension({
|
||||
this._syncWidgetsFromAdjustments();
|
||||
this._syncOverlayUI();
|
||||
this._updateBasicParamLabels();
|
||||
this._initWebSocket();
|
||||
this._scheduleHeavyRender();
|
||||
};
|
||||
img.onerror = () => {
|
||||
@@ -1144,15 +1164,6 @@ app.registerExtension({
|
||||
cancelAnimationFrame(this._overlayRenderLoop);
|
||||
this._overlayRenderLoop = null;
|
||||
}
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
this._ws = null;
|
||||
this._wsConnected = false;
|
||||
}
|
||||
if (this._wsReconnectTimer) {
|
||||
clearTimeout(this._wsReconnectTimer);
|
||||
this._wsReconnectTimer = null;
|
||||
}
|
||||
this.setDirtyCanvas(true);
|
||||
return;
|
||||
}
|
||||
@@ -1274,6 +1285,15 @@ app.registerExtension({
|
||||
cancelAnimationFrame(this._overlayRenderLoop);
|
||||
this._overlayRenderLoop = null;
|
||||
}
|
||||
if (this._ws) {
|
||||
this._ws.close();
|
||||
this._ws = null;
|
||||
this._wsConnected = false;
|
||||
}
|
||||
if (this._wsReconnectTimer) {
|
||||
clearTimeout(this._wsReconnectTimer);
|
||||
this._wsReconnectTimer = null;
|
||||
}
|
||||
this.setDirtyCanvas(true);
|
||||
};
|
||||
|
||||
@@ -1284,7 +1304,7 @@ app.registerExtension({
|
||||
const cSize = Math.max(200, size[0] - 40);
|
||||
this.canvasPixelSize = cSize;
|
||||
|
||||
const slidersH = 4 * 30 + 10;
|
||||
const slidersH = 5 * 30 + 10;
|
||||
const btnH = 30;
|
||||
const gaps = 10 + 8 + 8 + 20 + 15;
|
||||
const neededHeight = titleH + canvasTopPadding + cSize + slidersH + btnH + gaps;
|
||||
@@ -1493,6 +1513,7 @@ app.registerExtension({
|
||||
const sliderConfigs = [
|
||||
{ label: "BRIGHTNESS", key: "brightness", min: -100, max: 100 },
|
||||
{ label: "CONTRAST", key: "contrast", min: -100, max: 100 },
|
||||
{ label: "SHARPEN", key: "sharpen", min: 0, max: 200 },
|
||||
{ label: "HUE", key: "hue", min: -180, max: 180 },
|
||||
{ label: "SATURATION", key: "saturation", min: -100, max: 100 }
|
||||
];
|
||||
@@ -1519,7 +1540,7 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
const buttonsY = slidersY + 4 * (sliderH + gapSlider) + 10;
|
||||
const buttonsY = slidersY + 5 * (sliderH + gapSlider) + 10;
|
||||
const btnH2 = 30;
|
||||
const btnGap2 = 10;
|
||||
const btnW2 = (this.size[0] - 35) / 2;
|
||||
@@ -1541,7 +1562,7 @@ app.registerExtension({
|
||||
ctx.strokeStyle = "#dc3545";
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = "#dc3545";
|
||||
ctx.fillText("❌ CANCEL", 15 + btnW2 + btnGap2 + btnW2 / 2, buttonsY + btnH2 / 2 + 4);
|
||||
ctx.fillText(" CANCEL", 15 + btnW2 + btnGap2 + btnW2 / 2, buttonsY + btnH2 / 2 + 4);
|
||||
};
|
||||
|
||||
nodeType.prototype.onMouseDown = function(event, pos) {
|
||||
@@ -1605,7 +1626,7 @@ app.registerExtension({
|
||||
const btnH2 = 30;
|
||||
const btnGap2 = 10;
|
||||
const btnW2 = (this.size[0] - 35) / 2;
|
||||
const buttonsY = this.slidersY + 4 * 38 + 10;
|
||||
const buttonsY = this.slidersY + 5 * 38 + 10;
|
||||
const y1 = buttonsY;
|
||||
|
||||
if (pos[0] >= 15 && pos[0] <= 15 + btnW2 && pos[1] >= y1 && pos[1] <= y1 + btnH2) {
|
||||
@@ -1677,7 +1698,7 @@ app.registerExtension({
|
||||
const btnH2 = 30;
|
||||
const btnGap2 = 10;
|
||||
const btnW2 = (this.size[0] - 35) / 2;
|
||||
const buttonsY = this.slidersY + 4 * 38 + 10;
|
||||
const buttonsY = this.slidersY + 5 * 38 + 10;
|
||||
const y1 = buttonsY;
|
||||
|
||||
const prevApply = this.btnApplyHover;
|
||||
@@ -1715,7 +1736,11 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
this.previewImage = null;
|
||||
if (BASIC_PARAMS.includes(rect.key)) {
|
||||
this.previewImage = null;
|
||||
} else {
|
||||
this._scheduleHeavyRender();
|
||||
}
|
||||
this.setDirtyCanvas(true);
|
||||
};
|
||||
|
||||
|
||||
+17
-5
@@ -1,6 +1,3 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2025-2026 Raykosan (RaykoStudio)
|
||||
console.log("[SPLINE 🦊] spline_mask.js LOADED!");
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
@@ -52,8 +49,8 @@ app.registerExtension({
|
||||
node.imageList = [];
|
||||
|
||||
node.buttons = [
|
||||
{ label: "🎨 IMAGE", color: "#2196F3", callback: () => node.showImageSelector(), hover: false },
|
||||
{ label: "🖼️ UPLOAD", color: "#4CAF50", callback: () => node.triggerFileUpload(), hover: false },
|
||||
{ label: "📂 INPUT", color: "#2196F3", callback: () => node.showImageSelector(), hover: false },
|
||||
{ label: "🖥️ UPLOAD", color: "#4CAF50", callback: () => node.triggerFileUpload(), hover: false },
|
||||
{ label: "🔴 CLEAR", color: "#dc3545", callback: () => node.clearPoints(), hover: false }
|
||||
];
|
||||
|
||||
@@ -499,6 +496,20 @@ app.registerExtension({
|
||||
const btnY = h - 45;
|
||||
const btnW = (w - 50) / 3;
|
||||
|
||||
// === РАЗМЕРЫ ИЗОБРАЖЕНИЯ НАД ПРЕВЬЮ ===
|
||||
if (this.imageLoaded && this.image) {
|
||||
ctx.fillStyle = "#888";
|
||||
ctx.font = "11px sans-serif";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillText(
|
||||
`${this.image.width} × ${this.image.height}`,
|
||||
w / 2,
|
||||
titleBarHeight + widgetsHeight - 13
|
||||
);
|
||||
}
|
||||
// ====================================
|
||||
|
||||
const startY = titleBarHeight + widgetsHeight + padding;
|
||||
const availableHeight = h - startY - footerHeight - padding;
|
||||
const availableWidth = w - (padding * 2);
|
||||
@@ -527,6 +538,7 @@ app.registerExtension({
|
||||
ctx.fillStyle = "#555";
|
||||
ctx.font = "14px Arial";
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText("Select Image...", w / 2, startY + availableHeight / 2);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user