diff --git a/css/index.css b/css/index.css
index 9c6b916..20766f3 100644
--- a/css/index.css
+++ b/css/index.css
@@ -64,6 +64,11 @@ body {
margin-bottom: 5px;
}
+.button.tool:disabled {
+ background-color: #666 !important;
+ cursor: default;
+}
+
.button.tool:hover {
background-color: rgb(30, 30, 80);
}
diff --git a/js/index.js b/js/index.js
index f974a75..eb85116 100644
--- a/js/index.js
+++ b/js/index.js
@@ -205,7 +205,8 @@ function imageAcceptReject(x, y, data, extra = null) {
div.style.width = "200px";
div.style.height = "70px";
div.innerHTML =
- ' of ';
+ ' of ';
+
document.getElementById("tempDiv").appendChild(div);
document.getElementById("currentImgIndex").innerText = "1";
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
@@ -238,11 +239,28 @@ function reject(evt) {
blockNewImages = false;
}
+function resource(evt) {
+ // send image to resources
+ const img = new Image();
+ // load the image data after defining the closure
+ img.src = "data:image/png;base64," + returnedImages[imageIndex];
+
+ tools.stamp.state.addResource(
+ prompt("Enter new resource name", "Dream Resource"),
+ img
+ );
+}
+
function newImage(evt) {
clearPaintedMask();
clearBackupMask();
clearTargetMask();
- clearImgMask();
+ commands.runCommand("eraseImage", "Clear Canvas", {
+ x: 0,
+ y: 0,
+ w: imgCanvas.width,
+ h: imgCanvas.height,
+ });
}
function prevImg(evt) {
@@ -592,65 +610,6 @@ function drawBackground() {
}
}
-function downloadCanvas() {
- var link = document.createElement("a");
- link.download =
- new Date().toISOString().slice(0, 19).replace("T", " ").replace(":", " ") +
- " openOutpaint image.png";
- var croppedCanvas = cropCanvas(imgCanvas);
- if (croppedCanvas != null) {
- link.href = croppedCanvas.toDataURL("image/png");
- link.click();
- }
-}
-
-function cropCanvas(sourceCanvas) {
- var w = sourceCanvas.width;
- var h = sourceCanvas.height;
- var pix = {x: [], y: []};
- var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h);
- var x, y, index;
-
- for (y = 0; y < h; y++) {
- for (x = 0; x < w; x++) {
- // lol i need to learn what this part does
- index = (y * w + x) * 4; // OHHH OK this is setting the imagedata.data uint8clampeddataarray index for the specified x/y coords
- //this part i get, this is checking that 4th RGBA byte for opacity
- if (imageData.data[index + 3] > 0) {
- pix.x.push(x);
- pix.y.push(y);
- }
- }
- }
- // ...need to learn what this part does too :badpokerface:
- // is this just determining the boundaries of non-transparent pixel data?
- pix.x.sort(function (a, b) {
- return a - b;
- });
- pix.y.sort(function (a, b) {
- return a - b;
- });
- var n = pix.x.length - 1;
- w = pix.x[n] - pix.x[0] + 1;
- h = pix.y[n] - pix.y[0] + 1;
- // yup sure looks like it
-
- try {
- var cut = sourceCanvas
- .getContext("2d")
- .getImageData(pix.x[0], pix.y[0], w, h);
- var cutCanvas = document.createElement("canvas");
- cutCanvas.width = w;
- cutCanvas.height = h;
- cutCanvas.getContext("2d").putImageData(cut, 0, 0);
- } catch (ex) {
- // probably empty image
- //TODO confirm edge cases?
- cutCanvas = null;
- }
- return cutCanvas;
-}
-
function checkIfWebuiIsRunning() {
var url = document.getElementById("host").value + "/startup-events";
fetch(url)
diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js
index 409a25d..6bd6a76 100644
--- a/js/ui/tool/select.js
+++ b/js/ui/tool/select.js
@@ -20,6 +20,8 @@ const selectTransformTool = () =>
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
keyboard.onShortcut({ctrl: true, key: "KeyS"}, state.ctrlscb);
+
+ state.selected = null;
},
(state, opt) => {
mouse.listen.canvas.onmousemove.clear(state.movecb);
@@ -50,7 +52,16 @@ const selectTransformTool = () =>
state.original = null;
state.dragging = null;
- state.selected = null;
+ state._selected = null;
+ Object.defineProperty(state, "selected", {
+ get: () => state._selected,
+ set: (v) => {
+ if (v) state.ctxmenu.enableButtons();
+ else state.ctxmenu.disableButtons();
+
+ return (state._selected = v);
+ },
+ });
state.moving = null;
state.lastMouseTarget = null;
@@ -503,12 +514,52 @@ const selectTransformTool = () =>
state.ctxmenu.useClipboardLabel = clipboardCheckbox.label;
if (!navigator.clipboard.write)
clipboardCheckbox.checkbox.disabled = true; // Disable if not available
+
+ // Some useful actions to do with selection
+ const actionArray = document.createElement("div");
+ actionArray.classList.add("button-array");
+
+ const saveSelectionButton = document.createElement("button");
+ saveSelectionButton.classList.add("button", "tool");
+ saveSelectionButton.textContent = "Save";
+ saveSelectionButton.title = "Saves Selection";
+ saveSelectionButton.onclick = () => {
+ downloadCanvas({
+ cropToContent: false,
+ canvas: state.selected.image,
+ });
+ };
+
+ const createResourceButton = document.createElement("button");
+ createResourceButton.classList.add("button", "tool");
+ createResourceButton.textContent = "Resource";
+ createResourceButton.title = "Saves Selection as a Resource";
+ createResourceButton.onclick = () => {
+ const image = document.createElement("img");
+ image.src = state.selected.image.toDataURL();
+ tools.stamp.state.addResource("Selection Resource", image);
+ tools.stamp.enable();
+ };
+
+ actionArray.appendChild(saveSelectionButton);
+ actionArray.appendChild(createResourceButton);
+
+ state.ctxmenu.disableButtons = () => {
+ saveSelectionButton.disabled = true;
+ createResourceButton.disabled = true;
+ };
+ state.ctxmenu.enableButtons = () => {
+ saveSelectionButton.disabled = "";
+ createResourceButton.disabled = "";
+ };
+ state.ctxmenu.actionArray = actionArray;
}
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.useClipboardLabel);
+ menu.appendChild(state.ctxmenu.actionArray);
},
shortcut: "S",
}
diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js
index 44efcb3..00e3ce3 100644
--- a/js/ui/tool/stamp.js
+++ b/js/ui/tool/stamp.js
@@ -49,25 +49,49 @@ const stampTool = () =>
state.selected = null;
state.back = null;
+ const selectResource = (resource) => {
+ if (state.ctxmenu.uploadButton.disabled) return;
+
+ const resourceWrapper = resource.dom.wrapper;
+
+ const wasSelected = resourceWrapper.classList.contains("selected");
+
+ Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
+ child.classList.remove("selected");
+ });
+
+ // Select
+ if (!wasSelected) {
+ resourceWrapper.classList.add("selected");
+ state.selected = resource;
+ }
+ // If already selected, clear selection
+ else {
+ resourceWrapper.classList.remove("selected");
+ state.selected = null;
+ }
+ };
+
+ // Synchronizes resources array with the DOM
const syncResources = () => {
+ // Creates DOM elements when needed
state.resources.forEach((resource) => {
- if (!document.getElementById(`resource-${resource.id}`)) {
+ if (
+ !state.ctxmenu.resourceList.querySelector(
+ `#resource-${resource.id}`
+ )
+ ) {
+ console.debug(
+ `Creating resource element 'resource-${resource.id}'`
+ );
const resourceWrapper = document.createElement("div");
resourceWrapper.id = `resource-${resource.id}`;
resourceWrapper.textContent = resource.name;
resourceWrapper.classList.add("resource");
- resourceWrapper.addEventListener("click", () => {
- if (state.ctxmenu.uploadButton.disabled) return;
- state.selected = resource;
- Array.from(state.ctxmenu.resourceList.children).forEach(
- (child) => {
- child.classList.remove("selected");
- }
- );
-
- resourceWrapper.classList.add("selected");
- });
+ resourceWrapper.addEventListener("click", () =>
+ selectResource(resource)
+ );
resourceWrapper.addEventListener("mouseover", () => {
state.ctxmenu.previewPane.style.display = "block";
@@ -78,16 +102,17 @@ const stampTool = () =>
});
state.ctxmenu.resourceList.appendChild(resourceWrapper);
+ resource.dom = {wrapper: resourceWrapper};
}
});
+ // Removes DOM elements when needed
const elements = Array.from(state.ctxmenu.resourceList.children);
if (elements.length > state.resources.length)
elements.forEach((element) => {
let remove = true;
state.resources.some((resource) => {
- console.debug(element.id, resource.id);
if (element.id.endsWith(resource.id)) remove = false;
});
@@ -95,6 +120,7 @@ const stampTool = () =>
});
};
+ // Adds a image resource (temporary allows only one draw, used for pasting)
state.addResource = (name, image, temporary = false) => {
const id = guid();
const resource = {
@@ -105,8 +131,15 @@ const stampTool = () =>
};
state.resources.push(resource);
syncResources();
+
+ // Select this resource
+ selectResource(resource);
+
return resource;
};
+
+ // Deletes a resource (Yes, functionality is here, but we don't have an UI for this yet)
+ // Used for temporary images too
state.deleteResource = (id) => {
state.resources = state.resources.filter((v) => v.id !== id);
@@ -175,8 +208,10 @@ const stampTool = () =>
}
}
};
- },
- populateContextMenu: (menu, state) => {
+
+ /**
+ * Creates context menu
+ */
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
@@ -218,7 +253,7 @@ const stampTool = () =>
const image = document.createElement("img");
image.src = url.createObjectURL(file);
- state.selected = state.addResource(file.name, image, false);
+ state.addResource(file.name, image, false);
}
});
@@ -272,7 +307,8 @@ const stampTool = () =>
state.ctxmenu.resourceManager = resourceManager;
state.ctxmenu.resourceList = resourceList;
}
-
+ },
+ populateContextMenu: (menu, state) => {
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(state.ctxmenu.resourceManager);
},
diff --git a/js/util.js b/js/util.js
index bb75916..2383e9e 100644
--- a/js/util.js
+++ b/js/util.js
@@ -92,3 +92,77 @@ function getBoundingBox(cx, cy, w, h, gridSnap = null) {
h,
};
}
+
+/**
+ * Triggers Canvas Download
+ */
+function cropCanvas(sourceCanvas) {
+ var w = sourceCanvas.width;
+ var h = sourceCanvas.height;
+ var pix = {x: [], y: []};
+ var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h);
+ var x, y, index;
+
+ for (y = 0; y < h; y++) {
+ for (x = 0; x < w; x++) {
+ // lol i need to learn what this part does
+ index = (y * w + x) * 4; // OHHH OK this is setting the imagedata.data uint8clampeddataarray index for the specified x/y coords
+ //this part i get, this is checking that 4th RGBA byte for opacity
+ if (imageData.data[index + 3] > 0) {
+ pix.x.push(x);
+ pix.y.push(y);
+ }
+ }
+ }
+ // ...need to learn what this part does too :badpokerface:
+ // is this just determining the boundaries of non-transparent pixel data?
+ pix.x.sort(function (a, b) {
+ return a - b;
+ });
+ pix.y.sort(function (a, b) {
+ return a - b;
+ });
+ var n = pix.x.length - 1;
+ w = pix.x[n] - pix.x[0] + 1;
+ h = pix.y[n] - pix.y[0] + 1;
+ // yup sure looks like it
+
+ try {
+ var cut = sourceCanvas
+ .getContext("2d")
+ .getImageData(pix.x[0], pix.y[0], w, h);
+ var cutCanvas = document.createElement("canvas");
+ cutCanvas.width = w;
+ cutCanvas.height = h;
+ cutCanvas.getContext("2d").putImageData(cut, 0, 0);
+ } catch (ex) {
+ // probably empty image
+ //TODO confirm edge cases?
+ cutCanvas = null;
+ }
+ return cutCanvas;
+}
+
+function downloadCanvas(options) {
+ defaultOpt(options, {
+ cropToContent: true,
+ canvas: imgCanvas,
+ filename:
+ new Date()
+ .toISOString()
+ .slice(0, 19)
+ .replace("T", " ")
+ .replace(":", " ") + " openOutpaint image.png",
+ });
+
+ var link = document.createElement("a");
+ link.download = options.filename;
+
+ var croppedCanvas = options.cropToContent
+ ? cropCanvas(options.canvas)
+ : options.canvas;
+ if (croppedCanvas != null) {
+ link.href = croppedCanvas.toDataURL("image/png");
+ link.click();
+ }
+}