mirror of
https://git.datalinker.icu/kijai/ComfyUI-KJNodes.git
synced 2025-12-23 03:34:30 +08:00
PointsEditor refactor, support image input, copypaste etc.
This commit is contained in:
parent
5229aaa2c0
commit
286f8ea4a3
@ -1265,6 +1265,9 @@ class PointsEditor:
|
|||||||
"width": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}),
|
"width": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}),
|
||||||
"height": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}),
|
"height": ("INT", {"default": 512, "min": 8, "max": 4096, "step": 8}),
|
||||||
},
|
},
|
||||||
|
"optional": {
|
||||||
|
"bg_image": ("IMAGE", ),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
RETURN_TYPES = ("STRING", "STRING", "BBOX", "MASK")
|
RETURN_TYPES = ("STRING", "STRING", "BBOX", "MASK")
|
||||||
@ -1289,7 +1292,9 @@ The image is saved to the node
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def pointdata(self, points_store, bbox_store, width, height, coordinates, bboxes, bbox_format="xyxy"):
|
def pointdata(self, points_store, bbox_store, width, height, coordinates, bboxes, bbox_format="xyxy", bg_image=None):
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
|
||||||
coordinates = json.loads(coordinates)
|
coordinates = json.loads(coordinates)
|
||||||
normalized = []
|
normalized = []
|
||||||
@ -1329,4 +1334,19 @@ The image is saved to the node
|
|||||||
#mask_tensor = mask_tensor[:,:,0]
|
#mask_tensor = mask_tensor[:,:,0]
|
||||||
print(mask_tensor.shape)
|
print(mask_tensor.shape)
|
||||||
|
|
||||||
|
if bg_image is None:
|
||||||
return (json.dumps(coordinates), json.dumps(normalized), bboxes, mask_tensor)
|
return (json.dumps(coordinates), json.dumps(normalized), bboxes, mask_tensor)
|
||||||
|
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": (json.dumps(coordinates), json.dumps(normalized), bboxes, mask_tensor)
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { app } from '../../../scripts/app.js'
|
import { app } from '../../../scripts/app.js'
|
||||||
import { api } from '../../../scripts/api.js'
|
|
||||||
|
|
||||||
//from melmass
|
//from melmass
|
||||||
export function makeUUID() {
|
export function makeUUID() {
|
||||||
@ -16,7 +15,7 @@ export const loadScript = (
|
|||||||
FILE_URL,
|
FILE_URL,
|
||||||
async = true,
|
async = true,
|
||||||
type = 'text/javascript',
|
type = 'text/javascript',
|
||||||
) => {
|
) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
// Check if the script already exists
|
// Check if the script already exists
|
||||||
@ -47,8 +46,8 @@ export const loadScript = (
|
|||||||
reject(error)
|
reject(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const create_documentation_stylesheet = () => {
|
const create_documentation_stylesheet = () => {
|
||||||
const tag = 'kj-pointseditor-stylesheet'
|
const tag = 'kj-pointseditor-stylesheet'
|
||||||
|
|
||||||
let styleTag = document.head.querySelector(tag)
|
let styleTag = document.head.querySelector(tag)
|
||||||
@ -71,7 +70,7 @@ export const loadScript = (
|
|||||||
`
|
`
|
||||||
document.head.appendChild(styleTag)
|
document.head.appendChild(styleTag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadScript('/kjweb_async/svg-path-properties.min.js').catch((e) => {
|
loadScript('/kjweb_async/svg-path-properties.min.js').catch((e) => {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
@ -113,7 +112,7 @@ app.registerExtension({
|
|||||||
element.id = `points-editor-${this.uuid}`
|
element.id = `points-editor-${this.uuid}`
|
||||||
|
|
||||||
// fake image widget to allow copy/paste
|
// fake image widget to allow copy/paste
|
||||||
const fakeimagewidget = this.addWidget("COMBO", "image", null, () => {}, {});
|
const fakeimagewidget = this.addWidget("COMBO", "image", null, () => { }, {});
|
||||||
hideWidgetForGood(this, fakeimagewidget)
|
hideWidgetForGood(this, fakeimagewidget)
|
||||||
|
|
||||||
this.pointsEditor = this.addDOMWidget(nodeData.name, "PointsEditorWidget", element, {
|
this.pointsEditor = this.addDOMWidget(nodeData.name, "PointsEditorWidget", element, {
|
||||||
@ -158,11 +157,11 @@ app.registerExtension({
|
|||||||
|
|
||||||
// Add mouseover and mouseout event listeners to each menu item for styling
|
// Add mouseover and mouseout event listeners to each menu item for styling
|
||||||
this.menuItems.forEach(menuItem => {
|
this.menuItems.forEach(menuItem => {
|
||||||
menuItem.addEventListener('mouseover', function() {
|
menuItem.addEventListener('mouseover', function () {
|
||||||
this.style.backgroundColor = "gray";
|
this.style.backgroundColor = "gray";
|
||||||
});
|
});
|
||||||
|
|
||||||
menuItem.addEventListener('mouseout', function() {
|
menuItem.addEventListener('mouseout', function () {
|
||||||
this.style.backgroundColor = "#202020";
|
this.style.backgroundColor = "#202020";
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -172,16 +171,16 @@ app.registerExtension({
|
|||||||
this.contextMenu.appendChild(menuItem);
|
this.contextMenu.appendChild(menuItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.appendChild( this.contextMenu);
|
document.body.appendChild(this.contextMenu);
|
||||||
|
|
||||||
this.addWidget("button", "New canvas", null, () => {
|
this.addWidget("button", "New canvas", null, () => {
|
||||||
if (!this.properties || !("points" in this.properties)) {
|
if (!this.properties || !("points" in this.properties)) {
|
||||||
createPointsEditor(this)
|
this.editor = new PointsEditor(this);
|
||||||
this.addProperty("points", this.constructor.type, "string");
|
this.addProperty("points", this.constructor.type, "string");
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
createPointsEditor(this, true)
|
this.editor = new PointsEditor(this, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -192,8 +191,16 @@ app.registerExtension({
|
|||||||
this.pointsEditor.parentEl.id = `points-editor-${this.uuid}`
|
this.pointsEditor.parentEl.id = `points-editor-${this.uuid}`
|
||||||
element.appendChild(this.pointsEditor.parentEl);
|
element.appendChild(this.pointsEditor.parentEl);
|
||||||
|
|
||||||
chainCallback(this, "onConfigure", function() {
|
chainCallback(this, "onConfigure", function () {
|
||||||
createPointsEditor(this);
|
this.editor = new PointsEditor(this);
|
||||||
|
});
|
||||||
|
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
|
||||||
@ -201,32 +208,22 @@ app.registerExtension({
|
|||||||
} //before register
|
} //before register
|
||||||
})//register
|
})//register
|
||||||
|
|
||||||
|
class PointsEditor {
|
||||||
|
constructor(context, reset = false) {
|
||||||
|
this.node = context;
|
||||||
|
this.reset = reset;
|
||||||
|
|
||||||
function createPointsEditor(context, reset=false) {
|
|
||||||
console.log("creatingPointEditor")
|
console.log("creatingPointEditor")
|
||||||
|
|
||||||
// context menu
|
this.node.pasteFile = (file) => {
|
||||||
function 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.pasteFile = function(file) {
|
|
||||||
console.log(file);
|
|
||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
handleImageFile(file);
|
this.handleImageFile(file);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
context.onDragOver = function(e) {
|
this.node.onDragOver = function (e) {
|
||||||
if (e.dataTransfer && e.dataTransfer.items) {
|
if (e.dataTransfer && e.dataTransfer.items) {
|
||||||
return [...e.dataTransfer.items].some(f => f.kind === "file" && f.type.startsWith("image/"));
|
return [...e.dataTransfer.items].some(f => f.kind === "file" && f.type.startsWith("image/"));
|
||||||
}
|
}
|
||||||
@ -234,26 +231,229 @@ function createPointsEditor(context, reset=false) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// On drop upload files
|
// On drop upload files
|
||||||
context.onDragDrop = function(e) {
|
this.node.onDragDrop = (e) => {
|
||||||
console.log("onDragDrop called");
|
console.log("onDragDrop called");
|
||||||
let handled = false;
|
let handled = false;
|
||||||
for (const file of e.dataTransfer.files) {
|
for (const file of e.dataTransfer.files) {
|
||||||
if (file.type.startsWith("image/")) {
|
if (file.type.startsWith("image/")) {
|
||||||
handleImageFile(file);
|
this.handleImageFile(file);
|
||||||
handled = true;
|
handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handled;
|
return handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleImageFile(file) {
|
// context menu
|
||||||
const reader = new FileReader();
|
this.createContextMenu();
|
||||||
|
|
||||||
reader.onloadend = function() {
|
|
||||||
const img = new Image();
|
|
||||||
img.src = reader.result;
|
|
||||||
|
|
||||||
img.onload = function() {
|
if (reset && context.pointsEditor.element) {
|
||||||
|
context.pointsEditor.element.innerHTML = ''; // Clear the container
|
||||||
|
}
|
||||||
|
this.coordWidget = context.widgets.find(w => w.name === "coordinates");
|
||||||
|
this.pointsStoreWidget = context.widgets.find(w => w.name === "points_store");
|
||||||
|
this.widthWidget = context.widgets.find(w => w.name === "width");
|
||||||
|
this.heightWidget = context.widgets.find(w => w.name === "height");
|
||||||
|
this.bboxStoreWidget = context.widgets.find(w => w.name === "bbox_store");
|
||||||
|
this.bboxWidget = context.widgets.find(w => w.name === "bboxes");
|
||||||
|
|
||||||
|
//widget callbacks
|
||||||
|
this.widthWidget.callback = () => {
|
||||||
|
w = this.widthWidget.value;
|
||||||
|
if (w > 256) {
|
||||||
|
context.setSize([w + 45, context.size[1]]);
|
||||||
|
}
|
||||||
|
this.vis.width(w);
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
this.heightWidget.callback = () => {
|
||||||
|
h = this.heightWidget.value
|
||||||
|
this.vis.height(h)
|
||||||
|
context.setSize([context.size[0], h + 300]);
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
this.pointsStoreWidget.callback = () => {
|
||||||
|
this.points = JSON.parse(pointsStoreWidget.value);
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
var w = this.widthWidget.value;
|
||||||
|
var h = this.heightWidget.value;
|
||||||
|
var i = 3;
|
||||||
|
this.points = [];
|
||||||
|
this.bbox = [];
|
||||||
|
var drawing = false;
|
||||||
|
|
||||||
|
// Initialize or reset points array
|
||||||
|
if (!reset && this.pointsStoreWidget.value != "") {
|
||||||
|
this.points = JSON.parse(this.pointsStoreWidget.value);
|
||||||
|
this.bbox = JSON.parse(this.bboxStoreWidget.value);
|
||||||
|
} else {
|
||||||
|
this.points = [
|
||||||
|
{
|
||||||
|
x: w / 2, // Middle point horizontally centered
|
||||||
|
y: h / 2 // Middle point vertically centered
|
||||||
|
}
|
||||||
|
];
|
||||||
|
this.pointsStoreWidget.value = JSON.stringify(this.points);
|
||||||
|
this.bboxStoreWidget.value = JSON.stringify(this.bbox);
|
||||||
|
}
|
||||||
|
const self = this; // Keep a reference to the main class context
|
||||||
|
this.vis = new pv.Panel()
|
||||||
|
.width(w)
|
||||||
|
.height(h)
|
||||||
|
.fillStyle("#222")
|
||||||
|
.strokeStyle("gray")
|
||||||
|
.lineWidth(2)
|
||||||
|
.antialias(false)
|
||||||
|
.margin(10)
|
||||||
|
.event("mousedown", function () {
|
||||||
|
if (pv.event.shiftKey) { // Use pv.event to access the event object
|
||||||
|
let scaledMouse = {
|
||||||
|
x: this.mouse().x / app.canvas.ds.scale,
|
||||||
|
y: this.mouse().y / app.canvas.ds.scale
|
||||||
|
};
|
||||||
|
i = self.points.push(scaledMouse) - 1;
|
||||||
|
self.updateData();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
else if (pv.event.ctrlKey) {
|
||||||
|
console.log("start drawing at " + this.mouse().x / app.canvas.ds.scale + ", " + this.mouse().y / app.canvas.ds.scale);
|
||||||
|
drawing = true;
|
||||||
|
self.box_startX = this.mouse().x / app.canvas.ds.scale;
|
||||||
|
self.box_startY = this.mouse().y / app.canvas.ds.scale;
|
||||||
|
}
|
||||||
|
else if (pv.event.button === 2) {
|
||||||
|
self.node.contextMenu.style.display = 'block';
|
||||||
|
self.node.contextMenu.style.left = `${pv.event.clientX}px`;
|
||||||
|
self.node.contextMenu.style.top = `${pv.event.clientY}px`;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.event("mousemove", function () {
|
||||||
|
if (drawing) {
|
||||||
|
self.box_endX = this.mouse().x / app.canvas.ds.scale;
|
||||||
|
self.box_endY = this.mouse().y / app.canvas.ds.scale;
|
||||||
|
self.vis.render();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.event("mouseup", function () {
|
||||||
|
console.log("end drawing at " + this.mouse().x / app.canvas.ds.scale + ", " + this.mouse().y / app.canvas.ds.scale);
|
||||||
|
drawing = false;
|
||||||
|
self.updateData();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.backgroundImage = this.vis.add(pv.Image).visible(false)
|
||||||
|
|
||||||
|
this.vis.add(pv.Area)
|
||||||
|
.data(function () {return drawing || self.bbox ? [self.box_startX, self.box_endX] : []; })
|
||||||
|
.bottom(function () {return h - Math.max(self.box_startY, self.box_endY); })
|
||||||
|
.left(function (d) {return d; })
|
||||||
|
.height(function () {return Math.abs(self.box_startY - self.box_endY);})
|
||||||
|
.fillStyle("rgba(70, 130, 180, 0.5)")
|
||||||
|
.strokeStyle("steelblue");
|
||||||
|
|
||||||
|
this.vis.add(pv.Dot)
|
||||||
|
.data(() => this.points)
|
||||||
|
.left(d => d.x)
|
||||||
|
.top(d => d.y)
|
||||||
|
.radius(Math.log(Math.min(w, h)) * 4)
|
||||||
|
.shape("circle")
|
||||||
|
.cursor("move")
|
||||||
|
.strokeStyle(function () { return i == this.index ? "#ff7f0e" : "#00FFFF"; })
|
||||||
|
.lineWidth(4)
|
||||||
|
.fillStyle(function () { return "rgba(100, 100, 100, 0.6)"; })
|
||||||
|
.event("mousedown", pv.Behavior.drag())
|
||||||
|
.event("dragstart", function () {
|
||||||
|
i = this.index;
|
||||||
|
})
|
||||||
|
.event("dragend", function () {
|
||||||
|
if (pv.event.button === 2 && i !== 0 && i !== self.points.length - 1) {
|
||||||
|
this.index = i;
|
||||||
|
self.points.splice(i--, 1);
|
||||||
|
}
|
||||||
|
self.updateData();
|
||||||
|
|
||||||
|
})
|
||||||
|
.event("drag", function () {
|
||||||
|
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
|
||||||
|
// Determine the bounds of the vis.Panel
|
||||||
|
const panelWidth = self.vis.width();
|
||||||
|
const panelHeight = self.vis.height();
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
adjustedY = Math.max(0, Math.min(panelHeight, adjustedY));
|
||||||
|
self.points[this.index] = { x: adjustedX, y: adjustedY }; // Update the point's position
|
||||||
|
self.vis.render(); // Re-render the visualization to reflect the new position
|
||||||
|
})
|
||||||
|
|
||||||
|
.anchor("center")
|
||||||
|
.add(pv.Label)
|
||||||
|
.left(d => d.x < w / 2 ? d.x + 30 : d.x - 35) // Shift label to right if on left half, otherwise shift to left
|
||||||
|
.top(d => d.y < h / 2 ? d.y + 25 : d.y - 25) // Shift label down if on top half, otherwise shift up
|
||||||
|
.font(25 + "px sans-serif")
|
||||||
|
.text(d => {return this.points.indexOf(d); })
|
||||||
|
.textStyle("cyan")
|
||||||
|
.textShadow("2px 2px 2px black")
|
||||||
|
.add(pv.Dot) // Add smaller point in the center
|
||||||
|
.data(() => this.points)
|
||||||
|
.left(d => d.x)
|
||||||
|
.top(d => d.y)
|
||||||
|
.radius(2) // Smaller radius for the center point
|
||||||
|
.shape("circle")
|
||||||
|
.fillStyle("red") // Color for the center point
|
||||||
|
.lineWidth(1); // Stroke thickness for the center point
|
||||||
|
|
||||||
|
if (this.points.length != 0) {
|
||||||
|
this.vis.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
var svgElement = this.vis.canvas();
|
||||||
|
svgElement.style['zIndex'] = "2"
|
||||||
|
svgElement.style['position'] = "relative"
|
||||||
|
this.node.pointsEditor.element.appendChild(svgElement);
|
||||||
|
|
||||||
|
if (w > 256) {
|
||||||
|
this.node.setSize([w + 45, this.node.size[1]]);
|
||||||
|
}
|
||||||
|
this.node.setSize([this.node.size[0], h + 300]);
|
||||||
|
this.updateData();
|
||||||
|
this.refreshBackgroundImage();
|
||||||
|
|
||||||
|
}//end constructor
|
||||||
|
|
||||||
|
updateData = () => {
|
||||||
|
if (this.points.length == 0) {
|
||||||
|
console.log("no points")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let coordsString = JSON.stringify(this.points);
|
||||||
|
let bbox = calculateBBox(this.box_startX, this.box_startY, this.box_endX, this.box_endY);
|
||||||
|
let bboxString = JSON.stringify(bbox);
|
||||||
|
this.pointsStoreWidget.value = JSON.stringify(this.points);
|
||||||
|
this.bboxStoreWidget.value = JSON.stringify(bboxString);
|
||||||
|
this.coordWidget.value = coordsString;
|
||||||
|
this.bboxWidget.value = bboxString;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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.updateData();
|
||||||
|
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 canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
|
|
||||||
@ -275,56 +475,64 @@ function createPointsEditor(context, reset=false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the canvas to the new dimensions
|
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
|
||||||
// Draw the image to the canvas
|
|
||||||
ctx.drawImage(img, 0, 0, width, height);
|
ctx.drawImage(img, 0, 0, width, height);
|
||||||
|
|
||||||
// Get the compressed image data as a Base64 string
|
// Get the compressed image data as a Base64 string
|
||||||
const base64String = canvas.toDataURL('image/jpeg', 0.5).replace('data:', '').replace(/^.+,/, ''); // 0.7 is the quality from 0 to 1
|
const base64String = canvas.toDataURL('image/jpeg', 0.5).replace('data:', '').replace(/^.+,/, ''); // 0.5 is the quality from 0 to 1
|
||||||
|
|
||||||
context.properties.imgData = {
|
this.node.properties.imgData = {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
lastModified: file.lastModified,
|
lastModified: file.lastModified,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
base64: base64String
|
base64: base64String
|
||||||
};
|
};
|
||||||
|
handleImageLoad(img, file, base64String);
|
||||||
|
};
|
||||||
|
|
||||||
console.log('Compressed Image Data:', context.properties.imgData);
|
handleImageFile = (file) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = reader.result;
|
||||||
|
img.onload = () => processImage(img, file);
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsDataURL(file);
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = URL.createObjectURL(file);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.src = imageUrl;
|
img.src = imageUrl;
|
||||||
|
img.onload = () => this.handleImageLoad(img, file, null);
|
||||||
img.onload = function() {
|
|
||||||
console.log(this.width, this.height); // Access width and height here
|
|
||||||
const w = this.width;
|
|
||||||
const h = this.height;
|
|
||||||
widthWidget.value = w;
|
|
||||||
heightWidget.value = h;
|
|
||||||
|
|
||||||
if (w > 256) {
|
|
||||||
context.setSize([w + 45, context.size[1]]);
|
|
||||||
}
|
|
||||||
context.setSize([context.size[0], h + 300]);
|
|
||||||
vis.width(w);
|
|
||||||
vis.height(h);
|
|
||||||
updateData();
|
|
||||||
backgroundImage.url(imageUrl).visible(true).root.render();
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
context.menuItems.forEach((menuItem, index) => {
|
refreshBackgroundImage = () => {
|
||||||
menuItem.addEventListener('click', function(e) {
|
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();
|
e.preventDefault();
|
||||||
// Logic specific to each menu item based on its index or id
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
// Create file input element
|
// Create file input element
|
||||||
@ -333,293 +541,32 @@ function createPointsEditor(context, reset=false) {
|
|||||||
fileInput.accept = 'image/*'; // Accept only image files
|
fileInput.accept = 'image/*'; // Accept only image files
|
||||||
|
|
||||||
// Listen for file selection
|
// Listen for file selection
|
||||||
fileInput.addEventListener('change', function(event) {
|
fileInput.addEventListener('change', function (event) {
|
||||||
const file = event.target.files[0]; // Get the selected file
|
const file = event.target.files[0]; // Get the selected file
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
// Create a URL for the selected file
|
|
||||||
const imageUrl = URL.createObjectURL(file);
|
const imageUrl = URL.createObjectURL(file);
|
||||||
|
|
||||||
// Create an Image object
|
|
||||||
let img = new Image();
|
let img = new Image();
|
||||||
|
|
||||||
// Set the source of the image object
|
|
||||||
img.src = imageUrl;
|
img.src = imageUrl;
|
||||||
|
img.onload = () => self.handleImageLoad(img, file, null);
|
||||||
img.onload = function() {
|
|
||||||
console.log(this.width, this.height); // Access width and height here
|
|
||||||
widthWidget.value = this.width;
|
|
||||||
heightWidget.value = this.height;
|
|
||||||
w = widthWidget.value;
|
|
||||||
h = heightWidget.value;
|
|
||||||
if (this.width > 256) {
|
|
||||||
context.setSize([this.width + 45, context.size[1]]);
|
|
||||||
}
|
|
||||||
context.setSize([context.size[0], this.height + 230]);
|
|
||||||
vis.width(w);
|
|
||||||
vis.height(h);
|
|
||||||
updateData();
|
|
||||||
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();
|
fileInput.click();
|
||||||
}
|
|
||||||
context.contextMenu.style.display = 'none';
|
|
||||||
break;
|
|
||||||
|
|
||||||
|
self.node.contextMenu.style.display = 'none';
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
backgroundImage.visible(false).root.render();
|
self.backgroundImage.visible(false).root.render();
|
||||||
context.properties.imgData = null;
|
self.node.properties.imgData = null;
|
||||||
context.contextMenu.style.display = 'none';
|
self.node.contextMenu.style.display = 'none';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}//end createContextMenu
|
||||||
|
}//end class
|
||||||
|
|
||||||
createContextMenu();
|
|
||||||
function updateData() {
|
|
||||||
if (points.length == 0) {
|
|
||||||
console.log("no points")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let coords = points
|
|
||||||
let coordsString = JSON.stringify(coords);
|
|
||||||
let bbox = calculateBBox(box_startX, box_startY, box_endX, box_endY)
|
|
||||||
let bboxString = JSON.stringify(bbox);
|
|
||||||
pointsStoreWidget.value = JSON.stringify(points);
|
|
||||||
bboxStoreWidget.value = JSON.stringify(bboxString);
|
|
||||||
if (coordWidget) {
|
|
||||||
coordWidget.value = coordsString;
|
|
||||||
}
|
|
||||||
if (bboxWidget) {
|
|
||||||
bboxWidget.value = bboxString;
|
|
||||||
}
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reset && context.pointsEditor.element) {
|
|
||||||
context.pointsEditor.element.innerHTML = ''; // Clear the container
|
|
||||||
}
|
|
||||||
const coordWidget = context.widgets.find(w => w.name === "coordinates");
|
|
||||||
const pointsStoreWidget = context.widgets.find(w => w.name === "points_store");
|
|
||||||
const widthWidget = context.widgets.find(w => w.name === "width");
|
|
||||||
const heightWidget = context.widgets.find(w => w.name === "height");
|
|
||||||
const bboxStoreWidget = context.widgets.find(w => w.name === "bbox_store");
|
|
||||||
const bboxWidget = context.widgets.find(w => w.name === "bboxes");
|
|
||||||
|
|
||||||
widthWidget.callback = () => {
|
|
||||||
w = widthWidget.value;
|
|
||||||
if (w > 256) {
|
|
||||||
context.setSize([w + 45, context.size[1]]);
|
|
||||||
}
|
|
||||||
vis.width(w);
|
|
||||||
updateData();
|
|
||||||
}
|
|
||||||
heightWidget.callback = () => {
|
|
||||||
h = heightWidget.value
|
|
||||||
vis.height(h)
|
|
||||||
context.setSize([context.size[0], h + 300]);
|
|
||||||
updateData();
|
|
||||||
}
|
|
||||||
pointsStoreWidget.callback = () => {
|
|
||||||
points = JSON.parse(pointsStoreWidget.value);
|
|
||||||
updateData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize or reset points array
|
|
||||||
var hoverIndex = -1;
|
|
||||||
var isDragging = false;
|
|
||||||
var w = widthWidget.value;
|
|
||||||
var h = heightWidget.value;
|
|
||||||
var i = 3;
|
|
||||||
let points = [];
|
|
||||||
let bbox = [];
|
|
||||||
var box_startX, box_startY, box_endX, box_endY;
|
|
||||||
var drawing = false;
|
|
||||||
|
|
||||||
if (!reset && pointsStoreWidget.value != "") {
|
|
||||||
points = JSON.parse(pointsStoreWidget.value);
|
|
||||||
bbox = JSON.parse(bboxStoreWidget.value);
|
|
||||||
console.log(context);
|
|
||||||
} else {
|
|
||||||
points = [
|
|
||||||
{
|
|
||||||
x: w / 2, // Middle point horizontally centered
|
|
||||||
y: h / 2 // Middle point vertically centered
|
|
||||||
}
|
|
||||||
];
|
|
||||||
pointsStoreWidget.value = JSON.stringify(points);
|
|
||||||
bboxStoreWidget.value = JSON.stringify(bbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
var vis = new pv.Panel()
|
|
||||||
.width(w)
|
|
||||||
.height(h)
|
|
||||||
.fillStyle("#222")
|
|
||||||
.strokeStyle("gray")
|
|
||||||
.lineWidth(2)
|
|
||||||
.antialias(false)
|
|
||||||
.margin(10)
|
|
||||||
.event("mousedown", function() {
|
|
||||||
if (pv.event.shiftKey) { // Use pv.event to access the event object
|
|
||||||
let scaledMouse = {
|
|
||||||
x: this.mouse().x / app.canvas.ds.scale,
|
|
||||||
y: this.mouse().y / app.canvas.ds.scale
|
|
||||||
};
|
|
||||||
i = points.push(scaledMouse) - 1;
|
|
||||||
updateData();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
else if (pv.event.ctrlKey) {
|
|
||||||
console.log("start drawing at " + this.mouse().x / app.canvas.ds.scale + ", " + this.mouse().y / app.canvas.ds.scale);
|
|
||||||
drawing = true;
|
|
||||||
box_startX = this.mouse().x / app.canvas.ds.scale;
|
|
||||||
box_startY = this.mouse().y / app.canvas.ds.scale;
|
|
||||||
}
|
|
||||||
else if (pv.event.button === 2) {
|
|
||||||
context.contextMenu.style.display = 'block';
|
|
||||||
context.contextMenu.style.left = `${pv.event.clientX}px`;
|
|
||||||
context.contextMenu.style.top = `${pv.event.clientY}px`;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.event("mousemove", function() {
|
|
||||||
if (drawing) {
|
|
||||||
box_endX = this.mouse().x / app.canvas.ds.scale;
|
|
||||||
box_endY = this.mouse().y / app.canvas.ds.scale;
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.event("mouseup", function() {
|
|
||||||
console.log("end drawing at " + this.mouse().x / app.canvas.ds.scale + ", " + this.mouse().y / app.canvas.ds.scale);
|
|
||||||
drawing = false;
|
|
||||||
updateData();
|
|
||||||
});
|
|
||||||
|
|
||||||
var backgroundImage = vis.add(pv.Image)
|
|
||||||
.visible(false)
|
|
||||||
|
|
||||||
vis.add(pv.Area)
|
|
||||||
.data(function() {
|
|
||||||
return drawing || bbox ? [box_startX, box_endX] : [];
|
|
||||||
})
|
|
||||||
.bottom(function() {
|
|
||||||
return h - Math.max(box_startY, box_endY);
|
|
||||||
})
|
|
||||||
.left(function(d) {
|
|
||||||
return d;
|
|
||||||
})
|
|
||||||
.height(function() {
|
|
||||||
return Math.abs(box_startY - box_endY);
|
|
||||||
})
|
|
||||||
.fillStyle("rgba(70, 130, 180, 0.5)")
|
|
||||||
.strokeStyle("steelblue");
|
|
||||||
|
|
||||||
vis.add(pv.Dot)
|
|
||||||
.data(() => points)
|
|
||||||
.left(d => d.x)
|
|
||||||
.top(d => d.y)
|
|
||||||
.radius(Math.log(Math.min(w, h)) * 4)
|
|
||||||
.shape("circle")
|
|
||||||
.cursor("move")
|
|
||||||
.strokeStyle(function() { return i == this.index ? "#ff7f0e" : "#00FFFF"; })
|
|
||||||
.lineWidth(4) // Set the stroke thickness to 2
|
|
||||||
.fillStyle(function() { return "rgba(100, 100, 100, 0.6)"; })
|
|
||||||
.event("mousedown", pv.Behavior.drag())
|
|
||||||
.event("dragstart", function() {
|
|
||||||
i = this.index;
|
|
||||||
console.log(i)
|
|
||||||
hoverIndex = this.index;
|
|
||||||
isDragging = true;
|
|
||||||
|
|
||||||
})
|
|
||||||
.event("dragend", function() {
|
|
||||||
if (pv.event.button === 2 && i !== 0 && i !== points.length - 1) {
|
|
||||||
this.index = i;
|
|
||||||
points.splice(i--, 1);
|
|
||||||
|
|
||||||
}
|
|
||||||
updateData();
|
|
||||||
isDragging = false;
|
|
||||||
|
|
||||||
})
|
|
||||||
.event("drag", function() {
|
|
||||||
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
|
|
||||||
// Determine the bounds of the vis.Panel
|
|
||||||
const panelWidth = vis.width();
|
|
||||||
const panelHeight = vis.height();
|
|
||||||
|
|
||||||
// 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));
|
|
||||||
adjustedY = Math.max(0, Math.min(panelHeight, adjustedY));
|
|
||||||
points[this.index] = { x: adjustedX, y: adjustedY }; // Update the point's position
|
|
||||||
vis.render(); // Re-render the visualization to reflect the new position
|
|
||||||
})
|
|
||||||
// .event("mouseover", function() {
|
|
||||||
// hoverIndex = this.index; // Set the hover index to the index of the hovered dot
|
|
||||||
// vis.render(); // Re-render the visualization
|
|
||||||
// })
|
|
||||||
// .event("mouseout", function() {
|
|
||||||
// !isDragging && (hoverIndex = -1); // Reset the hover index when the mouse leaves the dot
|
|
||||||
// vis.render(); // Re-render the visualization
|
|
||||||
// })
|
|
||||||
.anchor("center")
|
|
||||||
.add(pv.Label)
|
|
||||||
// .visible(function() {
|
|
||||||
// return hoverIndex === this.index; // Only show the label for the hovered dot
|
|
||||||
// })
|
|
||||||
.left(d => d.x < w / 2 ? d.x + 30 : d.x - 35) // Shift label to right if on left half, otherwise shift to left
|
|
||||||
.top(d => d.y < h / 2 ? d.y + 25 : d.y - 25) // Shift label down if on top half, otherwise shift up
|
|
||||||
.font(25 + "px sans-serif")
|
|
||||||
// .text(d => {
|
|
||||||
// return `X: ${Math.round(d.x)}, Y: ${Math.round(d.y)}`;
|
|
||||||
// })
|
|
||||||
.text(d => {
|
|
||||||
return points.indexOf(d);
|
|
||||||
})
|
|
||||||
.textStyle("cyan")
|
|
||||||
.textShadow("2px 2px 2px black")
|
|
||||||
|
|
||||||
.add(pv.Dot) // Add smaller point in the center
|
|
||||||
.data(() => points)
|
|
||||||
.left(d => d.x)
|
|
||||||
.top(d => d.y)
|
|
||||||
.radius(2) // Smaller radius for the center point
|
|
||||||
.shape("circle")
|
|
||||||
.fillStyle("red") // Color for the center point
|
|
||||||
.lineWidth(1); // Stroke thickness for the center point
|
|
||||||
|
|
||||||
if (points.length != 0) {
|
|
||||||
vis.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
var svgElement = vis.canvas();
|
|
||||||
svgElement.style['zIndex'] = "2"
|
|
||||||
svgElement.style['position'] = "relative"
|
|
||||||
context.pointsEditor.element.appendChild(svgElement);
|
|
||||||
|
|
||||||
if (w > 256) {
|
|
||||||
context.setSize([w + 45, context.size[1]]);
|
|
||||||
}
|
|
||||||
context.setSize([context.size[0], h + 300]);
|
|
||||||
updateData();
|
|
||||||
|
|
||||||
if (context.properties.imgData && context.properties.imgData.base64) {
|
|
||||||
const base64String = context.properties.imgData.base64;
|
|
||||||
const imageUrl = `data:${context.properties.imgData.type};base64,${base64String}`;
|
|
||||||
|
|
||||||
backgroundImage.url(imageUrl).visible(true).root.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//from melmass
|
//from melmass
|
||||||
export function hideWidgetForGood(node, widget, suffix = '') {
|
export function hideWidgetForGood(node, widget, suffix = '') {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user