diff --git a/.docs/.eleventy.js b/.docs/.eleventy.js index cddd9b5..f410716 100644 --- a/.docs/.eleventy.js +++ b/.docs/.eleventy.js @@ -75,7 +75,7 @@ async function fetch(url) { } module.exports = function(eleventyConfig) { - + eleventyConfig.addPassthroughCopy("img2brush/img2brush.js"); eleventyConfig.addAsyncShortcode("fetch", fetch); // eleventyConfig.addPassthroughCopy("images"); diff --git a/.docs/css/theme.css b/.docs/css/theme.css index 22658f3..01a98e6 100644 --- a/.docs/css/theme.css +++ b/.docs/css/theme.css @@ -411,3 +411,30 @@ footer { flex-direction: column; align-items: center; } + +@keyframes move-diagonal { + from { + background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; + } + to { + background-position: -52px -52px, -52px -52px, -51px -51px, -51px -51px; + } +} + + +#dropzone { + border: 0.3em dashed #aaaaaa; + transition: border 0.2s; + justify-content: flex-start; +} +#dropzone.dropzone-active { + border: 0.3em dashed hsl(203, 79%, 55%); + + /* Ref https://www.magicpattern.design/tools/css-backgrounds */ + background-image: linear-gradient(var(--bg-bright) 2px, transparent 2px), linear-gradient(90deg, var(--bg-bright) 2px, transparent 2px), linear-gradient(var(--bg-bright) 1px, transparent 1px), linear-gradient(90deg, var(--bg-bright) 1px, var(--bg-transcluscent) 1px); + background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px; + background-position: -2px -2px, -2px -2px, -1px -1px, -1px -1px; + + animation: move-diagonal 5s linear infinite; +} +#brushimg-preview { flex: 1; } diff --git a/.docs/img2brush/img2brush.js b/.docs/img2brush/img2brush.js new file mode 100644 index 0000000..0ab1c30 --- /dev/null +++ b/.docs/img2brush/img2brush.js @@ -0,0 +1,97 @@ +window.addEventListener("load", () => { + let dropzone = document.querySelector("#dropzone"); + dropzone.addEventListener("dragenter", handle_drag_enter); + dropzone.addEventListener("dragleave", handle_drag_leave); + dropzone.addEventListener("dragover", handle_drag_over); + dropzone.addEventListener("drop", handle_drop); + + document.querySelector("#brushimg-tsv").addEventListener("click", select_output); + let button_copy = document.querySelector("#brushimg-copy") + button_copy.addEventListener("click", () => { + select_output(); + button_copy.innerHTML = document.execCommand("copy") ? "Copied!" : "Failed to copy :-("; + }) +}); + +function select_output() { + let output = document.querySelector("#brushimg-tsv"); + + let selection = window.getSelection(); + + if (selection.rangeCount > 0) + selection.removeAllRanges(); + + let range = document.createRange(); + range.selectNode(output); + selection.addRange(range); +} + + +function handle_drag_enter(event) { + event.target.classList.add("dropzone-active"); +} +function handle_drag_leave(event) { + event.target.classList.remove("dropzone-active"); +} + +function handle_drag_over(event) { + event.preventDefault(); +} + +function handle_drop(event) { + event.stopPropagation(); + event.preventDefault(); + event.target.classList.remove("dropzone-active"); + + let image_file = null; + + image_file = event.dataTransfer.files[0]; + + let reader = new FileReader(); + reader.addEventListener("load", function(_event) { + let image = new Image(); + image.src = reader.result; + image.addEventListener("load", () => handle_new_image(image)); + + + document.querySelector("#brushimg-preview").src = image.src; + }); + reader.readAsDataURL(image_file); + + return false; +} + +function image2pixels(image) { + let canvas = document.createElement("canvas"), + ctx = canvas.getContext("2d"); + + canvas.width = image.width; + canvas.height = image.height; + + ctx.drawImage(image, 0, 0); + + return ctx.getImageData(0, 0, image.width, image.height); +} + +function handle_new_image(image) { + let tsv = pixels2tsv(image2pixels(image)); + document.querySelector("#brushimg-stats").value = `${image.width} x ${image.height} | ${image.width * image.height} pixels`; + document.querySelector("#brushimg-tsv").value = tsv; +} + +function round(number, decimal_places = 0) { + let multiplier = Math.pow(10, decimal_places); + return Math.round(number * multiplier) / multiplier; +} + +function pixels2tsv(pixels) { + let result = ""; + for(let y = 0; y < pixels.height; y++) { + let row = []; + for(let x = 0; x < pixels.width; x++) { + row.push(round(pixels.data[((y*pixels.width + x) * 4) + 3], 3)); + } + result += row.join(`\t`) + `\n`; + } + return result; +} diff --git a/.docs/img2brush/index.html b/.docs/img2brush/index.html new file mode 100644 index 0000000..4c4b594 --- /dev/null +++ b/.docs/img2brush/index.html @@ -0,0 +1,34 @@ +--- +layout: theme.njk +title: Image to brush converter +--- + +
+

Image to sculpting brush converter

+ +

Convert any image to a sculpting brush here! The alpha (opacity) channel is used to determine the weight of the brush - all colour is ignored.

+
+ +
+

Input

+

Drop your image here.

+ + + + +
+ + +
+

Output

+ +

Paste the output below into a text file in worldeditadditions/lib/sculpt/brushes with the file extension .txt and restart your Minetest server for the brush to be recognised.

+ +

+ +

+ +
(your output will appear here as soon as you drop an image above)
+
+ +