openOutpaint/js/util.js
Victor Seiji Hariki df5dc32eee 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
2022-11-25 13:16:22 -03:00

168 lines
3.7 KiB
JavaScript

/**
* Implementation of a simple Oberver Pattern for custom event handling
*/
function Observer() {
this.handlers = new Set();
}
Observer.prototype = {
// Adds handler for this message
on(callback) {
this.handlers.add(callback);
return callback;
},
clear(callback) {
return this.handlers.delete(callback);
},
emit(msg) {
this.handlers.forEach(async (handler) => {
try {
await handler(msg);
} catch (e) {
console.warn("Observer failed to run handler");
console.warn(e);
}
});
},
};
/**
* Generates unique id
*/
const guid = (size = 3) => {
const s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
// returns id of format 'aaaaaaaa'-'aaaa'-'aaaa'-'aaaa'-'aaaaaaaaaaaa'
let id = "";
for (var i = 0; i < size - 1; i++) id += s4() + "-";
id += s4();
return id;
};
/**
* Default option set
*/
function defaultOpt(options, defaults) {
Object.keys(defaults).forEach((key) => {
if (options[key] === undefined) options[key] = defaults[key];
});
}
/**
* Bounding box Calculation
*/
function snap(i, scaled = true, gridSize = 64) {
// very cheap test proof of concept but it works surprisingly well
var scaleOffset = 0;
if (scaled) {
if (scaleFactor % 2 != 0) {
// odd number, snaps to center of cell, oops
scaleOffset = gridSize / 2;
}
}
const modulus = i % gridSize;
var snapOffset = modulus - scaleOffset;
if (modulus > gridSize / 2) snapOffset = modulus - gridSize;
if (snapOffset == 0) {
return snapOffset;
}
return -snapOffset;
}
function getBoundingBox(cx, cy, w, h, gridSnap = null) {
const offset = {x: 0, y: 0};
const box = {x: 0, y: 0};
if (gridSnap) {
offset.x = snap(cx, true, gridSnap);
offset.y = snap(cy, true, gridSnap);
}
box.x = offset.x + cx;
box.y = offset.y + cy;
return {
x: Math.floor(box.x - w / 2),
y: Math.floor(box.y - h / 2),
w,
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();
}
}