color picker

This commit is contained in:
ryanontheinstide
2025-01-18 11:09:50 -05:00
parent d8fe7df17e
commit cb6525da82
2 changed files with 314 additions and 0 deletions
+5
View File
@@ -176,8 +176,10 @@ from .nodes.utility_nodes import (
from .nodes.images.image_utility_nodes import (
DyeImage,
ColorPicker,
ImageCASBatch,
ImageScaleToTarget
)
from .nodes.masks.flex_masks import (
@@ -522,6 +524,7 @@ NODE_CLASS_MAPPINGS = {
"FlexImageHorizontalToVertical":FlexImageHorizontalToVertical,
#visulizers
"ProximityVisualizer": ProximityVisualizer,
"EffectVisualizer": EffectVisualizer,
@@ -529,12 +532,14 @@ NODE_CLASS_MAPPINGS = {
#garb
"DyeImage": DyeImage,
"ColorPicker": ColorPicker,
"ImageCASBatch": ImageCASBatch,
"ImageScaleToTarget": ImageScaleToTarget,
"MovingShape": MovingShape,
"_mfc": _mfc,
"TextMaskNode": TextMaskNode,
#TODO: make useful
# "MaskCompositePlus": MaskCompositePlus,
+309
View File
@@ -0,0 +1,309 @@
import { app } from "../../scripts/app.js";
app.registerExtension({
name: "org.ryanontheinside.color_picker",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "ColorPicker") {
// Save original methods
const onNodeCreated = nodeType.prototype.onNodeCreated;
const onRemoved = nodeType.prototype.onRemoved;
const onDrawForeground = nodeType.prototype.onDrawForeground;
// Override the onNodeCreated method
nodeType.prototype.onNodeCreated = function() {
console.log("ColorPicker: onNodeCreated called");
const r = onNodeCreated?.apply(this, arguments);
// Initialize internal values
this.currentColor = "#FF0000";
this.currentRGB = "255,0,0";
this.currentHue = 0;
// Make node resizable
this.flags = this.flags || {};
this.flags.resizable = true;
// Set initial size - matching drawable feature approach
this.size = [350, 450];
return r;
};
// Override the onDrawForeground method
nodeType.prototype.onDrawForeground = function(ctx) {
if (onDrawForeground) {
onDrawForeground.apply(this, arguments);
}
if (this.flags.collapsed) return;
// Calculate widget area height
const widgetHeight = 90; // Height for all widgets
const margin = 10;
// Calculate picker area (remaining space after widgets and margins)
const pickerWidth = this.size[0] - margin * 2;
const pickerHeight = this.size[1] - widgetHeight - margin * 2;
const pickerX = margin;
const pickerY = widgetHeight + margin;
// Draw color picker background
ctx.fillStyle = "#2d2d2d";
ctx.strokeStyle = "#666";
ctx.lineWidth = 1;
ctx.fillRect(pickerX, pickerY, pickerWidth, pickerHeight);
ctx.strokeRect(pickerX, pickerY, pickerWidth, pickerHeight);
// Draw color spectrum in top half
const spectrumHeight = pickerHeight / 2;
const gradient = ctx.createLinearGradient(pickerX, pickerY, pickerWidth + pickerX, pickerY);
// Add rainbow colors
gradient.addColorStop(0, "#FF0000"); // Red
gradient.addColorStop(0.17, "#FF00FF"); // Magenta
gradient.addColorStop(0.33, "#0000FF"); // Blue
gradient.addColorStop(0.5, "#00FFFF"); // Cyan
gradient.addColorStop(0.67, "#00FF00"); // Green
gradient.addColorStop(0.83, "#FFFF00"); // Yellow
gradient.addColorStop(1, "#FF0000"); // Red
ctx.fillStyle = gradient;
ctx.fillRect(pickerX + 2, pickerY + 2, pickerWidth - 4, spectrumHeight - 4);
// Draw brightness gradient in bottom half
const brightnessY = pickerY + spectrumHeight;
const brightnessGradient = ctx.createLinearGradient(pickerX, brightnessY, pickerX, pickerY + pickerHeight);
brightnessGradient.addColorStop(0, "rgba(255, 255, 255, 1)");
brightnessGradient.addColorStop(0.5, "rgba(255, 255, 255, 0)");
brightnessGradient.addColorStop(0.5, "rgba(0, 0, 0, 0)");
brightnessGradient.addColorStop(1, "rgba(0, 0, 0, 1)");
ctx.fillStyle = brightnessGradient;
ctx.fillRect(pickerX + 2, brightnessY + 2, pickerWidth - 4, spectrumHeight - 4);
// Draw current color indicator
if (this.currentColor) {
// Draw current color preview in corner
ctx.fillStyle = this.currentColor;
ctx.strokeStyle = "#fff";
ctx.lineWidth = 2;
// Draw a circle with the current color
const indicatorSize = 20;
const indicatorX = pickerX + pickerWidth - indicatorSize - 5;
const indicatorY = pickerY + pickerHeight - indicatorSize - 5;
ctx.beginPath();
ctx.arc(indicatorX, indicatorY, indicatorSize/2, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
// Draw position indicator in color picker
if (this.currentHue !== undefined) {
const x = pickerX + (this.currentHue / 360) * pickerWidth;
let y;
// Convert RGB to HSV to get value
const r = parseInt(this.currentColor.slice(1, 3), 16) / 255;
const g = parseInt(this.currentColor.slice(3, 5), 16) / 255;
const b = parseInt(this.currentColor.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const value = max * 100;
if (value >= 50) {
// Top half
y = pickerY + ((100 - value) / 100) * (pickerHeight / 2);
} else {
// Bottom half
y = pickerY + pickerHeight/2 + ((50 - value) / 50) * (pickerHeight / 2);
}
// Draw position indicator
ctx.beginPath();
ctx.arc(x, y, 5, 0, Math.PI * 2);
ctx.strokeStyle = value > 50 ? "#000" : "#fff";
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = value > 50 ? "#fff" : "#000";
ctx.fill();
}
}
// Store click area for later
this.colorPickerArea = {
x: pickerX,
y: pickerY,
width: pickerWidth,
height: pickerHeight
};
};
// Add click handler
nodeType.prototype.onMouseDown = function(e, pos, graphcanvas) {
if (this.flags.collapsed) return false;
// Get canvas coordinates like in drawable_feature.js
const scale = graphcanvas.ds.scale;
const offset = graphcanvas.ds.offset;
// Calculate node position in canvas space
const nodeX = this.pos[0] * scale + offset[0];
const nodeY = this.pos[1] * scale + offset[1];
// Get mouse position relative to canvas
const rect = graphcanvas.canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Calculate click position relative to node
const localX = (mouseX - nodeX) / scale;
const localY = (mouseY - nodeY) / scale;
console.log("=== Color Picker Click Debug ===");
console.log("Scale:", scale);
console.log("Offset:", offset);
console.log("Node pos:", this.pos);
console.log("Node canvas pos:", nodeX, nodeY);
console.log("Mouse pos:", mouseX, mouseY);
console.log("Local pos:", localX, localY);
console.log("Picker area:", this.colorPickerArea);
if (this.colorPickerArea) {
const { x: px, y: py, width, height } = this.colorPickerArea;
// Check if click is within picker area
if (localX >= px && localX <= px + width && localY >= py && localY <= py + height) {
// Calculate relative position within color picker
const relX = (localX - px) / width;
const relY = (localY - py) / height;
console.log("Click in picker area!");
console.log("Relative position:", relX, relY);
// Get base hue from X position
const hue = Math.max(0, Math.min(360, relX * 360));
// Get saturation and value based on Y position
let saturation = 100;
let value = 100;
if (relY <= 0.5) {
// Top half - full saturation, value varies from 100 to 50
value = 100 - (relY * 2 * 100);
} else {
// Bottom half - value varies from 50 to 0
value = 50 - ((relY - 0.5) * 2 * 100);
}
value = Math.max(0, Math.min(100, value));
console.log("HSV values:", { hue, saturation, value });
// Convert HSV to RGB
const rgb = this.hsvToRgb(hue, saturation, value);
const hexColor = '#' + rgb.map(x => {
const hex = Math.round(Math.max(0, Math.min(255, x))).toString(16);
return hex.length === 1 ? '0' + hex : hex;
}).join('').toUpperCase();
console.log("Final color:", hexColor);
// Update color
this.updateColor(hexColor);
// Trigger widget callbacks
const colorWidget = this.widgets.find(w => w.name === "color");
const rgbWidget = this.widgets.find(w => w.name === "rgb_value");
const hueWidget = this.widgets.find(w => w.name === "hue");
if (colorWidget?.callback) colorWidget.callback(hexColor);
if (rgbWidget?.callback) rgbWidget.callback(`${Math.round(rgb[0])},${Math.round(rgb[1])},${Math.round(rgb[2])}`);
if (hueWidget?.callback) hueWidget.callback(hue);
return true;
} else {
console.log("Click outside picker area");
}
}
return false;
};
// Add HSV to RGB conversion
nodeType.prototype.hsvToRgb = function(h, s, v) {
s = s / 100;
v = v / 100;
const i = Math.floor(h / 60);
const f = h / 60 - i;
const p = v * (1 - s);
const q = v * (1 - f * s);
const t = v * (1 - (1 - f) * s);
let r, g, b;
switch (i % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
return [r * 255, g * 255, b * 255];
};
// Update color values
nodeType.prototype.updateColor = function(hexColor) {
console.log("ColorPicker: Updating color values for", hexColor);
// Store hex color
this.currentColor = hexColor.toUpperCase();
// Convert hex to RGB
const r = parseInt(hexColor.slice(1, 3), 16);
const g = parseInt(hexColor.slice(3, 5), 16);
const b = parseInt(hexColor.slice(5, 7), 16);
// Store RGB
this.currentRGB = `${r},${g},${b}`;
// Convert RGB to HSV to get hue
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
if (max === min) {
h = 0;
} else {
const d = max - min;
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h *= 60;
}
// Store hue as integer
this.currentHue = Math.round(h);
// Update hidden widgets
const widgets = this.widgets || [];
for (const w of widgets) {
if (w.name === "color") w.value = this.currentColor;
if (w.name === "rgb_value") w.value = this.currentRGB;
if (w.name === "hue") w.value = this.currentHue;
}
// Force redraw
this.setDirtyCanvas(true, true);
// Update outputs
this.setOutputData(0, this.currentColor);
this.setOutputData(1, this.currentRGB);
this.setOutputData(2, this.currentHue);
};
}
}
});