mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-12 08:54:07 +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.
|
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
|
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">
|
<meta charset="UTF-8">
|
||||||
<style>
|
<style>
|
||||||
body { font-family: sans-serif; padding-left:1em; padding-right:1em;}
|
body { font-family: sans-serif; padding-left:1em; padding-right:1em;}
|
||||||
@@ -61,7 +62,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* cropping control panel stuff */
|
/* cropping control panel stuff */
|
||||||
|
|
||||||
.crop-container {
|
.crop-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -76,7 +77,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
.crop-center {
|
.crop-center {
|
||||||
grid-area: center;
|
grid-area: center;
|
||||||
@@ -86,7 +87,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
border: 2px dashed #ccc;
|
border: 2px dashed #ccc;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -120,7 +121,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Convert image to OpenSCAD array</h1>
|
<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>
|
<hr>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<div class="uiContainer" id="inputArea" tabindex="0">
|
<div class="uiContainer" id="inputArea" tabindex="0">
|
||||||
@@ -146,28 +148,27 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
<button id="flipHorizontal">⇋ Flip horizontal</button>
|
<button id="flipHorizontal">⇋ Flip horizontal</button>
|
||||||
<button id="flipVertical">⇵ Flip vertical</button>
|
<button id="flipVertical">⇵ Flip vertical</button>
|
||||||
|
|
||||||
<div class="crop-container">
|
<div class="crop-container">
|
||||||
<div class="crop-control crop-top">
|
<div class="crop-control crop-top">
|
||||||
<label for="crop-top">Top</label>
|
<label for="crop-top">Top</label>
|
||||||
<input type="number" id="cropTop" min="0" max="9999" value="0">
|
<input type="number" id="cropTop" min="0" max="9999" value="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="crop-control crop-left">
|
<div class="crop-control crop-left">
|
||||||
<label for="crop-left">Left</label>
|
<label for="crop-left">Left</label>
|
||||||
<input type="number" id="cropLeft" min="0" max="9999" value="0">
|
<input type="number" id="cropLeft" min="0" max="9999" value="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="crop-center">Crop</div>
|
<div class="crop-center">Crop</div>
|
||||||
<div class="crop-control crop-right">
|
<div class="crop-control crop-right">
|
||||||
<label for="crop-right">Right</label>
|
<label for="crop-right">Right</label>
|
||||||
<input type="number" id="cropRight" min="0" max="9999" value="0">
|
<input type="number" id="cropRight" min="0" max="9999" value="0">
|
||||||
</div>
|
</div>
|
||||||
<div class="crop-control crop-bottom">
|
<div class="crop-control crop-bottom">
|
||||||
<input type="number" id="cropBottom" min="0" max="9999" value="0">
|
<input type="number" id="cropBottom" min="0" max="9999" value="0">
|
||||||
<label for="crop-bottom">Bottom</label>
|
<label for="crop-bottom">Bottom</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Appearance</h3>
|
<h3>Appearance</h3>
|
||||||
|
|
||||||
<input type="radio" name="grayModel" value="ntsc" checked><label for "grayModel" class="tooltip"> NTSC grayscale formula
|
<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>
|
<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
|
<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>
|
<hr>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// get page element handles
|
||||||
|
|
||||||
const imageInput = document.getElementById('imageInput');
|
const imageInput = document.getElementById('imageInput');
|
||||||
const downloadButton = document.getElementById('downloadButton');
|
const downloadButton = document.getElementById('downloadButton');
|
||||||
const resizeWidthInput = document.getElementById('resizeWidth');
|
const resizeWidthInput = document.getElementById('resizeWidth');
|
||||||
@@ -215,37 +218,43 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
const rotateRightBtn = document.getElementById('rotateRight');
|
const rotateRightBtn = document.getElementById('rotateRight');
|
||||||
const flipHorizontalBtn = document.getElementById('flipHorizontal');
|
const flipHorizontalBtn = document.getElementById('flipHorizontal');
|
||||||
const flipVerticalBtn = document.getElementById('flipVertical');
|
const flipVerticalBtn = document.getElementById('flipVertical');
|
||||||
const cropTop = document.getElementById('cropTop');
|
const cropTop = document.getElementById('cropTop');
|
||||||
const cropLeft = document.getElementById('cropLeft');
|
const cropLeft = document.getElementById('cropLeft');
|
||||||
const cropRight = document.getElementById('cropRight');
|
const cropRight = document.getElementById('cropRight');
|
||||||
const cropBottom = document.getElementById('cropBottom');
|
const cropBottom = document.getElementById('cropBottom');
|
||||||
const blurRadiusInput = document.getElementById('blurRadius');
|
const blurRadiusInput = document.getElementById('blurRadius');
|
||||||
const arrayName = document.getElementById('arrayName');
|
const arrayName = document.getElementById('arrayName');
|
||||||
const inputArea = document.getElementById('inputArea');
|
const inputArea = document.getElementById('inputArea');
|
||||||
const originalCanvas = document.getElementById('originalCanvas');
|
const originalCanvas = document.getElementById('originalCanvas');
|
||||||
const grayscaleCanvas = document.getElementById('grayscaleCanvas');
|
const grayscaleCanvas = document.getElementById('grayscaleCanvas');
|
||||||
|
|
||||||
|
// other initializations
|
||||||
|
|
||||||
const originalCtx = originalCanvas.getContext('2d');
|
const originalCtx = originalCanvas.getContext('2d');
|
||||||
const grayscaleCtx = grayscaleCanvas.getContext('2d');
|
const grayscaleCtx = grayscaleCanvas.getContext('2d');
|
||||||
|
|
||||||
const cropID = [ cropRight, cropTop, cropLeft, cropBottom ];
|
const cropID = [ cropRight, cropTop, cropLeft, cropBottom ]; // counterclockwise from right
|
||||||
let edgeID = [ 0, 1, 2 ,3 ];
|
let edgeID = [ 0, 1, 2 ,3 ]; // counterclockwise from right: right, top, left, bottom
|
||||||
const edgeconfig = [
|
const edgeconfig = [
|
||||||
// noFlip flipH flipV flipV+H
|
// IDs of crop gadgets corresponding to image edges, from right edge counterclockwise,
|
||||||
/* 0*/ [[0,1,2,3], [2,1,0,3], [0,3,2,1], [2,3,0,1]],
|
// in all combinations of rotations and flips.
|
||||||
/* 90*/ [[3,0,1,2], [1,0,3,2], [3,2,1,0], [1,2,3,0]],
|
// no flip flipH flipV flipV+H
|
||||||
/*180*/ [[2,3,0,1], [0,3,2,1], [2,1,0,3], [0,1,2,3]],
|
/* 0*/ [[0,1,2,3], [2,1,0,3], [0,3,2,1], [2,3,0,1]],
|
||||||
/*270*/ [[1,2,3,0], [3,2,1,0], [1,0,3,2], [3,0,1,2]]
|
/* 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]],
|
||||||
|
/*270*/ [[1,2,3,0], [3,2,1,0], [1,0,3,2], [3,0,1,2]]
|
||||||
|
];
|
||||||
let grayscaleMatrix = [];
|
let grayscaleMatrix = [];
|
||||||
let currentImage = new Image();
|
let currentImage = new Image();
|
||||||
let rotation = 0;
|
let rotation = 0;
|
||||||
let flipH = false;
|
let flipH = false;
|
||||||
let flipV = false;
|
let flipV = false;
|
||||||
let fileSuffix = "";
|
let fileSuffix = "";
|
||||||
let origDim = { width:0, height:0 };
|
let origDim = { width:0, height:0 };
|
||||||
let uncropDim = { width:0, height:0 };
|
let uncropDim = { width:0, height:0 };
|
||||||
let cropDim = { width:0, height:0 };
|
let cropDim = { width:0, height:0 };
|
||||||
|
|
||||||
|
// image processing functions
|
||||||
|
|
||||||
function getGrayscaleModel() {
|
function getGrayscaleModel() {
|
||||||
return document.querySelector('input[name="grayModel"]:checked').value;
|
return document.querySelector('input[name="grayModel"]:checked').value;
|
||||||
@@ -320,8 +329,8 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
originalSizeText.textContent = `Original size: ${origDim.width}×${origDim.height}`;
|
originalSizeText.textContent = `Original size: ${origDim.width}×${origDim.height}`;
|
||||||
|
|
||||||
// get output image dimensions
|
// get output image dimensions
|
||||||
uncropDim.width = origDim.width;
|
uncropDim.width = origDim.width;
|
||||||
uncropDim.height = origDim.height;
|
uncropDim.height = origDim.height;
|
||||||
const newWidth = parseInt(resizeWidthInput.value);
|
const newWidth = parseInt(resizeWidthInput.value);
|
||||||
if (!isNaN(newWidth) && newWidth > 0) {
|
if (!isNaN(newWidth) && newWidth > 0) {
|
||||||
uncropDim.width = newWidth;
|
uncropDim.width = newWidth;
|
||||||
@@ -358,25 +367,25 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
const blurRadius = parseInt(blurRadiusInput.value) || 0;
|
const blurRadius = parseInt(blurRadiusInput.value) || 0;
|
||||||
const blurredMatrix = applyGaussianBlur(brightnessMatrix, blurRadius);
|
const blurredMatrix = applyGaussianBlur(brightnessMatrix, blurRadius);
|
||||||
|
|
||||||
// crop the blurred matrix, gather min and max values in crop area
|
// crop the blurred matrix, gather min and max values in crop area
|
||||||
const cropMatrix = [];
|
const cropMatrix = [];
|
||||||
let cropx1 = parseInt(cropID[edgeID[2]].value) || 0;
|
let cropx1 = parseInt(cropID[edgeID[2]].value) || 0;
|
||||||
let cropx2 = parseInt(cropID[edgeID[0]].value) || 0;
|
let cropx2 = parseInt(cropID[edgeID[0]].value) || 0;
|
||||||
let cropy1 = parseInt(cropID[edgeID[1]].value) || 0;
|
let cropy1 = parseInt(cropID[edgeID[1]].value) || 0;
|
||||||
let cropy2 = parseInt(cropID[edgeID[3]].value) || 0;
|
let cropy2 = parseInt(cropID[edgeID[3]].value) || 0;
|
||||||
let min = 255;
|
let min = 255;
|
||||||
let max = 0;
|
let max = 0;
|
||||||
for (let y=cropy1; y<uncropDim.height-cropy2; y++) {
|
for (let y=cropy1; y<uncropDim.height-cropy2; y++) {
|
||||||
const row = [];
|
const row = [];
|
||||||
for(let x=cropx1; x<uncropDim.width-cropx2; x++) {
|
for(let x=cropx1; x<uncropDim.width-cropx2; x++) {
|
||||||
row.push(blurredMatrix[y][x]);
|
row.push(blurredMatrix[y][x]);
|
||||||
min = Math.min(min, blurredMatrix[y][x]);
|
min = Math.min(min, blurredMatrix[y][x]);
|
||||||
max = Math.max(max, blurredMatrix[y][x]);
|
max = Math.max(max, blurredMatrix[y][x]);
|
||||||
}
|
}
|
||||||
cropMatrix.push(row);
|
cropMatrix.push(row);
|
||||||
}
|
}
|
||||||
cropDim.width = uncropDim.width - cropx1 - cropx2;
|
cropDim.width = uncropDim.width - cropx1 - cropx2;
|
||||||
cropDim.height = uncropDim.height - cropy1 - cropy2;
|
cropDim.height = uncropDim.height - cropy1 - cropy2;
|
||||||
|
|
||||||
// normalize cropped image brightness to 0-255 range, invert brightness if checkbox is selected
|
// normalize cropped image brightness to 0-255 range, invert brightness if checkbox is selected
|
||||||
const range = max - min || 1;
|
const range = max - min || 1;
|
||||||
@@ -427,20 +436,28 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
fileSuffix = finalWidth.toString()+"x"+finalHeight.toString();
|
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 () {
|
imageInput.addEventListener('change', function () {
|
||||||
const file = this.files[0];
|
const file = this.files[0];
|
||||||
if (file && file.type.startsWith('image/')) {
|
if (file && file.type.startsWith('image/')) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
currentImage.onload = function () {
|
currentImage.onload = function () {
|
||||||
cropLeft.value="0";
|
resetInputs();
|
||||||
cropRight.value="0";
|
|
||||||
cropTop.value="0";
|
|
||||||
cropBottom.value="0";
|
|
||||||
rotate = 0;
|
|
||||||
flipV = flipH = false;
|
|
||||||
resizeWidthInput.value = "100";
|
|
||||||
blurRadiusInput.value = "0";
|
|
||||||
processImage();
|
processImage();
|
||||||
};
|
};
|
||||||
currentImage.src = e.target.result;
|
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) {
|
inputArea.addEventListener('paste', function (event) {
|
||||||
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
const items = (event.clipboardData || event.originalEvent.clipboardData).items;
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
@@ -457,6 +475,7 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = function (e) {
|
reader.onload = function (e) {
|
||||||
currentImage.onload = function () {
|
currentImage.onload = function () {
|
||||||
|
resetInputs();
|
||||||
processImage();
|
processImage();
|
||||||
};
|
};
|
||||||
currentImage.src = e.target.result;
|
currentImage.src = e.target.result;
|
||||||
@@ -466,85 +485,90 @@ Versions 1-5: 22 April 2025 - by Alex Matulich
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// set up events for all the input gadgets
|
||||||
|
|
||||||
[resizeWidthInput, invertBrightnessCheckbox, normalizeToUnitCheckbox, blurRadiusInput,
|
[resizeWidthInput, invertBrightnessCheckbox, normalizeToUnitCheckbox, blurRadiusInput,
|
||||||
...document.querySelectorAll('input[name="grayModel"]')].forEach(el => el.addEventListener('input', processImage));
|
...document.querySelectorAll('input[name="grayModel"]')].forEach(el => el.addEventListener('input', processImage));
|
||||||
|
|
||||||
cropLeft.addEventListener('input', () => {
|
cropLeft.addEventListener('input', () => {
|
||||||
if (!currentImage.src) { cropLeft.value="0"; return; }
|
if (!currentImage.src) { cropLeft.value="0"; return; }
|
||||||
const cl = parseInt(cropLeft.value) || 0;
|
const cl = parseInt(cropLeft.value) || 0;
|
||||||
const cr = parseInt(cropRight.value) || 0;
|
const cr = parseInt(cropRight.value) || 0;
|
||||||
if(uncropDim.width - cl - cr < 2) cropLeft.value = (uncropDim.width - cr - 2).toString();
|
if(uncropDim.width - cl - cr < 2) cropLeft.value = (uncropDim.width - cr - 2).toString();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
cropTop.addEventListener('input', () => {
|
cropTop.addEventListener('input', () => {
|
||||||
if (!currentImage.src) { cropTop.value="0"; return; }
|
if (!currentImage.src) { cropTop.value="0"; return; }
|
||||||
const ct = parseInt(cropTop.value) || 0;
|
const ct = parseInt(cropTop.value) || 0;
|
||||||
const cb = parseInt(cropBottom.value) || 0;
|
const cb = parseInt(cropBottom.value) || 0;
|
||||||
if(uncropDim.width - ct - cb < 2) cropTop.value = (uncropDim.height - cb - 2).toString();
|
if(uncropDim.width - ct - cb < 2) cropTop.value = (uncropDim.height - cb - 2).toString();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
cropRight.addEventListener('input', () => {
|
cropRight.addEventListener('input', () => {
|
||||||
if (!currentImage.src) { cropRight.value="0"; return; }
|
if (!currentImage.src) { cropRight.value="0"; return; }
|
||||||
const cl = parseInt(cropLeft.value) || 0;
|
const cl = parseInt(cropLeft.value) || 0;
|
||||||
const cr = parseInt(cropRight.value) || 0;
|
const cr = parseInt(cropRight.value) || 0;
|
||||||
if(uncropDim.width - cl - cr < 2) cropRight.value = (uncropDim.width - cl - 2).toString();
|
if(uncropDim.width - cl - cr < 2) cropRight.value = (uncropDim.width - cl - 2).toString();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
cropBottom.addEventListener('input', () => {
|
cropBottom.addEventListener('input', () => {
|
||||||
if (!currentImage.src) { cropBottom.value="0"; return; }
|
if (!currentImage.src) { cropBottom.value="0"; return; }
|
||||||
const ct = parseInt(cropTop.value) || 0;
|
const ct = parseInt(cropTop.value) || 0;
|
||||||
const cb = parseInt(cropBottom.value) || 0;
|
const cb = parseInt(cropBottom.value) || 0;
|
||||||
if(uncropDim.width - ct - cb < 2) cropBottom.value = (uncropDim.height - ct - 2).toString();
|
if(uncropDim.width - ct - cb < 2) cropBottom.value = (uncropDim.height - ct - 2).toString();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateEdgeID(out="") {
|
function updateEdgeID(out="") {
|
||||||
const fi = (flipH ? 1 : 0) + (flipV ? 2 : 0);
|
const fi = (flipH ? 1 : 0) + (flipV ? 2 : 0);
|
||||||
const ri = Math.round(rotation/90);
|
const ri = Math.round(rotation/90);
|
||||||
edgeID = edgeconfig[ri][fi];
|
edgeID = edgeconfig[ri][fi];
|
||||||
if (out.length>0) console.log(out, rotation, flipH, flipV, edgeID);
|
if (out.length>0) console.log(out, rotation, flipH, flipV, edgeID);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotateLeftBtn.addEventListener('click', () => {
|
rotateLeftBtn.addEventListener('click', () => {
|
||||||
if (!currentImage.src) return;
|
if (!currentImage.src) return;
|
||||||
rotation = (rotation - 90 + 360) % 360;
|
rotation = (rotation - 90 + 360) % 360;
|
||||||
const tmp = cropTop.value;
|
const tmp = cropTop.value;
|
||||||
cropTop.value = cropRight.value;
|
cropTop.value = cropRight.value;
|
||||||
cropRight.value = cropBottom.value;
|
cropRight.value = cropBottom.value;
|
||||||
cropBottom.value = cropLeft.value;
|
cropBottom.value = cropLeft.value;
|
||||||
cropLeft.value = tmp;
|
cropLeft.value = tmp;
|
||||||
updateEdgeID();
|
updateEdgeID();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
rotateRightBtn.addEventListener('click', () => {
|
rotateRightBtn.addEventListener('click', () => {
|
||||||
if (!currentImage.src) return;
|
if (!currentImage.src) return;
|
||||||
rotation = (rotation + 90) % 360;
|
rotation = (rotation + 90) % 360;
|
||||||
const tmp = cropTop.value;
|
const tmp = cropTop.value;
|
||||||
cropTop.value = cropLeft.value;
|
cropTop.value = cropLeft.value;
|
||||||
cropLeft.value = cropBottom.value;
|
cropLeft.value = cropBottom.value;
|
||||||
cropBottom.value = cropRight.value;
|
cropBottom.value = cropRight.value;
|
||||||
cropRight.value = tmp;
|
cropRight.value = tmp;
|
||||||
updateEdgeID();
|
updateEdgeID();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
flipHorizontalBtn.addEventListener('click', () => {
|
flipHorizontalBtn.addEventListener('click', () => {
|
||||||
if (!currentImage.src) return;
|
if (!currentImage.src) return;
|
||||||
flipH = !flipH;
|
flipH = !flipH;
|
||||||
let tmp = cropRight.value;
|
let tmp = cropRight.value;
|
||||||
cropRight.value = cropLeft.value;
|
cropRight.value = cropLeft.value;
|
||||||
cropLeft.value = tmp;
|
cropLeft.value = tmp;
|
||||||
updateEdgeID();
|
updateEdgeID();
|
||||||
processImage();
|
processImage();
|
||||||
});
|
});
|
||||||
flipVerticalBtn.addEventListener('click', () => {
|
flipVerticalBtn.addEventListener('click', () => {
|
||||||
if (!currentImage.src) return;
|
if (!currentImage.src) return;
|
||||||
flipV = !flipV;
|
flipV = !flipV;
|
||||||
let tmp = cropTop.value;
|
let tmp = cropTop.value;
|
||||||
cropTop.value = cropBottom.value;
|
cropTop.value = cropBottom.value;
|
||||||
cropBottom.value = tmp;
|
cropBottom.value = tmp;
|
||||||
updateEdgeID();
|
updateEdgeID();
|
||||||
processImage();
|
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', () => {
|
downloadButton.addEventListener('click', () => {
|
||||||
if (grayscaleMatrix.length === 0) return alert("No data to save.");
|
if (grayscaleMatrix.length === 0) return alert("No data to save.");
|
||||||
|
Reference in New Issue
Block a user