Some quick lunch patches

Allows for selections and dreams to be sent to the resource manager,
allows resource deselection when clicking on the currently selected
item, put some extra comments and allow saving of a selected canvas area

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>

Former-commit-id: 963312115d4827de1c8d4fac88a97dd64db7fa15
This commit is contained in:
Victor Seiji Hariki 2022-11-25 13:16:22 -03:00
parent db73982df2
commit df5dc32eee
5 changed files with 198 additions and 78 deletions

View file

@ -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);
}

View file

@ -204,7 +204,8 @@ function imageAcceptReject(x, y, data, extra = null) {
div.style.width = "200px";
div.style.height = "70px";
div.innerHTML =
'<button onclick="prevImg(this)">&lt;</button><button onclick="nextImg(this)">&gt;</button><span class="strokeText" id="currentImgIndex"></span><span class="strokeText"> of </span><span class="strokeText" id="totalImgIndex"></span><button onclick="accept(this)">Y</button><button onclick="reject(this)">N</button><span class="strokeText" id="estRemaining"></span>';
'<button onclick="prevImg(this)">&lt;</button><button onclick="nextImg(this)">&gt;</button><span class="strokeText" id="currentImgIndex"></span><span class="strokeText"> of </span><span class="strokeText" id="totalImgIndex"></span><button onclick="accept(this)">Y</button><button onclick="reject(this)">N</button><button onclick="resource(this)">RES</button><span class="strokeText" id="estRemaining"></span>';
document.getElementById("tempDiv").appendChild(div);
document.getElementById("currentImgIndex").innerText = "1";
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
@ -237,6 +238,18 @@ 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();
@ -576,65 +589,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)

View file

@ -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",
}

View file

@ -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);
},

View file

@ -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();
}
}