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;
|
||||
}
|
||||
|
||||
.button.tool:disabled {
|
||||
background-color: #666 !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button.tool:hover {
|
||||
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.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;
|
||||
|
@ -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)
|
||||
|
|
|
@ -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