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:
parent
db73982df2
commit
df5dc32eee
5 changed files with 198 additions and 78 deletions
|
@ -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);
|
||||
}
|
||||
|
|
74
js/index.js
74
js/index.js
|
@ -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)"><</button><button onclick="nextImg(this)">></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)"><</button><button onclick="nextImg(this)">></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)
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
74
js/util.js
74
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();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue