mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-09 08:16:34 +02:00
Added cropping UI to img2scad.html
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
<!--
|
||||
Standalone web app to convert an image file to an OpenSCAD array, for use with BOSL2 textures.
|
||||
Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
Version 6: 23 April 2025 - added cropping UI
|
||||
-->
|
||||
<title>Image to OpenSCAD array, v5</title>
|
||||
<title>Image to OpenSCAD array, v6</title>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body { font-family: sans-serif; padding-left:1em; padding-right:1em;}
|
||||
@@ -120,7 +121,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
</head>
|
||||
<body>
|
||||
<h1>Convert image to OpenSCAD array</h1>
|
||||
<p>This utility accepts any raster image and converts it to grayscale expanded to use the maximum possible luminance range. Alpha channel is ignored. After resizing, rotating, or reflecting the image as desired, you may save it as an OpenSCAD array.</p>
|
||||
<p>This utility accepts any image that can be displayed in your browser, and converts it to grayscale expanded to use the maximum
|
||||
possible luminance range. Alpha channel is ignored. After processing the image as desired, you may save it as an OpenSCAD array.</p>
|
||||
<hr>
|
||||
<div id="content">
|
||||
<div class="uiContainer" id="inputArea" tabindex="0">
|
||||
@@ -167,7 +169,6 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
</div>
|
||||
|
||||
<h3>Appearance</h3>
|
||||
|
||||
<input type="radio" name="grayModel" value="ntsc" checked><label for "grayModel" class="tooltip"> NTSC grayscale formula
|
||||
<span class="tooltiptext">0.299R + 0.587G + 0.114B<br>Based on average human perception of color luminance</span></label><br>
|
||||
<input type="radio" name="grayModel" value="linear"><label for="grayModel" class="tooltip"> Linear luminance
|
||||
@@ -204,6 +205,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
<hr>
|
||||
|
||||
<script>
|
||||
// get page element handles
|
||||
|
||||
const imageInput = document.getElementById('imageInput');
|
||||
const downloadButton = document.getElementById('downloadButton');
|
||||
const resizeWidthInput = document.getElementById('resizeWidth');
|
||||
@@ -225,13 +228,17 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
const originalCanvas = document.getElementById('originalCanvas');
|
||||
const grayscaleCanvas = document.getElementById('grayscaleCanvas');
|
||||
|
||||
// other initializations
|
||||
|
||||
const originalCtx = originalCanvas.getContext('2d');
|
||||
const grayscaleCtx = grayscaleCanvas.getContext('2d');
|
||||
|
||||
const cropID = [ cropRight, cropTop, cropLeft, cropBottom ];
|
||||
let edgeID = [ 0, 1, 2 ,3 ];
|
||||
const cropID = [ cropRight, cropTop, cropLeft, cropBottom ]; // counterclockwise from right
|
||||
let edgeID = [ 0, 1, 2 ,3 ]; // counterclockwise from right: right, top, left, bottom
|
||||
const edgeconfig = [
|
||||
// noFlip flipH flipV flipV+H
|
||||
// IDs of crop gadgets corresponding to image edges, from right edge counterclockwise,
|
||||
// in all combinations of rotations and flips.
|
||||
// no flip flipH flipV flipV+H
|
||||
/* 0*/ [[0,1,2,3], [2,1,0,3], [0,3,2,1], [2,3,0,1]],
|
||||
/* 90*/ [[3,0,1,2], [1,0,3,2], [3,2,1,0], [1,2,3,0]],
|
||||
/*180*/ [[2,3,0,1], [0,3,2,1], [2,1,0,3], [0,1,2,3]],
|
||||
@@ -247,6 +254,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
let uncropDim = { width:0, height:0 };
|
||||
let cropDim = { width:0, height:0 };
|
||||
|
||||
// image processing functions
|
||||
|
||||
function getGrayscaleModel() {
|
||||
return document.querySelector('input[name="grayModel"]:checked').value;
|
||||
}
|
||||
@@ -427,20 +436,28 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
fileSuffix = finalWidth.toString()+"x"+finalHeight.toString();
|
||||
}
|
||||
|
||||
// loading an image
|
||||
|
||||
function resetInputs() { // executed after an image loads
|
||||
cropLeft.value="0";
|
||||
cropRight.value="0";
|
||||
cropTop.value="0";
|
||||
cropBottom.value="0";
|
||||
rotation = 0;
|
||||
flipV = flipH = false;
|
||||
resizeWidthInput.value = "100";
|
||||
blurRadiusInput.value = "0";
|
||||
invertBrightnessCheckbox.checked = false;
|
||||
}
|
||||
|
||||
// user pressed button to load image from disk
|
||||
imageInput.addEventListener('change', function () {
|
||||
const file = this.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
currentImage.onload = function () {
|
||||
cropLeft.value="0";
|
||||
cropRight.value="0";
|
||||
cropTop.value="0";
|
||||
cropBottom.value="0";
|
||||
rotate = 0;
|
||||
flipV = flipH = false;
|
||||
resizeWidthInput.value = "100";
|
||||
blurRadiusInput.value = "0";
|
||||
resetInputs();
|
||||
processImage();
|
||||
};
|
||||
currentImage.src = e.target.result;
|
||||
@@ -449,6 +466,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
}
|
||||
});
|
||||
|
||||
// user pasted an image from the clipboard into the input area
|
||||
inputArea.addEventListener('paste', function (event) {
|
||||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||
for (const item of items) {
|
||||
@@ -457,6 +475,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
currentImage.onload = function () {
|
||||
resetInputs();
|
||||
processImage();
|
||||
};
|
||||
currentImage.src = e.target.result;
|
||||
@@ -466,6 +485,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
}
|
||||
});
|
||||
|
||||
// set up events for all the input gadgets
|
||||
|
||||
[resizeWidthInput, invertBrightnessCheckbox, normalizeToUnitCheckbox, blurRadiusInput,
|
||||
...document.querySelectorAll('input[name="grayModel"]')].forEach(el => el.addEventListener('input', processImage));
|
||||
|
||||
@@ -546,6 +567,9 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
||||
processImage();
|
||||
});
|
||||
|
||||
// saving the file - try to use "Save As" file picker,
|
||||
// fall back to saving with a default name to browser's downloads directory.
|
||||
|
||||
downloadButton.addEventListener('click', () => {
|
||||
if (grayscaleMatrix.length === 0) return alert("No data to save.");
|
||||
const useUnit = normalizeToUnitCheckbox.checked;
|
||||
|
Reference in New Issue
Block a user