mirror of
https://git.datalinker.icu/kijai/ComfyUI-KJNodes.git
synced 2025-12-10 05:15:05 +08:00
SplineEditor rework
This commit is contained in:
parent
bbb28a0fc9
commit
971bf375e5
@ -5,6 +5,9 @@ from PIL import Image, ImageDraw, ImageFont, ImageColor, ImageFilter
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from ..utility.utility import pil2tensor
|
from ..utility.utility import pil2tensor
|
||||||
import folder_paths
|
import folder_paths
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
from comfy.utils import common_upscale
|
from comfy.utils import common_upscale
|
||||||
|
|
||||||
def plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt):
|
def plot_coordinates_to_tensor(coordinates, height, width, bbox_height, bbox_width, size_multiplier, prompt):
|
||||||
@ -150,6 +153,7 @@ class SplineEditor:
|
|||||||
"optional": {
|
"optional": {
|
||||||
"min_value": ("FLOAT", {"default": 0.0, "min": -10000.0, "max": 10000.0, "step": 0.01}),
|
"min_value": ("FLOAT", {"default": 0.0, "min": -10000.0, "max": 10000.0, "step": 0.01}),
|
||||||
"max_value": ("FLOAT", {"default": 1.0, "min": -10000.0, "max": 10000.0, "step": 0.01}),
|
"max_value": ("FLOAT", {"default": 1.0, "min": -10000.0, "max": 10000.0, "step": 0.01}),
|
||||||
|
"bg_image": ("IMAGE", ),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +200,8 @@ output types:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation,
|
def splinedata(self, mask_width, mask_height, coordinates, float_output_type, interpolation,
|
||||||
points_to_sample, sampling_method, points_store, tension, repeat_output, min_value=0.0, max_value=1.0):
|
points_to_sample, sampling_method, points_store, tension, repeat_output,
|
||||||
|
min_value=0.0, max_value=1.0, bg_image=None):
|
||||||
|
|
||||||
coordinates = json.loads(coordinates)
|
coordinates = json.loads(coordinates)
|
||||||
normalized = []
|
normalized = []
|
||||||
@ -226,7 +231,22 @@ output types:
|
|||||||
masks_out = torch.stack(mask_tensors)
|
masks_out = torch.stack(mask_tensors)
|
||||||
masks_out = masks_out.repeat(repeat_output, 1, 1, 1)
|
masks_out = masks_out.repeat(repeat_output, 1, 1, 1)
|
||||||
masks_out = masks_out.mean(dim=-1)
|
masks_out = masks_out.mean(dim=-1)
|
||||||
return (masks_out, json.dumps(coordinates), out_floats, len(out_floats) , json.dumps(normalized))
|
if bg_image is None:
|
||||||
|
return (masks_out, json.dumps(coordinates), out_floats, len(out_floats) , json.dumps(normalized))
|
||||||
|
else:
|
||||||
|
transform = transforms.ToPILImage()
|
||||||
|
image = transform(bg_image[0].permute(2, 0, 1))
|
||||||
|
buffered = io.BytesIO()
|
||||||
|
image.save(buffered, format="JPEG", quality=75)
|
||||||
|
|
||||||
|
# Step 3: Encode the image bytes to a Base64 string
|
||||||
|
img_bytes = buffered.getvalue()
|
||||||
|
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
||||||
|
return {
|
||||||
|
"ui": {"bg_image": [img_base64]},
|
||||||
|
"result":(masks_out, json.dumps(coordinates), out_floats, len(out_floats) , json.dumps(normalized))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CreateShapeMaskOnPath:
|
class CreateShapeMaskOnPath:
|
||||||
|
|
||||||
@ -335,8 +355,8 @@ Locations are center locations.
|
|||||||
"coordinates": ("STRING", {"forceInput": True}),
|
"coordinates": ("STRING", {"forceInput": True}),
|
||||||
"frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}),
|
"frame_width": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}),
|
||||||
"frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}),
|
"frame_height": ("INT", {"default": 512,"min": 16, "max": 4096, "step": 1}),
|
||||||
"shape_width": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}),
|
"shape_width": ("INT", {"default": 128,"min": 2, "max": 4096, "step": 1}),
|
||||||
"shape_height": ("INT", {"default": 128,"min": 8, "max": 4096, "step": 1}),
|
"shape_height": ("INT", {"default": 128,"min": 2, "max": 4096, "step": 1}),
|
||||||
"shape_color": ("STRING", {"default": 'white'}),
|
"shape_color": ("STRING", {"default": 'white'}),
|
||||||
"bg_color": ("STRING", {"default": 'black'}),
|
"bg_color": ("STRING", {"default": 'black'}),
|
||||||
"blur_radius": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100, "step": 0.1}),
|
"blur_radius": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100, "step": 0.1}),
|
||||||
@ -1308,9 +1328,6 @@ you can clear the image from the context menu by right clicking on the canvas
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def pointdata(self, points_store, bbox_store, width, height, coordinates, neg_coordinates, normalize, bboxes, bbox_format="xyxy", bg_image=None):
|
def pointdata(self, points_store, bbox_store, width, height, coordinates, neg_coordinates, normalize, bboxes, bbox_format="xyxy", bg_image=None):
|
||||||
import io
|
|
||||||
import base64
|
|
||||||
|
|
||||||
coordinates = json.loads(coordinates)
|
coordinates = json.loads(coordinates)
|
||||||
pos_coordinates = []
|
pos_coordinates = []
|
||||||
for coord in coordinates:
|
for coord in coordinates:
|
||||||
|
|||||||
@ -110,6 +110,10 @@ app.registerExtension({
|
|||||||
this.uuid = makeUUID()
|
this.uuid = makeUUID()
|
||||||
element.id = `spline-editor-${this.uuid}`
|
element.id = `spline-editor-${this.uuid}`
|
||||||
|
|
||||||
|
// fake image widget to allow copy/paste
|
||||||
|
const fakeimagewidget = this.addWidget("COMBO", "image", null, () => { }, {});
|
||||||
|
hideWidgetForGood(this, fakeimagewidget)
|
||||||
|
|
||||||
this.splineEditor = this.addDOMWidget(nodeData.name, "SplineEditorWidget", element, {
|
this.splineEditor = this.addDOMWidget(nodeData.name, "SplineEditorWidget", element, {
|
||||||
serialize: false,
|
serialize: false,
|
||||||
hideOnZoom: false,
|
hideOnZoom: false,
|
||||||
@ -169,15 +173,15 @@ app.registerExtension({
|
|||||||
this.contextMenu.appendChild(menuItem);
|
this.contextMenu.appendChild(menuItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild( this.contextMenu);
|
document.body.appendChild(this.contextMenu);
|
||||||
|
|
||||||
this.addWidget("button", "New spline", null, () => {
|
this.addWidget("button", "New spline", null, () => {
|
||||||
if (!this.properties || !("points" in this.properties)) {
|
if (!this.properties || !("points" in this.properties)) {
|
||||||
createSplineEditor(this)
|
this.editor = new SplineEditor(this);
|
||||||
this.addProperty("points", this.constructor.type, "string");
|
this.addProperty("points", this.constructor.type, "string");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
createSplineEditor(this, true)
|
this.editor = new SplineEditor(this, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -188,9 +192,21 @@ app.registerExtension({
|
|||||||
this.splineEditor.parentEl.id = `spline-editor-${this.uuid}`
|
this.splineEditor.parentEl.id = `spline-editor-${this.uuid}`
|
||||||
element.appendChild(this.splineEditor.parentEl);
|
element.appendChild(this.splineEditor.parentEl);
|
||||||
|
|
||||||
chainCallback(this, "onConfigure", function() {
|
chainCallback(this, "onConfigure", function () {
|
||||||
createSplineEditor(this);
|
try {
|
||||||
});
|
this.editor = new SplineEditor(this);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("An error occurred while configuring the editor:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
chainCallback(this, "onExecuted", function (message) {
|
||||||
|
let bg_image = message["bg_image"];
|
||||||
|
this.properties.imgData = {
|
||||||
|
name: "bg_image",
|
||||||
|
base64: bg_image
|
||||||
|
};
|
||||||
|
this.editor.refreshBackgroundImage(this);
|
||||||
|
});
|
||||||
|
|
||||||
}); // onAfterGraphConfigured
|
}); // onAfterGraphConfigured
|
||||||
}//node created
|
}//node created
|
||||||
@ -198,259 +214,159 @@ app.registerExtension({
|
|||||||
})//register
|
})//register
|
||||||
|
|
||||||
|
|
||||||
function createSplineEditor(context, reset=false) {
|
class SplineEditor{
|
||||||
|
constructor(context, reset = false) {
|
||||||
|
this.node = context;
|
||||||
|
this.reset=reset;
|
||||||
|
const self = this;
|
||||||
console.log("creatingSplineEditor")
|
console.log("creatingSplineEditor")
|
||||||
|
|
||||||
|
this.node.pasteFile = (file) => {
|
||||||
|
if (file.type.startsWith("image/")) {
|
||||||
|
this.handleImageFile(file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.node.onDragOver = function (e) {
|
||||||
|
if (e.dataTransfer && e.dataTransfer.items) {
|
||||||
|
return [...e.dataTransfer.items].some(f => f.kind === "file" && f.type.startsWith("image/"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// On drop upload files
|
||||||
|
this.node.onDragDrop = (e) => {
|
||||||
|
console.log("onDragDrop called");
|
||||||
|
let handled = false;
|
||||||
|
for (const file of e.dataTransfer.files) {
|
||||||
|
if (file.type.startsWith("image/")) {
|
||||||
|
this.handleImageFile(file);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handled;
|
||||||
|
};
|
||||||
|
|
||||||
// context menu
|
// context menu
|
||||||
function createContextMenu() {
|
this.createContextMenu();
|
||||||
document.addEventListener('contextmenu', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener('click', function(e) {
|
|
||||||
if (!context.contextMenu.contains(e.target)) {
|
|
||||||
context.contextMenu.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
context.menuItems.forEach((menuItem, index) => {
|
this.dotShape = "circle";
|
||||||
menuItem.addEventListener('click', function(e) {
|
this.drawSamplePoints = false;
|
||||||
e.preventDefault();
|
|
||||||
// Logic specific to each menu item based on its index or id
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
e.preventDefault();
|
|
||||||
if (!drawHandles) {
|
|
||||||
drawHandles = true
|
|
||||||
vis.add(pv.Line)
|
|
||||||
.data(() => points.map((point, index) => ({
|
|
||||||
start: point,
|
|
||||||
end: [index]
|
|
||||||
})))
|
|
||||||
.left(d => d.start.x)
|
|
||||||
.top(d => d.start.y)
|
|
||||||
.interpolate("linear")
|
|
||||||
.tension(0) // Straight lines
|
|
||||||
.strokeStyle("#ff7f0e") // Same color as control points
|
|
||||||
.lineWidth(1)
|
|
||||||
.visible(() => drawHandles);
|
|
||||||
vis.render();
|
|
||||||
} else {
|
|
||||||
drawHandles = false
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
context.contextMenu.style.display = 'none';
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
e.preventDefault();
|
|
||||||
drawSamplePoints = !drawSamplePoints;
|
|
||||||
updatePath();
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
e.preventDefault();
|
|
||||||
if (dotShape == "circle"){
|
|
||||||
dotShape = "triangle"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
dotShape = "circle"
|
|
||||||
}
|
|
||||||
console.log(dotShape)
|
|
||||||
updatePath();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
// Create file input element
|
|
||||||
const fileInput = document.createElement('input');
|
|
||||||
fileInput.type = 'file';
|
|
||||||
fileInput.accept = 'image/*'; // Accept only image files
|
|
||||||
|
|
||||||
// Listen for file selection
|
|
||||||
fileInput.addEventListener('change', function(event) {
|
|
||||||
const file = event.target.files[0]; // Get the selected file
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
// Create a URL for the selected file
|
|
||||||
const imageUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
// Set the backgroundImage with the new URL and make it visible
|
|
||||||
backgroundImage
|
|
||||||
.url(imageUrl)
|
|
||||||
.visible(true)
|
|
||||||
.root.render();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the backgroundImage is already visible, hide it. Otherwise, show file input.
|
|
||||||
if (backgroundImage.visible()) {
|
|
||||||
backgroundImage.visible(false)
|
|
||||||
.root.render();
|
|
||||||
} else {
|
|
||||||
// Trigger the file input dialog
|
|
||||||
fileInput.click();
|
|
||||||
}
|
|
||||||
context.contextMenu.style.display = 'none';
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
e.preventDefault();
|
|
||||||
points.reverse();
|
|
||||||
updatePath();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var dotShape = "circle";
|
|
||||||
var drawSamplePoints = false;
|
|
||||||
|
|
||||||
createContextMenu();
|
|
||||||
function updatePath() {
|
|
||||||
if (samplingMethod != "controlpoints") {
|
|
||||||
var coords = samplePoints(pathElements[0], points_to_sample, samplingMethod, w);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var coords = points
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawSamplePoints) {
|
|
||||||
if (pointsLayer) {
|
|
||||||
// Update the data of the existing points layer
|
|
||||||
pointsLayer.data(coords);
|
|
||||||
} else {
|
|
||||||
// Create the points layer if it doesn't exist
|
|
||||||
pointsLayer = vis.add(pv.Dot)
|
|
||||||
.data(coords)
|
|
||||||
.left(function(d) { return d.x; })
|
|
||||||
.top(function(d) { return d.y; })
|
|
||||||
.radius(5) // Adjust the radius as needed
|
|
||||||
.fillStyle("red") // Change the color as needed
|
|
||||||
.strokeStyle("black") // Change the stroke color as needed
|
|
||||||
.lineWidth(1); // Adjust the line width as needed
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (pointsLayer) {
|
|
||||||
// Remove the points layer
|
|
||||||
pointsLayer.data([]);
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let coordsString = JSON.stringify(coords);
|
|
||||||
pointsStoreWidget.value = JSON.stringify(points);
|
|
||||||
if (coordWidget) {
|
|
||||||
coordWidget.value = coordsString;
|
|
||||||
}
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reset && context.splineEditor.element) {
|
if (reset && context.splineEditor.element) {
|
||||||
context.splineEditor.element.innerHTML = ''; // Clear the container
|
context.splineEditor.element.innerHTML = ''; // Clear the container
|
||||||
}
|
}
|
||||||
const coordWidget = context.widgets.find(w => w.name === "coordinates");
|
this.coordWidget = context.widgets.find(w => w.name === "coordinates");
|
||||||
const interpolationWidget = context.widgets.find(w => w.name === "interpolation");
|
this.interpolationWidget = context.widgets.find(w => w.name === "interpolation");
|
||||||
const pointsWidget = context.widgets.find(w => w.name === "points_to_sample");
|
this.pointsWidget = context.widgets.find(w => w.name === "points_to_sample");
|
||||||
const pointsStoreWidget = context.widgets.find(w => w.name === "points_store");
|
this.pointsStoreWidget = context.widgets.find(w => w.name === "points_store");
|
||||||
const tensionWidget = context.widgets.find(w => w.name === "tension");
|
this.tensionWidget = context.widgets.find(w => w.name === "tension");
|
||||||
const minValueWidget = context.widgets.find(w => w.name === "min_value");
|
this.minValueWidget = context.widgets.find(w => w.name === "min_value");
|
||||||
const maxValueWidget = context.widgets.find(w => w.name === "max_value");
|
this.maxValueWidget = context.widgets.find(w => w.name === "max_value");
|
||||||
const samplingMethodWidget = context.widgets.find(w => w.name === "sampling_method");
|
this.samplingMethodWidget = context.widgets.find(w => w.name === "sampling_method");
|
||||||
const widthWidget = context.widgets.find(w => w.name === "mask_width");
|
this.widthWidget = context.widgets.find(w => w.name === "mask_width");
|
||||||
const heightWidget = context.widgets.find(w => w.name === "mask_height");
|
this.heightWidget = context.widgets.find(w => w.name === "mask_height");
|
||||||
//const segmentedWidget = context.widgets.find(w => w.name === "segmented");
|
|
||||||
|
|
||||||
var interpolation = interpolationWidget.value
|
var interpolation = this.interpolationWidget.value
|
||||||
var tension = tensionWidget.value
|
var tension = this.tensionWidget.value
|
||||||
var points_to_sample = pointsWidget.value
|
this.points_to_sample = this.pointsWidget.value
|
||||||
var rangeMin = minValueWidget.value
|
var rangeMin = this.minValueWidget.value
|
||||||
var rangeMax = maxValueWidget.value
|
var rangeMax = this.maxValueWidget.value
|
||||||
var pointsLayer = null;
|
this.pointsLayer = null;
|
||||||
var samplingMethod = samplingMethodWidget.value
|
this.samplingMethod = this.samplingMethodWidget.value
|
||||||
|
|
||||||
if (samplingMethod == "path") {
|
if (this.samplingMethod == "path") {
|
||||||
dotShape = "triangle"
|
this.dotShape = "triangle"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interpolationWidget.callback = () => {
|
this.interpolationWidget.callback = () => {
|
||||||
interpolation = interpolationWidget.value
|
interpolation = this.interpolationWidget.value
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
samplingMethodWidget.callback = () => {
|
this.samplingMethodWidget.callback = () => {
|
||||||
samplingMethod = samplingMethodWidget.value
|
this.samplingMethod = samplingMethodWidget.value
|
||||||
if (samplingMethod == "path") {
|
if (this.samplingMethod == "path") {
|
||||||
dotShape = "triangle"
|
this.dotShape = "triangle"
|
||||||
}
|
}
|
||||||
else if (samplingMethod == "controlpoints") {
|
else if (this.samplingMethod == "controlpoints") {
|
||||||
dotShape = "circle"
|
this.dotShape = "circle"
|
||||||
drawSamplePoints = true;
|
this.drawSamplePoints = true;
|
||||||
}
|
}
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
tensionWidget.callback = () => {
|
this.tensionWidget.callback = () => {
|
||||||
tension = tensionWidget.value
|
tension = this.tensionWidget.value
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
pointsWidget.callback = () => {
|
this.pointsWidget.callback = () => {
|
||||||
points_to_sample = pointsWidget.value
|
this.points_to_sample = this.pointsWidget.value
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
minValueWidget.callback = () => {
|
this.minValueWidget.callback = () => {
|
||||||
rangeMin = minValueWidget.value
|
rangeMin = this.minValueWidget.value
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
maxValueWidget.callback = () => {
|
this.maxValueWidget.callback = () => {
|
||||||
rangeMax = maxValueWidget.value
|
rangeMax = this.maxValueWidget.value
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
widthWidget.callback = () => {
|
this.widthWidget.callback = () => {
|
||||||
w = widthWidget.value;
|
w = this.widthWidget.value;
|
||||||
if (w > 256) {
|
if (w > 256) {
|
||||||
context.setSize([w + 45, context.size[1]]);
|
context.setSize([w + 45, context.size[1]]);
|
||||||
}
|
}
|
||||||
vis.width(w);
|
vis.width(w);
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
heightWidget.callback = () => {
|
this.heightWidget.callback = () => {
|
||||||
h = heightWidget.value
|
this.height = this.heightWidget.value
|
||||||
vis.height(h)
|
vis.height(this.height)
|
||||||
context.setSize([context.size[0], h + 430]);
|
context.setSize([context.size[0], this.height + 430]);
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
pointsStoreWidget.callback = () => {
|
this.pointsStoreWidget.callback = () => {
|
||||||
points = JSON.parse(pointsStoreWidget.value);
|
points = JSON.parse(this.pointsStoreWidget.value);
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize or reset points array
|
// Initialize or reset points array
|
||||||
var drawHandles = false;
|
this.drawHandles = false;
|
||||||
|
this.drawRuler = true;
|
||||||
var hoverIndex = -1;
|
var hoverIndex = -1;
|
||||||
var isDragging = false;
|
var isDragging = false;
|
||||||
var w = widthWidget.value;
|
this.width = this.widthWidget.value;
|
||||||
var h = heightWidget.value;
|
this.height = this.heightWidget.value;
|
||||||
var i = 3;
|
var i = 3;
|
||||||
let points = [];
|
this.points = [];
|
||||||
|
|
||||||
if (!reset && pointsStoreWidget.value != "") {
|
if (!reset && this.pointsStoreWidget.value != "") {
|
||||||
points = JSON.parse(pointsStoreWidget.value);
|
this.points = JSON.parse(this.pointsStoreWidget.value);
|
||||||
} else {
|
} else {
|
||||||
points = pv.range(1, 4).map((i, index) => {
|
this.points = pv.range(1, 4).map((i, index) => {
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
// First point at the bottom-left corner
|
// First point at the bottom-left corner
|
||||||
return { x: 0, y: h };
|
return { x: 0, y: this.height };
|
||||||
} else if (index === 2) {
|
} else if (index === 2) {
|
||||||
// Last point at the top-right corner
|
// Last point at the top-right corner
|
||||||
return { x: w, y: 0 };
|
return { x: this.width, y: 0 };
|
||||||
} else {
|
} else {
|
||||||
// Other points remain as they were
|
// Other points remain as they were
|
||||||
return {
|
return {
|
||||||
x: i * w / 5,
|
x: i * this.width / 5,
|
||||||
y: 50 + Math.random() * (h - 100)
|
y: 50 + Math.random() * (this.height - 100)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
pointsStoreWidget.value = JSON.stringify(points);
|
this.pointsStoreWidget.value = JSON.stringify(this.points);
|
||||||
}
|
}
|
||||||
|
|
||||||
var vis = new pv.Panel()
|
this.vis = new pv.Panel()
|
||||||
.width(w)
|
.width(this.width)
|
||||||
.height(h)
|
.height(this.height)
|
||||||
.fillStyle("#222")
|
.fillStyle("#222")
|
||||||
.strokeStyle("gray")
|
.strokeStyle("gray")
|
||||||
.lineWidth(2)
|
.lineWidth(2)
|
||||||
@ -462,8 +378,8 @@ function createSplineEditor(context, reset=false) {
|
|||||||
x: this.mouse().x / app.canvas.ds.scale,
|
x: this.mouse().x / app.canvas.ds.scale,
|
||||||
y: this.mouse().y / app.canvas.ds.scale
|
y: this.mouse().y / app.canvas.ds.scale
|
||||||
};
|
};
|
||||||
i = points.push(scaledMouse) - 1;
|
i = self.points.push(scaledMouse) - 1;
|
||||||
updatePath();
|
self.updatePath();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
else if (pv.event.ctrlKey) {
|
else if (pv.event.ctrlKey) {
|
||||||
@ -474,32 +390,33 @@ function createSplineEditor(context, reset=false) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Find the two closest points to the clicked location
|
// Find the two closest points to the clicked location
|
||||||
let { point1Index, point2Index } = findClosestPoints(points, clickedPoint);
|
let { point1Index, point2Index } = self.findClosestPoints(self.points, clickedPoint);
|
||||||
|
|
||||||
// Calculate the midpoint between the two closest points
|
// Calculate the midpoint between the two closest points
|
||||||
let midpoint = {
|
let midpoint = {
|
||||||
x: (points[point1Index].x + points[point2Index].x) / 2,
|
x: (self.points[point1Index].x + self.points[point2Index].x) / 2,
|
||||||
y: (points[point1Index].y + points[point2Index].y) / 2
|
y: (self.points[point1Index].y + self.points[point2Index].y) / 2
|
||||||
};
|
};
|
||||||
|
|
||||||
// Insert the midpoint into the array
|
// Insert the midpoint into the array
|
||||||
points.splice(point2Index, 0, midpoint);
|
self.points.splice(point2Index, 0, midpoint);
|
||||||
i = point2Index;
|
i = point2Index;
|
||||||
updatePath();
|
self.updatePath();
|
||||||
}
|
}
|
||||||
else if (pv.event.button === 2) {
|
else if (pv.event.button === 2) {
|
||||||
context.contextMenu.style.display = 'block';
|
self.node.contextMenu.style.display = 'block';
|
||||||
context.contextMenu.style.left = `${pv.event.clientX}px`;
|
self.node.contextMenu.style.left = `${pv.event.clientX}px`;
|
||||||
context.contextMenu.style.top = `${pv.event.clientY}px`;
|
self.node.contextMenu.style.top = `${pv.event.clientY}px`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
var backgroundImage = vis.add(pv.Image)
|
this.backgroundImage = this.vis.add(pv.Image).visible(false)
|
||||||
.visible(false)
|
|
||||||
vis.add(pv.Rule)
|
this.vis.add(pv.Rule)
|
||||||
.data(pv.range(0, h, 64))
|
.data(pv.range(0, this.height, 64))
|
||||||
.bottom(d => d)
|
.bottom(d => d)
|
||||||
.strokeStyle("gray")
|
.strokeStyle("gray")
|
||||||
.lineWidth(3)
|
.lineWidth(3)
|
||||||
|
.visible(() => self.drawRuler)
|
||||||
|
|
||||||
// vis.add(pv.Rule)
|
// vis.add(pv.Rule)
|
||||||
// .data(pv.range(0, points_to_sample, 1))
|
// .data(pv.range(0, points_to_sample, 1))
|
||||||
@ -507,8 +424,8 @@ function createSplineEditor(context, reset=false) {
|
|||||||
// .strokeStyle("gray")
|
// .strokeStyle("gray")
|
||||||
// .lineWidth(2)
|
// .lineWidth(2)
|
||||||
|
|
||||||
vis.add(pv.Line)
|
this.vis.add(pv.Line)
|
||||||
.data(() => points)
|
.data(() => this.points)
|
||||||
.left(d => d.x)
|
.left(d => d.x)
|
||||||
.top(d => d.y)
|
.top(d => d.y)
|
||||||
.interpolate(() => interpolation)
|
.interpolate(() => interpolation)
|
||||||
@ -517,19 +434,19 @@ function createSplineEditor(context, reset=false) {
|
|||||||
.strokeStyle(pv.Colors.category10().by(pv.index))
|
.strokeStyle(pv.Colors.category10().by(pv.index))
|
||||||
.lineWidth(3)
|
.lineWidth(3)
|
||||||
|
|
||||||
vis.add(pv.Dot)
|
this.vis.add(pv.Dot)
|
||||||
.data(() => points)
|
.data(() => this.points)
|
||||||
.left(d => d.x)
|
.left(d => d.x)
|
||||||
.top(d => d.y)
|
.top(d => d.y)
|
||||||
.radius(10)
|
.radius(10)
|
||||||
.shape(function() {
|
.shape(function() {
|
||||||
return dotShape;
|
return self.dotShape;
|
||||||
})
|
})
|
||||||
.angle(function() {
|
.angle(function() {
|
||||||
const index = this.index;
|
const index = this.index;
|
||||||
let angle = 0;
|
let angle = 0;
|
||||||
|
|
||||||
if (dotShape === "triangle") {
|
if (this.dotShape === "triangle") {
|
||||||
let dxNext = 0, dyNext = 0;
|
let dxNext = 0, dyNext = 0;
|
||||||
if (index < points.length - 1) {
|
if (index < points.length - 1) {
|
||||||
dxNext = points[index + 1].x - points[index].x;
|
dxNext = points[index + 1].x - points[index].x;
|
||||||
@ -560,15 +477,15 @@ function createSplineEditor(context, reset=false) {
|
|||||||
i = this.index;
|
i = this.index;
|
||||||
hoverIndex = this.index;
|
hoverIndex = this.index;
|
||||||
isDragging = true;
|
isDragging = true;
|
||||||
if (pv.event.button === 2 && i !== 0 && i !== points.length - 1) {
|
if (pv.event.button === 2 && i !== 0 && i !== self.points.length - 1) {
|
||||||
points.splice(i--, 1);
|
self.points.splice(i--, 1);
|
||||||
vis.render();
|
self.vis.render();
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
})
|
})
|
||||||
.event("dragend", function() {
|
.event("dragend", function() {
|
||||||
if (this.pathElements !== null) {
|
if (this.pathElements !== null) {
|
||||||
updatePath();
|
self.updatePath();
|
||||||
}
|
}
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
})
|
})
|
||||||
@ -576,58 +493,283 @@ function createSplineEditor(context, reset=false) {
|
|||||||
let adjustedX = this.mouse().x / app.canvas.ds.scale; // Adjust the new X position by the inverse of the scale factor
|
let adjustedX = this.mouse().x / app.canvas.ds.scale; // Adjust the new X position by the inverse of the scale factor
|
||||||
let adjustedY = this.mouse().y / app.canvas.ds.scale; // Adjust the new Y position by the inverse of the scale factor
|
let adjustedY = this.mouse().y / app.canvas.ds.scale; // Adjust the new Y position by the inverse of the scale factor
|
||||||
// Determine the bounds of the vis.Panel
|
// Determine the bounds of the vis.Panel
|
||||||
const panelWidth = vis.width();
|
const panelWidth = self.vis.width();
|
||||||
const panelHeight = vis.height();
|
const panelHeight = self.vis.height();
|
||||||
|
|
||||||
// Adjust the new position if it would place the dot outside the bounds of the vis.Panel
|
// Adjust the new position if it would place the dot outside the bounds of the vis.Panel
|
||||||
adjustedX = Math.max(0, Math.min(panelWidth, adjustedX));
|
adjustedX = Math.max(0, Math.min(panelWidth, adjustedX));
|
||||||
adjustedY = Math.max(0, Math.min(panelHeight, adjustedY));
|
adjustedY = Math.max(0, Math.min(panelHeight, adjustedY));
|
||||||
points[this.index] = { x: adjustedX, y: adjustedY }; // Update the point's position
|
self.points[this.index] = { x: adjustedX, y: adjustedY }; // Update the point's position
|
||||||
vis.render(); // Re-render the visualization to reflect the new position
|
self.vis.render(); // Re-render the visualization to reflect the new position
|
||||||
})
|
})
|
||||||
.event("mouseover", function() {
|
.event("mouseover", function() {
|
||||||
hoverIndex = this.index; // Set the hover index to the index of the hovered dot
|
hoverIndex = this.index; // Set the hover index to the index of the hovered dot
|
||||||
vis.render(); // Re-render the visualization
|
self.vis.render(); // Re-render the visualization
|
||||||
})
|
})
|
||||||
.event("mouseout", function() {
|
.event("mouseout", function() {
|
||||||
!isDragging && (hoverIndex = -1); // Reset the hover index when the mouse leaves the dot
|
!isDragging && (hoverIndex = -1); // Reset the hover index when the mouse leaves the dot
|
||||||
vis.render(); // Re-render the visualization
|
self.vis.render(); // Re-render the visualization
|
||||||
})
|
})
|
||||||
.anchor("center")
|
.anchor("center")
|
||||||
.add(pv.Label)
|
.add(pv.Label)
|
||||||
.visible(function() {
|
.visible(function() {
|
||||||
return hoverIndex === this.index; // Only show the label for the hovered dot
|
return hoverIndex === this.index; // Only show the label for the hovered dot
|
||||||
})
|
})
|
||||||
.left(d => d.x < w / 2 ? d.x + 80 : d.x - 70) // Shift label to right if on left half, otherwise shift to left
|
.left(d => d.x < this.width / 2 ? d.x + 80 : d.x - 70) // Shift label to right if on left half, otherwise shift to left
|
||||||
.top(d => d.y < h / 2 ? d.y + 20 : d.y - 20) // Shift label down if on top half, otherwise shift up
|
.top(d => d.y < this.height / 2 ? d.y + 20 : d.y - 20) // Shift label down if on top half, otherwise shift up
|
||||||
.font(12 + "px sans-serif")
|
.font(12 + "px sans-serif")
|
||||||
.text(d => {
|
.text(d => {
|
||||||
if (samplingMethod == "path") {
|
if (this.samplingMethod == "path") {
|
||||||
return `X: ${Math.round(d.x)}, Y: ${Math.round(d.y)}`;
|
return `X: ${Math.round(d.x)}, Y: ${Math.round(d.y)}`;
|
||||||
} else {
|
} else {
|
||||||
let frame = Math.round((d.x / w) * points_to_sample);
|
let frame = Math.round((d.x / self.width) * this.points_to_sample);
|
||||||
let normalizedY = (1.0 - (d.y / h) - 0.0) * (rangeMax - rangeMin) + rangeMin;
|
let normalizedY = (1.0 - (d.y / self.height) - 0.0) * (rangeMax - rangeMin) + rangeMin;
|
||||||
let normalizedX = (d.x / w);
|
let normalizedX = (d.x / self.width);
|
||||||
return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`;
|
return `F: ${frame}, X: ${normalizedX.toFixed(2)}, Y: ${normalizedY.toFixed(2)}`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.textStyle("orange")
|
.textStyle("orange")
|
||||||
|
|
||||||
vis.render();
|
if (this.points.length != 0) {
|
||||||
var svgElement = vis.canvas();
|
this.vis.render();
|
||||||
|
}
|
||||||
|
var svgElement = this.vis.canvas();
|
||||||
svgElement.style['zIndex'] = "2"
|
svgElement.style['zIndex'] = "2"
|
||||||
svgElement.style['position'] = "relative"
|
svgElement.style['position'] = "relative"
|
||||||
context.splineEditor.element.appendChild(svgElement);
|
context.splineEditor.element.appendChild(svgElement);
|
||||||
var pathElements = svgElement.getElementsByTagName('path'); // Get all path elements
|
this.pathElements = svgElement.getElementsByTagName('path'); // Get all path elements
|
||||||
|
|
||||||
if (w > 256) {
|
if (this.width > 256) {
|
||||||
context.setSize([w + 45, context.size[1]]);
|
this.node.setSize([this.width + 45, context.size[1]]);
|
||||||
}
|
}
|
||||||
context.setSize([context.size[0], h + 430]);
|
this.node.setSize([this.node.size[0], this.height + 430]);
|
||||||
updatePath();
|
this.updatePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
function samplePoints(svgPathElement, numSamples, samplingMethod, width) {
|
updatePath = () => {
|
||||||
|
if (!this.points || this.points.length === 0) {
|
||||||
|
console.log("no points");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.samplingMethod != "controlpoints") {
|
||||||
|
var coords = this.samplePoints(this.pathElements[0], this.points_to_sample, this.samplingMethod, this.width);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var coords = this.points
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.drawSamplePoints) {
|
||||||
|
if (this.pointsLayer) {
|
||||||
|
// Update the data of the existing points layer
|
||||||
|
this.pointsLayer.data(coords);
|
||||||
|
} else {
|
||||||
|
// Create the points layer if it doesn't exist
|
||||||
|
this.pointsLayer = this.vis.add(pv.Dot)
|
||||||
|
.data(coords)
|
||||||
|
.left(function(d) { return d.x; })
|
||||||
|
.top(function(d) { return d.y; })
|
||||||
|
.radius(5) // Adjust the radius as needed
|
||||||
|
.fillStyle("red") // Change the color as needed
|
||||||
|
.strokeStyle("black") // Change the stroke color as needed
|
||||||
|
.lineWidth(1); // Adjust the line width as needed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.pointsLayer) {
|
||||||
|
// Remove the points layer
|
||||||
|
this.pointsLayer.data([]);
|
||||||
|
this.vis.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let coordsString = JSON.stringify(coords);
|
||||||
|
this.pointsStoreWidget.value = JSON.stringify(this.points);
|
||||||
|
if (this.coordWidget) {
|
||||||
|
this.coordWidget.value = coordsString;
|
||||||
|
}
|
||||||
|
this.vis.render();
|
||||||
|
};
|
||||||
|
handleImageLoad = (img, file, base64String) => {
|
||||||
|
console.log(img.width, img.height); // Access width and height here
|
||||||
|
this.widthWidget.value = img.width;
|
||||||
|
this.heightWidget.value = img.height;
|
||||||
|
this.drawRuler = false;
|
||||||
|
|
||||||
|
if (img.width != this.vis.width() || img.height != this.vis.height()) {
|
||||||
|
if (img.width > 256) {
|
||||||
|
this.node.setSize([img.width + 45, this.node.size[1]]);
|
||||||
|
}
|
||||||
|
this.node.setSize([this.node.size[0], img.height + 300]);
|
||||||
|
this.vis.width(img.width);
|
||||||
|
this.vis.height(img.height);
|
||||||
|
this.height = img.height;
|
||||||
|
this.width = img.width;
|
||||||
|
|
||||||
|
this.updatePath();
|
||||||
|
}
|
||||||
|
this.backgroundImage.url(file ? URL.createObjectURL(file) : `data:${this.node.properties.imgData.type};base64,${base64String}`).visible(true).root.render();
|
||||||
|
};
|
||||||
|
|
||||||
|
processImage = (img, file) => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
|
const maxWidth = 800; // maximum width
|
||||||
|
const maxHeight = 600; // maximum height
|
||||||
|
let width = img.width;
|
||||||
|
let height = img.height;
|
||||||
|
|
||||||
|
// Calculate the new dimensions while preserving the aspect ratio
|
||||||
|
if (width > height) {
|
||||||
|
if (width > maxWidth) {
|
||||||
|
height *= maxWidth / width;
|
||||||
|
width = maxWidth;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (height > maxHeight) {
|
||||||
|
width *= maxHeight / height;
|
||||||
|
height = maxHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
|
||||||
|
// Get the compressed image data as a Base64 string
|
||||||
|
const base64String = canvas.toDataURL('image/jpeg', 0.5).replace('data:', '').replace(/^.+,/, ''); // 0.5 is the quality from 0 to 1
|
||||||
|
|
||||||
|
this.node.properties.imgData = {
|
||||||
|
name: file.name,
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
base64: base64String
|
||||||
|
};
|
||||||
|
handleImageLoad(img, file, base64String);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleImageFile = (file) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = reader.result;
|
||||||
|
img.onload = () => processImage(img, file);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
const imageUrl = URL.createObjectURL(file);
|
||||||
|
const img = new Image();
|
||||||
|
img.src = imageUrl;
|
||||||
|
img.onload = () => this.handleImageLoad(img, file, null);
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshBackgroundImage = () => {
|
||||||
|
if (this.node.properties.imgData && this.node.properties.imgData.base64) {
|
||||||
|
const base64String = this.node.properties.imgData.base64;
|
||||||
|
const imageUrl = `data:${this.node.properties.imgData.type};base64,${base64String}`;
|
||||||
|
const img = new Image();
|
||||||
|
img.src = imageUrl;
|
||||||
|
img.onload = () => this.handleImageLoad(img, null, base64String);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
createContextMenu() {
|
||||||
|
self = this;
|
||||||
|
document.addEventListener('contextmenu', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (!self.node.contextMenu.contains(e.target)) {
|
||||||
|
self.node.contextMenu.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.node.menuItems.forEach((menuItem, index) => {
|
||||||
|
self = this;
|
||||||
|
menuItem.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
// Logic specific to each menu item based on its index or id
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
e.preventDefault();
|
||||||
|
if (!self.drawHandles) {
|
||||||
|
self.drawHandles = true
|
||||||
|
self.vis.add(pv.Line)
|
||||||
|
.data(() => self.points.map((point, index) => ({
|
||||||
|
start: point,
|
||||||
|
end: [index]
|
||||||
|
})))
|
||||||
|
.left(d => d.start.x)
|
||||||
|
.top(d => d.start.y)
|
||||||
|
.interpolate("linear")
|
||||||
|
.tension(0) // Straight lines
|
||||||
|
.strokeStyle("#ff7f0e") // Same color as control points
|
||||||
|
.lineWidth(1)
|
||||||
|
.visible(() => self.drawHandles);
|
||||||
|
self.vis.render();
|
||||||
|
} else {
|
||||||
|
self.drawHandles = false
|
||||||
|
self.vis.render();
|
||||||
|
}
|
||||||
|
self.node.contextMenu.style.display = 'none';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
e.preventDefault();
|
||||||
|
self.drawSamplePoints = !self.drawSamplePoints;
|
||||||
|
self.updatePath();
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
e.preventDefault();
|
||||||
|
if (self.dotShape == "circle"){
|
||||||
|
self.dotShape = "triangle"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
self.dotShape = "circle"
|
||||||
|
}
|
||||||
|
console.log(self.dotShape)
|
||||||
|
self.updatePath();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// Create file input element
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = 'image/*'; // Accept only image files
|
||||||
|
|
||||||
|
// Listen for file selection
|
||||||
|
fileInput.addEventListener('change', function(event) {
|
||||||
|
const file = event.target.files[0]; // Get the selected file
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
// Create a URL for the selected file
|
||||||
|
const imageUrl = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
// Set the backgroundImage with the new URL and make it visible
|
||||||
|
self.backgroundImage
|
||||||
|
.url(imageUrl)
|
||||||
|
.visible(true)
|
||||||
|
.root.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the backgroundImage is already visible, hide it. Otherwise, show file input.
|
||||||
|
if (self.backgroundImage.visible()) {
|
||||||
|
self.backgroundImage.visible(false)
|
||||||
|
.root.render();
|
||||||
|
} else {
|
||||||
|
// Trigger the file input dialog
|
||||||
|
fileInput.click();
|
||||||
|
}
|
||||||
|
self.node.contextMenu.style.display = 'none';
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
e.preventDefault();
|
||||||
|
self.points.reverse();
|
||||||
|
self.updatePath();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
samplePoints(svgPathElement, numSamples, samplingMethod, width) {
|
||||||
var svgWidth = width; // Fixed width of the SVG element
|
var svgWidth = width; // Fixed width of the SVG element
|
||||||
var pathLength = svgPathElement.getTotalLength();
|
var pathLength = svgPathElement.getTotalLength();
|
||||||
var points = [];
|
var points = [];
|
||||||
@ -637,7 +779,7 @@ function samplePoints(svgPathElement, numSamples, samplingMethod, width) {
|
|||||||
// Calculate the x-coordinate for the current sample based on the SVG's width
|
// Calculate the x-coordinate for the current sample based on the SVG's width
|
||||||
var x = (svgWidth / (numSamples - 1)) * i;
|
var x = (svgWidth / (numSamples - 1)) * i;
|
||||||
// Find the point on the path that intersects the vertical line at the calculated x-coordinate
|
// Find the point on the path that intersects the vertical line at the calculated x-coordinate
|
||||||
var point = findPointAtX(svgPathElement, x, pathLength);
|
var point = this.findPointAtX(svgPathElement, x, pathLength);
|
||||||
}
|
}
|
||||||
else if (samplingMethod === "path") {
|
else if (samplingMethod === "path") {
|
||||||
// Calculate the distance along the path for the current sample
|
// Calculate the distance along the path for the current sample
|
||||||
@ -650,55 +792,55 @@ function samplePoints(svgPathElement, numSamples, samplingMethod, width) {
|
|||||||
points.push({ x: point.x, y: point.y });
|
points.push({ x: point.x, y: point.y });
|
||||||
}
|
}
|
||||||
return points;
|
return points;
|
||||||
}
|
|
||||||
|
|
||||||
function findClosestPoints(points, clickedPoint) {
|
|
||||||
// Calculate distances from clickedPoint to each point in the array
|
|
||||||
let distances = points.map(point => {
|
|
||||||
let dx = clickedPoint.x - point.x;
|
|
||||||
let dy = clickedPoint.y - point.y;
|
|
||||||
return { index: points.indexOf(point), distance: Math.sqrt(dx * dx + dy * dy) };
|
|
||||||
});
|
|
||||||
// Sort distances and get the indices of the two closest points
|
|
||||||
let sortedDistances = distances.sort((a, b) => a.distance - b.distance);
|
|
||||||
let closestPoint1Index = sortedDistances[0].index;
|
|
||||||
let closestPoint2Index = sortedDistances[1].index;
|
|
||||||
// Ensure point1Index is always the smaller index
|
|
||||||
if (closestPoint1Index > closestPoint2Index) {
|
|
||||||
[closestPoint1Index, closestPoint2Index] = [closestPoint2Index, closestPoint1Index];
|
|
||||||
}
|
|
||||||
return { point1Index: closestPoint1Index, point2Index: closestPoint2Index };
|
|
||||||
}
|
|
||||||
|
|
||||||
function findPointAtX(svgPathElement, targetX, pathLength) {
|
|
||||||
let low = 0;
|
|
||||||
let high = pathLength;
|
|
||||||
let bestPoint = svgPathElement.getPointAtLength(0);
|
|
||||||
|
|
||||||
while (low <= high) {
|
|
||||||
let mid = low + (high - low) / 2;
|
|
||||||
let point = svgPathElement.getPointAtLength(mid);
|
|
||||||
|
|
||||||
if (Math.abs(point.x - targetX) < 1) {
|
|
||||||
return point; // The point is close enough to the target
|
|
||||||
}
|
|
||||||
|
|
||||||
if (point.x < targetX) {
|
|
||||||
low = mid + 1;
|
|
||||||
} else {
|
|
||||||
high = mid - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of the closest point found so far
|
|
||||||
if (Math.abs(point.x - targetX) < Math.abs(bestPoint.x - targetX)) {
|
|
||||||
bestPoint = point;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the closest point found
|
findClosestPoints(points, clickedPoint) {
|
||||||
return bestPoint;
|
// Calculate distances from clickedPoint to each point in the array
|
||||||
}
|
let distances = points.map(point => {
|
||||||
|
let dx = clickedPoint.x - point.x;
|
||||||
|
let dy = clickedPoint.y - point.y;
|
||||||
|
return { index: points.indexOf(point), distance: Math.sqrt(dx * dx + dy * dy) };
|
||||||
|
});
|
||||||
|
// Sort distances and get the indices of the two closest points
|
||||||
|
let sortedDistances = distances.sort((a, b) => a.distance - b.distance);
|
||||||
|
let closestPoint1Index = sortedDistances[0].index;
|
||||||
|
let closestPoint2Index = sortedDistances[1].index;
|
||||||
|
// Ensure point1Index is always the smaller index
|
||||||
|
if (closestPoint1Index > closestPoint2Index) {
|
||||||
|
[closestPoint1Index, closestPoint2Index] = [closestPoint2Index, closestPoint1Index];
|
||||||
|
}
|
||||||
|
return { point1Index: closestPoint1Index, point2Index: closestPoint2Index };
|
||||||
|
}
|
||||||
|
|
||||||
|
findPointAtX(svgPathElement, targetX, pathLength) {
|
||||||
|
let low = 0;
|
||||||
|
let high = pathLength;
|
||||||
|
let bestPoint = svgPathElement.getPointAtLength(0);
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
let mid = low + (high - low) / 2;
|
||||||
|
let point = svgPathElement.getPointAtLength(mid);
|
||||||
|
|
||||||
|
if (Math.abs(point.x - targetX) < 1) {
|
||||||
|
return point; // The point is close enough to the target
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point.x < targetX) {
|
||||||
|
low = mid + 1;
|
||||||
|
} else {
|
||||||
|
high = mid - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep track of the closest point found so far
|
||||||
|
if (Math.abs(point.x - targetX) < Math.abs(bestPoint.x - targetX)) {
|
||||||
|
bestPoint = point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the closest point found
|
||||||
|
return bestPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
//from melmass
|
//from melmass
|
||||||
export function hideWidgetForGood(node, widget, suffix = '') {
|
export function hideWidgetForGood(node, widget, suffix = '') {
|
||||||
widget.origType = widget.type
|
widget.origType = widget.type
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user