Merge pull request #50 from seijihariki/quick-patches
Dream and Selection - Resource Manager Interaction and Selected Area saving Former-commit-id: d361b4731686b52d5dc48a4717d637569a03180a
This commit is contained in:
commit
905a815cf2
5 changed files with 204 additions and 79 deletions
|
@ -64,6 +64,11 @@ body {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.tool:disabled {
|
||||||
|
background-color: #666 !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
.button.tool:hover {
|
.button.tool:hover {
|
||||||
background-color: rgb(30, 30, 80);
|
background-color: rgb(30, 30, 80);
|
||||||
}
|
}
|
||||||
|
|
81
js/index.js
81
js/index.js
|
@ -205,7 +205,8 @@ function imageAcceptReject(x, y, data, extra = null) {
|
||||||
div.style.width = "200px";
|
div.style.width = "200px";
|
||||||
div.style.height = "70px";
|
div.style.height = "70px";
|
||||||
div.innerHTML =
|
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("tempDiv").appendChild(div);
|
||||||
document.getElementById("currentImgIndex").innerText = "1";
|
document.getElementById("currentImgIndex").innerText = "1";
|
||||||
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
|
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
|
||||||
|
@ -238,11 +239,28 @@ function reject(evt) {
|
||||||
blockNewImages = false;
|
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) {
|
function newImage(evt) {
|
||||||
clearPaintedMask();
|
clearPaintedMask();
|
||||||
clearBackupMask();
|
clearBackupMask();
|
||||||
clearTargetMask();
|
clearTargetMask();
|
||||||
clearImgMask();
|
commands.runCommand("eraseImage", "Clear Canvas", {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: imgCanvas.width,
|
||||||
|
h: imgCanvas.height,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevImg(evt) {
|
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() {
|
function checkIfWebuiIsRunning() {
|
||||||
var url = document.getElementById("host").value + "/startup-events";
|
var url = document.getElementById("host").value + "/startup-events";
|
||||||
fetch(url)
|
fetch(url)
|
||||||
|
|
|
@ -20,6 +20,8 @@ const selectTransformTool = () =>
|
||||||
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
|
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
|
||||||
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
|
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
|
||||||
keyboard.onShortcut({ctrl: true, key: "KeyS"}, state.ctrlscb);
|
keyboard.onShortcut({ctrl: true, key: "KeyS"}, state.ctrlscb);
|
||||||
|
|
||||||
|
state.selected = null;
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
||||||
|
@ -50,7 +52,16 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
state.original = null;
|
state.original = null;
|
||||||
state.dragging = 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.moving = null;
|
||||||
|
|
||||||
state.lastMouseTarget = null;
|
state.lastMouseTarget = null;
|
||||||
|
@ -503,12 +514,52 @@ const selectTransformTool = () =>
|
||||||
state.ctxmenu.useClipboardLabel = clipboardCheckbox.label;
|
state.ctxmenu.useClipboardLabel = clipboardCheckbox.label;
|
||||||
if (!navigator.clipboard.write)
|
if (!navigator.clipboard.write)
|
||||||
clipboardCheckbox.checkbox.disabled = true; // Disable if not available
|
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(state.ctxmenu.snapToGridLabel);
|
||||||
menu.appendChild(document.createElement("br"));
|
menu.appendChild(document.createElement("br"));
|
||||||
menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
|
menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
|
||||||
menu.appendChild(document.createElement("br"));
|
menu.appendChild(document.createElement("br"));
|
||||||
menu.appendChild(state.ctxmenu.useClipboardLabel);
|
menu.appendChild(state.ctxmenu.useClipboardLabel);
|
||||||
|
menu.appendChild(state.ctxmenu.actionArray);
|
||||||
},
|
},
|
||||||
shortcut: "S",
|
shortcut: "S",
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,25 +49,49 @@ const stampTool = () =>
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
state.back = 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 = () => {
|
const syncResources = () => {
|
||||||
|
// Creates DOM elements when needed
|
||||||
state.resources.forEach((resource) => {
|
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");
|
const resourceWrapper = document.createElement("div");
|
||||||
resourceWrapper.id = `resource-${resource.id}`;
|
resourceWrapper.id = `resource-${resource.id}`;
|
||||||
resourceWrapper.textContent = resource.name;
|
resourceWrapper.textContent = resource.name;
|
||||||
resourceWrapper.classList.add("resource");
|
resourceWrapper.classList.add("resource");
|
||||||
|
|
||||||
resourceWrapper.addEventListener("click", () => {
|
resourceWrapper.addEventListener("click", () =>
|
||||||
if (state.ctxmenu.uploadButton.disabled) return;
|
selectResource(resource)
|
||||||
state.selected = resource;
|
);
|
||||||
Array.from(state.ctxmenu.resourceList.children).forEach(
|
|
||||||
(child) => {
|
|
||||||
child.classList.remove("selected");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
resourceWrapper.classList.add("selected");
|
|
||||||
});
|
|
||||||
|
|
||||||
resourceWrapper.addEventListener("mouseover", () => {
|
resourceWrapper.addEventListener("mouseover", () => {
|
||||||
state.ctxmenu.previewPane.style.display = "block";
|
state.ctxmenu.previewPane.style.display = "block";
|
||||||
|
@ -78,16 +102,17 @@ const stampTool = () =>
|
||||||
});
|
});
|
||||||
|
|
||||||
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
||||||
|
resource.dom = {wrapper: resourceWrapper};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Removes DOM elements when needed
|
||||||
const elements = Array.from(state.ctxmenu.resourceList.children);
|
const elements = Array.from(state.ctxmenu.resourceList.children);
|
||||||
|
|
||||||
if (elements.length > state.resources.length)
|
if (elements.length > state.resources.length)
|
||||||
elements.forEach((element) => {
|
elements.forEach((element) => {
|
||||||
let remove = true;
|
let remove = true;
|
||||||
state.resources.some((resource) => {
|
state.resources.some((resource) => {
|
||||||
console.debug(element.id, resource.id);
|
|
||||||
if (element.id.endsWith(resource.id)) remove = false;
|
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) => {
|
state.addResource = (name, image, temporary = false) => {
|
||||||
const id = guid();
|
const id = guid();
|
||||||
const resource = {
|
const resource = {
|
||||||
|
@ -105,8 +131,15 @@ const stampTool = () =>
|
||||||
};
|
};
|
||||||
state.resources.push(resource);
|
state.resources.push(resource);
|
||||||
syncResources();
|
syncResources();
|
||||||
|
|
||||||
|
// Select this resource
|
||||||
|
selectResource(resource);
|
||||||
|
|
||||||
return 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.deleteResource = (id) => {
|
||||||
state.resources = state.resources.filter((v) => v.id !== 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) {
|
if (!state.ctxmenu) {
|
||||||
state.ctxmenu = {};
|
state.ctxmenu = {};
|
||||||
// Snap To Grid Checkbox
|
// Snap To Grid Checkbox
|
||||||
|
@ -218,7 +253,7 @@ const stampTool = () =>
|
||||||
const image = document.createElement("img");
|
const image = document.createElement("img");
|
||||||
image.src = url.createObjectURL(file);
|
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.resourceManager = resourceManager;
|
||||||
state.ctxmenu.resourceList = resourceList;
|
state.ctxmenu.resourceList = resourceList;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
populateContextMenu: (menu, state) => {
|
||||||
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
menu.appendChild(state.ctxmenu.snapToGridLabel);
|
||||||
menu.appendChild(state.ctxmenu.resourceManager);
|
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,
|
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