Version 1.0.1

Scaling enabled
Hybrid mode in addressing
UI overhaul
Clean up
This commit is contained in:
Henrik
2023-01-15 21:07:08 +01:00
committed by GitHub
parent b470749935
commit 5e55782909
8 changed files with 1579 additions and 307 deletions
+24 -33
View File
@@ -60,41 +60,32 @@ function drawBoxes(inputPixelArray, widthPixels, heightPixels) {
}
function drawBackground() {
const grid = document.createElement("div");
grid.id = "grid";
grid.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: grid;
grid-template-columns: repeat(auto-fill, 20px);
grid-template-rows: repeat(auto-fill, 20px);
grid-gap: 0px;
`;
const boxSize = 20;
const boxCount = Math.ceil(window.innerWidth / boxSize) * Math.ceil(window.innerHeight / boxSize);;
const grid = document.createElement("div");
grid.id = "grid";
grid.classList.add("grid-class");
grid.style.cssText = "";
for (let i = 0; i < boxCount; i++) {
const box = document.createElement("div");
box.classList.add("box");
box.style.backgroundColor = getRandomColor();
grid.appendChild(box);
}
grid.style.zIndex = -1;
document.body.appendChild(grid);
const boxSize = 20;
const boxCount = Math.ceil(window.innerWidth / boxSize) * Math.ceil(window.innerHeight / boxSize);;
for (let i = 0; i < boxCount; i++) {
const box = document.createElement("div");
box.classList.add("box");
box.style.backgroundColor = getRandomColor();
grid.appendChild(box);
}
function getRandomColor() {
const letters = "0123456789ABCDEF";
let color = "rgba(";
for (let i = 0; i < 3; i++) {
color += Math.floor(Math.random() * 256) + ",";
}
color += "0.05)";
return color;
grid.style.zIndex = -1;
document.body.appendChild(grid);
}
function getRandomColor() {
const letters = "0123456789ABCDEF";
let color = "rgba(";
for (let i = 0; i < 3; i++) {
color += Math.floor(Math.random() * 256) + ",";
}
color += "0.05)";
return color;
}
window.drawBackground = drawBackground;
+61 -17
View File
@@ -3,7 +3,7 @@ function getPixelRGBValues(base64Image) {
const copyJSONledbutton = document.getElementById('copyJSONledbutton');
const JSONled = document.getElementById('JSONled');
const maxNoOfColorsInCommandSting = document.getElementById('colorLimitNumber').value;
let hybridAddressing = false;
let selectedIndex = -1;
let selector = document.getElementById("formatSelector");
@@ -23,9 +23,11 @@ function getPixelRGBValues(base64Image) {
selector = document.getElementById("addressingSelector");
selectedIndex = selector.selectedIndex;
let segmentValueCheck = true;
let segmentValueCheck = true; //If Range or Hybrid
if (selector.options[selectedIndex].value == 'single'){
segmentValueCheck = false
} else if (selector.options[selectedIndex].value == 'hybrid'){
hybridAddressing = true;
}
let segmentString = ''
@@ -54,16 +56,29 @@ function getPixelRGBValues(base64Image) {
// Wait for the image to load before drawing it onto the canvas
image.onload = function() {
// Set the canvas size to the same as the image
canvas.width = image.width;
canvas.height = image.height;
imageInfo = '<p>Width: ' + image.width + ', Height: ' + image.height + ' (make sure this matches your led matrix setup)</p>'
let scalePath = document.getElementById("scalePath");
let color = scalePath.getAttribute("fill");
let sizeX = document.getElementById("sizeX").value;
let sizeY = document.getElementById("sizeY").value;
if (color != accentColor || sizeX < 1 || sizeY < 1){
//image will not be rezised Set desitred size to original size
sizeX = image.width;
sizeY = image.height;
}
// Set the canvas size to the same as the desired image size
canvas.width = sizeX;
canvas.height = sizeY;
imageInfo = '<p>Width: ' + sizeX + ', Height: ' + image.height + ' (make sure this matches your led matrix setup)</p>'
// Draw the image onto the canvas
context.drawImage(image, 0, 0);
context.drawImage(image, 0, 0, sizeX, sizeY);
// Get the pixel data from the canvas
var pixelData = context.getImageData(0, 0, image.width, image.height).data;
var pixelData = context.getImageData(0, 0, sizeX, sizeY).data;
// Create an array to hold the RGB values of each pixel
var pixelRGBValues = [];
@@ -83,7 +98,7 @@ function getPixelRGBValues(base64Image) {
var a = pixelData[i + 3];
let pixel = i/4
let row = Math.floor(pixel/image.width);
let row = Math.floor(pixel/sizeX);
let led = pixel;
if (ledSetupSelection == 'matrix'){
//Do nothing, the matrix is set upp like the index in the image
@@ -99,10 +114,10 @@ function getPixelRGBValues(base64Image) {
//Setup is traditional zigzag
//Row is right to left
//Invert index of row for led
let indexOnRow = led - (row * image.width);
let maxIndexOnRow = image.width - 1;
let indexOnRow = led - (row * sizeX);
let maxIndexOnRow = sizeX - 1;
let reversedIndexOnRow = maxIndexOnRow - indexOnRow;
led = (row * image.width) + reversedIndexOnRow;
led = (row * sizeX) + reversedIndexOnRow;
}
// Add the RGB values to the pixel RGB values array
@@ -152,19 +167,45 @@ function getPixelRGBValues(base64Image) {
//Next pixel has new color
//The current segment ends with this pixel
segmentEnd = i + 1 //WLED wants the NEXT LED as the stop led...
segmentString = segmentStart + ',' + segmentEnd + ',';
if (segmentStart == i && hybridAddressing){
//If only one led/pixel, no segment info needed
if (JSONledString == ''){
//If addressing is single, we need to start every command with a starting possition
segmentString = '' + i + ',';
//Fixed to b2
} else{
segmentString = ''
}
}
else {
segmentString = segmentStart + ',' + segmentEnd + ',';
}
}
} else {
//This is the last pixel, so the segment must end
segmentEnd = i + 1;
segmentString = segmentStart + ',' + segmentEnd + ',';
if (segmentStart + 1 == segmentEnd && hybridAddressing){
//If only one led/pixel, no segment info needed
if (JSONledString == ''){
//If addressing is single, we need to start every command with a starting possition
segmentString = '' + i + ',';
//Fixed to b2
} else{
segmentString = ''
}
}
else {
segmentString = segmentStart + ',' + segmentEnd + ',';
}
}
} else{
//Write every pixel
if (JSONledString == ''){
//If addressing is single, we ned to start every command with a starting possition
JSONledString = i + ', \'dummy\',';
//If addressing is single, we need to start every command with a starting possition
JSONledString = i
//Fixed to b2
}
segmentStart = i
@@ -188,6 +229,8 @@ function getPixelRGBValues(base64Image) {
//do nothing, allready set
}
// Check if start and end is the same, in which case remove
JSONledString = JSONledString + segmentString + colorSeparatorStart + colorValueString + colorSeparatorEnd;
curentColorIndex = curentColorIndex + 1; // We've just added a new color to the string so up the count with one
@@ -247,7 +290,8 @@ function getPixelRGBValues(base64Image) {
infoDiv.innerHTML = imageInfo;
canvasDiv.style.display = "block"
//Drawing the image
drawBoxes(pixelRGBValues, image.width, image.width);
drawBoxes(pixelRGBValues, sizeX, sizeY);
}
}
+55 -73
View File
@@ -1,7 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
@@ -13,7 +12,9 @@
<body>
<div class = top-part>
<h1>Led Matrix Pixel Art Converter</h1>
<h1><svg style="width:32px;height:32px" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 16C13.1 16 14 16.9 14 18S13.1 20 12 20 10 19.1 10 18 10.9 16 12 16M12 10C13.1 10 14 10.9 14 12S13.1 14 12 14 10 13.1 10 12 10.9 10 12 10M12 4C13.1 4 14 4.9 14 6S13.1 8 12 8 10 7.1 10 6 10.9 4 12 4M6 16C7.1 16 8 16.9 8 18S7.1 20 6 20 4 19.1 4 18 4.9 16 6 16M6 10C7.1 10 8 10.9 8 12S7.1 14 6 14 4 13.1 4 12 4.9 10 6 10M6 4C7.1 4 8 4.9 8 6S7.1 8 6 8 4 7.1 4 6 4.9 4 6 4M18 16C19.1 16 20 16.9 20 18S19.1 20 18 20 16 19.1 16 18 16.9 16 18 16M18 10C19.1 10 20 10.9 20 12S19.1 14 18 14 16 13.1 16 12 16.9 10 18 10M18 4C19.1 4 20 4.9 20 6S19.1 8 18 8 16 7.1 16 6 16.9 4 18 4Z" />
</svg>Led Matrix Pixel Art Converter</h1>
<h2>Convert image to WLED JSON (pixel art on WLED matrix)</h2>
<p>
<table id="fieldTable" style="width: 100%; table-layout: fixed; align-content: center;">
@@ -60,6 +61,7 @@
<select id="addressingSelector">
<option value="range" selected>Range (10, 17, #f4f4f4)</option>
<option value="single">Single (#f4f4f4)</option>
<option value="hybrid">Hybrid (#f0f0f0,10, 17, #f4f4f4)</option>
</select>
</td>
</tr>
@@ -68,8 +70,8 @@
<label for="brightnessNumber">Brightness:</label>
</td>
<td style="vertical-align: middle; display: flex; align-items: center;">
<input type="range" id="brightnessNumber" min="1" max="255" value="127">
<span id="brightnessValue">100</span>
<input type="range" id="brightnessNumber" min="1" max="255" value="255">
<span id="brightnessValue">255</span>
</td>
</tr>
<tr>
@@ -94,7 +96,7 @@
<label for="haUID">HA Device Unique ID:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="haUID" value="pixel_art_controller_001a">
<input class="fullTextField" type="text" id="haUID" value="pixel_art_controller_001a">
</td>
</tr>
<tr class="ha-hide">
@@ -102,7 +104,7 @@
<label for="haName">HA Device Name:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="haName" value="Pixel Art Kitchen">
<input class="fullTextField" type="text" id="haName" value="Pixel Art Kitchen">
</td>
</tr>
<tr>
@@ -110,61 +112,35 @@
<label for="curlUrl">Device IP/host name:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="curlUrl" value="">
<input class="fullTextField" type="text" id="curlUrl" value="">
</td>
</tr>
</table>
<table class= "scaleTableClass" id="scaleTable" style="width: 100%; table-layout: fixed; align-content: center;">
<tr>
<td style="vertical-align: middle;">
<label for="renderCheckbox">Show pixel rendering</label>
<div id="scaleDiv">
<svg id="scaleToggle" style="width:36px;height:36px" viewBox="0 0 24 24" onclick="switchScale()">
<path id="scaleTogglePath" fill="currentColor" d="M17,7H7A5,5 0 0,0 2,12A5,5 0 0,0 7,17H17A5,5 0 0,0 22,12A5,5 0 0,0 17,7M7,15A3,3 0 0,1 4,12A3,3 0 0,1 7,9A3,3 0 0,1 10,12A3,3 0 0,1 7,15Z" />
</svg>
&nbsp;&nbsp;&nbsp;
<svg id="scaleSvg" style="width:36px;height:36px" viewBox="0 0 24 24" onclick="switchScale()">
<path id="scalePath" fill="currentColor" d="M21,15H23V17H21V15M21,11H23V13H21V11M23,19H21V21C22,21 23,20 23,19M13,3H15V5H13V3M21,7H23V9H21V7M21,3V5H23C23,4 22,3 21,3M1,7H3V9H1V7M17,3H19V5H17V3M17,19H19V21H17V19M3,3C2,3 1,4 1,5H3V3M9,3H11V5H9V3M5,3H7V5H5V3M1,11V19A2,2 0 0,0 3,21H15V11H1M3,19L5.5,15.79L7.29,17.94L9.79,14.72L13,19H3Z" />
</svg>
</div>
</td>
<td style="vertical-align: middle;">
<input type="checkbox" id="renderCheckbox" checked>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="helpCheckbox">Show help/about</label>
</td>
<td style="vertical-align: middle;">
<input type="checkbox" id="helpCheckbox">
<div id="sizeDiv" style="display: none;">
<label for="sizeX">X : </label> &nbsp;<input class="sizeInputFields" type="number" id="sizeX" value="0">
&nbsp;&nbsp;&nbsp;
<label for="sizeY">Y : </label> &nbsp;<input class="sizeInputFields" type="number" id="sizeY" value="0">
</div>
</td>
</tr>
</table>
</p>
</p>
<div id="help-container" style="display: none">>
<p>
1a. Create a GIF or PNG ( <a href="https://www.pixilart.com/" target="_blank">www.pixilart.com</a> ) <br>
1b. Download some image that fits your led matrix ( <a href="https://www.spriters-resource.com/" target="_blank">www.spriters-resource.com</a>) <br>
2. Upload the image using the file picker or drag-and-drop it <br>
3. Select the led setup that matches your WLED. ( <a href="https://kno.wled.ge/advanced/mapping/" target="_blank">https://kno.wled.ge</a>) <br>
4. Select Otput format. <br>
- WLED is pure JSON in the way the documentation descripbes it.<br>&nbsp;&nbsp;&nbsp;Use in WLED (presets?)<br>
- CURL is formated as a curl command you can past into a command <br>&nbsp;&nbsp;&nbsp;window and test yor led matrix<br>
- Home Assistant is teh full YAML you can past into your <br>&nbsp;&nbsp;&nbsp;configuration.yaml in Home Assistant<br>
5. Select Color format. Select HEX if possible (more efficiant)<br>
6. Select Addressing scheme. Send all pixels individually or try to <br>&nbsp;&nbsp;&nbsp;send ranges of the same color.<br>
7. Select brighness value<br>
8. According to docs WLED can handle max 256 colors/command. So the JSON is <br>&nbsp;&nbsp;&nbsp;split if you have larger images. <br>&nbsp;&nbsp;&nbsp;Lower this value if you have issues.<br>
9. Set Home Assistant Device values if you are going to use HA<br>
10. Set the device IP/host for HA and CURL to work<br>
11. Press the convert button <br>
12. Copy the generated JSON and put it somewhere in WLED<br>&nbsp;&nbsp;&nbsp;, Run CURL or paste to Home Assistant
</p>
<p>
This tool is a proof of concept and work in progress. As you might expect, there is absolutely no warranty, what so ever.
</p>
<p>
The Arcade font is copyright (c) Jakob Fischer at www.pizzadude.dk, all rights reserved. Do not distribute without the author's permission.
</p>
<p>
It should be said that I don&lsquo;t acctually own a setup for this. I basically did it to play around over new years. But I have ordered a matrix now, and a small controller chip... let's see how that works out.
</p>
</div>
</p>
<p>
<label for="file-picker">
<div id="drop-zone">
@@ -177,32 +153,31 @@
<p>
<input type="file" id="file-picker" style="display: none;">
<div style="width: 100%; text-align: center;" >
<img id="preview" style="display: block; margin: 0 auto;"><br>
<img id="preview" style="display: block; margin: 0 auto;">
<img id="newimage" style="display: block; margin: 0 auto;"><br>
</div>
<form id="form">
<input id="submitConvert" type="submit" value="Convert to JSON for WLED" style="display: none">
</form>
<div id="submitConvertDiv" style="display: none;">
<button id="convertbutton" class="buttonclass"></button>
</div>
<div id="raw-image-container" style="display: none">
<img id="image" src="" alt="RawImage image">
</div>
</p>
<div id="image-container" style="display: none">
<div id="image-container" style="display: none;">
<div id="image-info" style="display: none"></div>
<p>
<div>
<textarea id="JSONled"></textarea>
<br>
<button id="copyJSONledbutton" >Copy led-JSON to Clipboard</button>
<br>
<button id="sendJSONledbutton" >Send to Device</button>
<br>
</div>
</p>
</div>
<textarea id="JSONled"></textarea>
</div>
<div id="button-container" style="display: none;">
<button id="copyJSONledbutton" class="buttonclass"></button>
<div class="gap"></div>
<button id="sendJSONledbutton" class="buttonclass"></button>
</div>
</div>
<div id=bottom-part style="display: none" class=bottom-part></div>
<canvas id="pixelCanvas"></canvas>
@@ -210,9 +185,12 @@
<script>
//Code for dynamically loading the scripts as to prevent caching. Remove in production.
function loadFiles(fileNames) {
fileNames.forEach(function(fileName) {
function loadFiles(fileNames, index) {
if (index === fileNames.length) {
return;
}
var fileName = fileNames[index];
var fileExt = fileName.split('.').pop();
var element;
if (fileExt === 'js') {
@@ -224,12 +202,16 @@
element.rel = 'stylesheet';
element.href = fileName + '?time=' + new Date().getTime();
}
element.onload = function() {
loadFiles(fileNames, index + 1);
}
document.getElementsByTagName('head')[0].appendChild(element);
});
}
var files = ["statics.js", "getPixelValues.js", "boxdraw.js", "index.js", "styles.css"];
loadFiles(files);
var files = ["statics.js", "getPixelValues.js", "boxdraw.js", "index.js", "styles.css"];
loadFiles(files, 0);
</script>
</body>
+44 -24
View File
@@ -1,28 +1,29 @@
//Start up code
console.log(location.host);
document.getElementById('curlUrl').value = location.host;
let httpArray = [];
//On submit button pressed =======================
document.getElementById('form').addEventListener('submit', function(event) {
event.preventDefault();
document.getElementById("convertbutton").addEventListener("click", function() {
let base64Image = document.getElementById('preview').src;
if (isValidBase64Gif(base64Image)) {
document.getElementById('image').src = base64Image;
getPixelRGBValues(base64Image);
document.getElementById('image-container').style.display = "block"
document.getElementById("button-container").style.display = "";
}
else {
let infoDiv = document.getElementById('image-info');
let imageInfo = '<p><b>WARNING!</b> File does not appear to be a valid GIF image</p>'
let imageInfo = '<p><b>WARNING!</b> File does not appear to be a valid image</p>'
infoDiv.innerHTML = imageInfo;
infoDiv.style.display = "block"
document.getElementById('image-container').style.display = "none";
document.getElementById('JSONled').value = '';
console.log("The string '" + base64Image + "' is not a valid base64 GIF image.");
console.log("The string '" + base64Image + "' is not a valid base64 image.");
}
});
@@ -30,12 +31,16 @@ document.getElementById('form').addEventListener('submit', function(event) {
// Code for copying the generated string to clipboard
copyJSONledbutton.addEventListener('click', async () => {
let JSONled = document.getElementById('JSONled');
JSONled.select();
try {
await navigator.clipboard.writeText('test text');
console.log('Text copied to clipboard');
await navigator.clipboard.writeText(JSONled.value);
} catch (err) {
console.error('Failed to copy text: ', err);
try {
await document.execCommand("copy");
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
});
@@ -67,18 +72,6 @@ async function postPixels() {
}
}
}
let helpCheckbox = document.getElementById("helpCheckbox");
let helpDiv = document.getElementById("help-container");
helpCheckbox.addEventListener("change", function() {
if (helpCheckbox.checked) {
helpDiv.style.display = "block";
} else {
helpDiv.style.display = "none";
}
});
//File uploader code
const dropZone = document.getElementById('drop-zone');
const filePicker = document.getElementById('file-picker');
@@ -136,7 +129,7 @@ function updatePreview(file) {
reader.onload = function() {
// Update the preview image
preview.src = reader.result;
document.getElementById("submitConvert").style.display = "block";
document.getElementById("submitConvertDiv").style.display = "";
};
reader.readAsDataURL(file);
}
@@ -149,7 +142,7 @@ function isValidBase64Gif(string) {
const base64jpgPattern = /^data:image\/jpg;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
const base64webpPattern = /^data:image\/webp;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
*/
//REMOVED, Any image appear to work as long as it can be drawn to the canvas. Leavingg code in for future use, possibly
//REMOVED, Any image appear to work as long as it can be drawn to the canvas. Leaving code in for future use, possibly
if (1==1 || base64gifPattern.test(string) || base64pngPattern.test(string) || base64jpgPattern.test(string) || base64webpPattern.test(string)) {
return true;
}
@@ -176,4 +169,31 @@ formatSelector.addEventListener("change", function() {
for (var i = 0; i < hideableRows.length; i++) {
hideableRows[i].classList.toggle("hide", this.value !== "ha");
}
});
});
function switchScale() {
let scalePath = document.getElementById("scalePath");
let scaleTogglePath = document.getElementById("scaleTogglePath");
let color = scalePath.getAttribute("fill");
let d = ''
if (color === accentColor) {
color = accentTextColor;
d = scaleToggleOffd
document.getElementById("sizeDiv").style.display = "none";
// Set values to actual XY of image, if possible
} else {
color = accentColor;
d = scaleToggleOnd
document.getElementById("sizeDiv").style.display = "";
}
scalePath.setAttribute("fill", color);
scaleTogglePath.setAttribute("fill", color);
scaleTogglePath.setAttribute("d", d);
}
document.getElementById("convertbutton").innerHTML =
'<svg style="width:36px;height:36px" viewBox="0 0 24 24"><path fill="currentColor" d="M12,6V9L16,5L12,1V4A8,8 0 0,0 4,12C4,13.57 4.46,15.03 5.24,16.26L6.7,14.8C6.25,13.97 6,13 6,12A6,6 0 0,1 12,6M18.76,7.74L17.3,9.2C17.74,10.04 18,11 18,12A6,6 0 0,1 12,18V15L8,19L12,23V20A8,8 0 0,0 20,12C20,10.43 19.54,8.97 18.76,7.74Z" /> </svg>&nbsp; Convert to WLED JSON ';
document.getElementById("copyJSONledbutton").innerHTML =
'<svg class="svg-icon" style="width:36px;height:36px" viewBox="0 0 24 24"> <path fill="currentColor" d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z" /> </svg>&nbsp; Copy to clipboard';
document.getElementById("sendJSONledbutton").innerHTML =
'<svg class="svg-icon" style="width:36px;height:36px" viewBox="0 0 24 24"> <path fill="currentColor" d="M6.5 20Q4.22 20 2.61 18.43 1 16.85 1 14.58 1 12.63 2.17 11.1 3.35 9.57 5.25 9.15 5.88 6.85 7.75 5.43 9.63 4 12 4 14.93 4 16.96 6.04 19 8.07 19 11 20.73 11.2 21.86 12.5 23 13.78 23 15.5 23 17.38 21.69 18.69 20.38 20 18.5 20H13Q12.18 20 11.59 19.41 11 18.83 11 18V12.85L9.4 14.4L8 13L12 9L16 13L14.6 14.4L13 12.85V18H18.5Q19.55 18 20.27 17.27 21 16.55 21 15.5 21 14.45 20.27 13.73 19.55 13 18.5 13H17V11Q17 8.93 15.54 7.46 14.08 6 12 6 9.93 6 8.46 7.46 7 8.93 7 11H6.5Q5.05 11 4.03 12.03 3 13.05 3 14.5 3 15.95 4.03 17 5.05 18 6.5 18H9V20M12 13Z" /> </svg>&nbsp; Send to device';
+949
View File
@@ -0,0 +1,949 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Led Matrix Pixel Art Convertor</title>
<!-- not in use in minified version
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
<link rel="manifest" href="/site.webmanifest">
-->
<!--For minifyed-->
<style>
.box {
border: 2px solid white;
}
body {
font-family: 'Arcade', Arial, sans-serif;
background-color: #151515;
}
.top-part {
width: 600px;
margin: 0 auto;
}
.container {
max-width: 100% -40px;
border-radius: 0px;
padding: 20px;
text-align: center;
}
h1 {
font-size: 2.3em;
color: rgb(126, 76, 128);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
h2 {
font-size: 1.3em;
color: rgba(126, 76, 128, 0.61);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
p {
font-size: 1.2em;
color: rgb(119, 119, 119);
line-height: 1.5;
font-family: 'Arcade', Arial, sans-serif;
}
#fieldTable {
font-size: 1 em;
color: #777;
line-height: 1;
font-family: 'Arcade', Arial, sans-serif;
}
#drop-zone {
display: block;
width: 100%-40px;
border: 3px dashed #7E4C80;
border-radius: 0px;
text-align: center;
padding: 20px;
margin: 0px;
cursor: pointer;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: #777;
}
*.button {
display: block;
width: 100% - 40px;
border: 2px dashed #ccc;
border-radius: 20px;
text-align: center;
padding: 20px;
margin: 0px;
cursor: pointer;
}
#file-picker {
display: none;
}
* select {
background-color: #333333;
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-bottom: 0.5em;
padding: 0em;
width: 100%;
height: 27px;
font-size: 15px;
color: rgb(119, 119, 119);
border-radius: 0;
}
* input[type=range] {
-webkit-appearance:none;
flex-grow: 1;
border-radius: 0px;
background: linear-gradient(to right, #333333 0%, #333333 100%);
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-left: 0em;
}
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance:none;
width: 25px;
height:25px;
background:#7E4C80;
position:relative;
z-index:3;
}
.rangeNumber{
width: 20px;
vertical-align: middle;
}
* input[type=text] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding-inline-start: 5px;
margin-top: 10px;
width: 100%;
height: 27px;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: rgb(119, 119, 119);
}
* input[type="checkbox"] {
}
* input[type=submit] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0.5em;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
}
* button {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0.5em;
margin-bottom: 15px;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
}
* textarea {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0em;
margin-bottom: 10px;
width: 100%;
height: 200px;
border-radius: 0px;
font-family: 'Courier', Arial, sans-serif;
font-size: 1em;
color: rgb(119, 119, 119);
}
.hide {
display: none;
}
.gridstyle{
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: grid;
grid-template-columns: repeat(auto-fill, 20px);
grid-template-rows: repeat(auto-fill, 20px);
grid-gap: 0px;
}
</style>
</head>
<body>
<div class = top-part>
<h1>Led Matrix Pixel Art Converter</h1>
<h2>Convert image to WLED JSON (pixel art on WLED matrix)</h2>
<p>
<table id="fieldTable" style="width: 100%; table-layout: fixed; align-content: center;">
<tr>
<td style="vertical-align: middle;">
<label for="ledSetupSelector">Led setup:</label>
</td>
<td style="vertical-align: middle;">
<select id="ledSetupSelector">
<option value="matrix" selected>2D Matrix</option>
<option value="r2l">Serpentine, first row right to left <-</option>
<option value="l2r">Serpentine, first row left to right -></option>
</select>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="formatSelector">Output format:</label>
</td>
<td style="vertical-align: middle;">
<select id="formatSelector">
<option value="wled" selected>WLED JSON</option>
<option value="curl">CURL</option>
<option value="ha">Home Assistant YAML</option>
</select>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="colorFormatSelector">Color code format:</label>
</td>
<td style="vertical-align: middle;">
<select id="colorFormatSelector">
<option value="hex" selected>HEX (#f4f4f4)</option>
<option value="dec">DEC (244,244,244)</option>
</select>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="addressingSelector">Addressing:</label>
</td>
<td style="vertical-align: middle;">
<select id="addressingSelector">
<option value="range" selected>Range (10, 17, #f4f4f4)</option>
<option value="single">Single (#f4f4f4)</option>
</select>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="brightnessNumber">Brightness:</label>
</td>
<td style="vertical-align: middle; display: flex; align-items: center;">
<input type="range" id="brightnessNumber" min="1" max="255" value="127">
<span id="brightnessValue">100</span>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="colorLimitNumber">Max no of colors/JSON:</label>
</td>
<td style="vertical-align: middle; display: flex; align-items: center;">
<input type="range" id="colorLimitNumber" min="1" max="512" value="256">
<span id="colorLimitValue" >256</span>
</td>
</tr>
<tr class="ha-hide">
<td style="vertical-align: middle;">
<label for="haID">HA Device ID:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="haID" value="pixel_art_controller_001">
</td>
</tr>
<tr class="ha-hide">
<td style="vertical-align: middle;">
<label for="haUID">HA Device Unique ID:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="haUID" value="pixel_art_controller_001a">
</td>
</tr>
<tr class="ha-hide">
<td style="vertical-align: middle;">
<label for="haName">HA Device Name:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="haName" value="Pixel Art Kitchen">
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="curlUrl">Device IP/host name:</label>
</td>
<td style="vertical-align: middle;">
<input type="text" id="curlUrl" value="">
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="renderCheckbox">Show pixel rendering</label>
</td>
<td style="vertical-align: middle;">
<input type="checkbox" id="renderCheckbox" checked>
</td>
</tr>
<tr>
<td style="vertical-align: middle;">
<label for="helpCheckbox">Show help/about</label>
</td>
<td style="vertical-align: middle;">
<input type="checkbox" id="helpCheckbox">
</td>
</tr>
</table>
</p>
</p>
<div id="help-container" style="display: none">>
<p>
1a. Create a GIF or PNG ( <a href="https://www.pixilart.com/" target="_blank">www.pixilart.com</a> ) <br>
1b. Download some image that fits your led matrix ( <a href="https://www.spriters-resource.com/" target="_blank">www.spriters-resource.com</a>) <br>
2. Upload the image using the file picker or drag-and-drop it <br>
3. Select the led setup that matches your WLED. ( <a href="https://kno.wled.ge/advanced/mapping/" target="_blank">https://kno.wled.ge</a>) <br>
4. Select Otput format. <br>
- WLED is pure JSON in the way the documentation descripbes it.<br>&nbsp;&nbsp;&nbsp;Use in WLED (presets?)<br>
- CURL is formated as a curl command you can past into a command <br>&nbsp;&nbsp;&nbsp;window and test yor led matrix<br>
- Home Assistant is teh full YAML you can past into your <br>&nbsp;&nbsp;&nbsp;configuration.yaml in Home Assistant<br>
5. Select Color format. Select HEX if possible (more efficiant)<br>
6. Select Addressing scheme. Send all pixels individually or try to <br>&nbsp;&nbsp;&nbsp;send ranges of the same color.<br>
7. Select brighness value<br>
8. According to docs WLED can handle max 256 colors/command. So the JSON is <br>&nbsp;&nbsp;&nbsp;split if you have larger images. <br>&nbsp;&nbsp;&nbsp;Lower this value if you have issues.<br>
9. Set Home Assistant Device values if you are going to use HA<br>
10. Set the device IP/host for HA and CURL to work<br>
11. Press the convert button <br>
12. Copy the generated JSON and put it somewhere in WLED<br>&nbsp;&nbsp;&nbsp;, Run CURL or paste to Home Assistant
</p>
<p>
This tool is a proof of concept and work in progress. As you might expect, there is absolutely no warranty, what so ever.
</p>
<p>
The Arcade font is copyright (c) Jakob Fischer at www.pizzadude.dk, all rights reserved. Do not distribute without the author's permission.
</p>
<p>
It should be said that I don&lsquo;t acctually own a setup for this. I basically did it to play around over new years. But I have ordered a matrix now, and a small controller chip... let's see how that works out.
</p>
</div>
<p>
<label for="file-picker">
<div id="drop-zone">
Drop image here <br>or <br>
Click to select a file
</div>
</label>
</p>
<p>
<input type="file" id="file-picker" style="display: none;">
<div style="width: 100%; text-align: center;" >
<img id="preview" style="display: block; margin: 0 auto;"><br>
</div>
<form id="form">
<input id="submitConvert" type="submit" value="Convert to JSON for WLED" style="display: none">
</form>
<div id="raw-image-container" style="display: none">
<img id="image" src="" alt="RawImage image">
</div>
</p>
<div id="image-container" style="display: none">
<div id="image-info" style="display: none"></div>
<p>
<div>
<textarea id="JSONled"></textarea>
<br>
<button id="copyJSONledbutton" >Copy led-JSON to Clipboard</button>
<br>
<button id="sendJSONledbutton" >Send to Device</button>
<br>
</div>
</p>
</div>
</div>
</div>
<div id=bottom-part style="display: none" class=bottom-part></div>
<canvas id="pixelCanvas"></canvas>
</div>
<script>
// STATICS.JS
var curlStart = 'curl -X POST "http://';
var curlMid1 = '/json/state" -d \'';
var curlEnd = '\' -H "Content-Type: application/json"';
const haStart = '#Uncomment if you don\'t allready have these defined in your switch section of your configuration.yaml\n#- platform: command_line\n #switches:\n ';
const haMid1 = '\n friendly_name: ';
const haMid2 = '\n unique_id: ';
const haMid3= '\n command_on: >\n ';
const haMid4 = '\n command_off: >\n curl -X POST "http://';
const haEnd = '/json/state" -d \'{"on":false}\' -H "Content-Type: application/json"';
const haCommandLeading = ' ';
const JSONledStringStart = '{"on":true, "bri":';
const JSONledStringMid1 = ', "seg":{"i":[';
const JSONledShortStringStart = '{';
const JSONledShortStringMid1 = '"seg":{"i":[';
const JSONledStringEnd = ']}}';
// INDEX.JS
//Start up code
console.log(location.host);
document.getElementById('curlUrl').value = location.host;
let httpArray = [];
//On submit button pressed =======================
document.getElementById('form').addEventListener('submit', function(event) {
event.preventDefault();
let base64Image = document.getElementById('preview').src;
if (isValidBase64Gif(base64Image)) {
document.getElementById('image').src = base64Image;
getPixelRGBValues(base64Image);
document.getElementById('image-container').style.display = "block"
}
else {
let infoDiv = document.getElementById('image-info');
let imageInfo = '<p><b>WARNING!</b> File does not appear to be a valid GIF image</p>'
infoDiv.innerHTML = imageInfo;
infoDiv.style.display = "block"
document.getElementById('image-container').style.display = "none";
document.getElementById('JSONled').value = '';
console.log("The string '" + base64Image + "' is not a valid base64 GIF image.");
}
});
// Code for copying the generated string to clipboard
copyJSONledbutton.addEventListener('click', async () => {
JSONled.select();
try {
await navigator.clipboard.writeText('test text');
console.log('Text copied to clipboard');
} catch (err) {
console.error('Failed to copy text: ', err);
}
});
sendJSONledbutton.addEventListener('click', async () => {
if (window.location.protocol === "https:") {
alert('Will only be available when served over http (or WLED is run over https)');
} else {
postPixels();
}
});
async function postPixels() {
for (let i of httpArray) {
try {
console.log(i);
console.log(i.length);
const response = await fetch('http://'+document.getElementById('curlUrl').value+'/json/state', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
//'Content-Type': 'text/html; charset=UTF-8'
},
body: i
});
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
}
let helpCheckbox = document.getElementById("helpCheckbox");
let helpDiv = document.getElementById("help-container");
helpCheckbox.addEventListener("change", function() {
if (helpCheckbox.checked) {
helpDiv.style.display = "block";
} else {
helpDiv.style.display = "none";
}
});
//File uploader code
const dropZone = document.getElementById('drop-zone');
const filePicker = document.getElementById('file-picker');
const preview = document.getElementById('preview');
// Listen for dragenter, dragover, and drop events
dropZone.addEventListener('dragenter', dragEnter);
dropZone.addEventListener('dragover', dragOver);
dropZone.addEventListener('drop', dropped);
dropZone.addEventListener('click', zoneClicked);
// Listen for change event on file picker
filePicker.addEventListener('change', filePicked);
// Handle zone click
function zoneClicked(e) {
e.preventDefault();
//this.classList.add('drag-over');
//alert('Hej');
filePicker.click();
}
// Handle dragenter
function dragEnter(e) {
e.preventDefault();
this.classList.add('drag-over');
}
// Handle dragover
function dragOver(e) {
e.preventDefault();
}
// Handle drop
function dropped(e) {
e.preventDefault();
this.classList.remove('drag-over');
// Get the dropped file
const file = e.dataTransfer.files[0];
updatePreview(file);
}
// Handle file picked
function filePicked(e) {
// Get the picked file
const file = e.target.files[0];
updatePreview(file);
}
// Update the preview image
function updatePreview(file) {
// Use FileReader to read the file
const reader = new FileReader();
reader.onload = function() {
// Update the preview image
preview.src = reader.result;
document.getElementById("submitConvert").style.display = "block";
};
reader.readAsDataURL(file);
}
function isValidBase64Gif(string) {
// Use a regular expression to check that the string is a valid base64 string
/*
const base64gifPattern = /^data:image\/gif;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
const base64pngPattern = /^data:image\/png;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
const base64jpgPattern = /^data:image\/jpg;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
const base64webpPattern = /^data:image\/webp;base64,([A-Za-z0-9+/:]{4})*([A-Za-z0-9+/:]{3}=|[A-Za-z0-9+/:]{2}==)?$/;
*/
//REMOVED, Any image appear to work as long as it can be drawn to the canvas. Leavingg code in for future use, possibly
if (1==1 || base64gifPattern.test(string) || base64pngPattern.test(string) || base64jpgPattern.test(string) || base64webpPattern.test(string)) {
return true;
}
else {
//Not OK
return false;
}
}
document.getElementById("brightnessNumber").oninput = function() {
document.getElementById("brightnessValue").textContent = this.value;
}
document.getElementById("colorLimitNumber").oninput = function() {
document.getElementById("colorLimitValue").textContent = this.value;
}
var formatSelector = document.getElementById("formatSelector");
var hideableRows = document.querySelectorAll(".ha-hide");
for (var i = 0; i < hideableRows.length; i++) {
hideableRows[i].classList.add("hide");
}
formatSelector.addEventListener("change", function() {
for (var i = 0; i < hideableRows.length; i++) {
hideableRows[i].classList.toggle("hide", this.value !== "ha");
}
});
//GETPIXELVALUES.JS
function getPixelRGBValues(base64Image) {
httpArray = [];
const copyJSONledbutton = document.getElementById('copyJSONledbutton');
const JSONled = document.getElementById('JSONled');
const maxNoOfColorsInCommandSting = document.getElementById('colorLimitNumber').value;
let selectedIndex = -1;
let selector = document.getElementById("formatSelector");
selectedIndex = selector.selectedIndex;
const formatSelection = selector.options[selectedIndex].value;
selector = document.getElementById("ledSetupSelector");
selectedIndex = selector.selectedIndex;
const ledSetupSelection = selector.options[selectedIndex].value;
selector = document.getElementById("colorFormatSelector");
selectedIndex = selector.selectedIndex;
let hexValueCheck = true;
if (selector.options[selectedIndex].value == 'dec'){
hexValueCheck = false
}
selector = document.getElementById("addressingSelector");
selectedIndex = selector.selectedIndex;
let segmentValueCheck = true;
if (selector.options[selectedIndex].value == 'single'){
segmentValueCheck = false
}
let segmentString = ''
let curlString = ''
let haString = ''
let haCommandCurlString = '';
let colorSeparatorStart = '\'';
let colorSeparatorEnd = '\'';
if (!hexValueCheck){
colorSeparatorStart = '[';
colorSeparatorEnd = ']';
}
// Warnings
let hasTransparency = false; //If alpha < 255 is detected on any pixel, this is set to true in code below
let imageInfo = '';
// Create an off-screen canvas
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// Create an image element and set its src to the base64 image
var image = new Image();
image.src = base64Image;
// Wait for the image to load before drawing it onto the canvas
image.onload = function() {
// Set the canvas size to the same as the image
canvas.width = image.width;
canvas.height = image.height;
imageInfo = '<p>Width: ' + image.width + ', Height: ' + image.height + ' (make sure this matches your led matrix setup)</p>'
// Draw the image onto the canvas
context.drawImage(image, 0, 0);
// Get the pixel data from the canvas
var pixelData = context.getImageData(0, 0, image.width, image.height).data;
// Create an array to hold the RGB values of each pixel
var pixelRGBValues = [];
// If the first row of the led matrix is right -> left
let right2leftAdjust = 1;
if (ledSetupSelection == 'l2r'){
right2leftAdjust = 0;
}
// Loop through the pixel data and get the RGB values of each pixel
for (var i = 0; i < pixelData.length; i += 4) {
var r = pixelData[i];
var g = pixelData[i + 1];
var b = pixelData[i + 2];
var a = pixelData[i + 3];
let pixel = i/4
let row = Math.floor(pixel/image.width);
let led = pixel;
if (ledSetupSelection == 'matrix'){
//Do nothing, the matrix is set upp like the index in the image
//Every row starts from the left, i.e. no zigzagging
}
else if ((row + right2leftAdjust) % 2 === 0) {
//Setup is traditional zigzag
//right2leftAdjust basically flips the row order if = 1
//Row is left to right
//Leave led index as pixel index
} else {
//Setup is traditional zigzag
//Row is right to left
//Invert index of row for led
let indexOnRow = led - (row * image.width);
let maxIndexOnRow = image.width - 1;
let reversedIndexOnRow = maxIndexOnRow - indexOnRow;
led = (row * image.width) + reversedIndexOnRow;
}
// Add the RGB values to the pixel RGB values array
pixelRGBValues.push([r, g, b, a, led, pixel, row]);
}
pixelRGBValues.sort((a, b) => a[5] - b[5]);
//Copy the values to a new array for resorting
let ledRGBValues = [... pixelRGBValues];
//Sort the array based on led index
ledRGBValues.sort((a, b) => a[4] - b[4]);
//Generate JSON in WLED format
let JSONledString = '';
let JSONledStringShort = '';
//Set starting values for the segment check to something that is no color
let segmentStart = -1;
let maxi = ledRGBValues.length;
let curentColorIndex = 0
let commandArray = [];
//For evry pixel in the LED array
for (let i = 0; i < maxi; i++) {
let pixel = ledRGBValues[i];
let r = pixel[0];
let g = pixel[1];
let b = pixel[2];
let a = pixel[3];
let segmentString = '';
let segmentEnd = -1;
if(segmentValueCheck){
if (segmentStart < 0){
//This is the first led of a new segment
segmentStart = i;
} //Else we allready have a start index
if (i < maxi - 1){
let iNext = i + 1;
let nextPixel = ledRGBValues[iNext];
if (nextPixel[0] != r || nextPixel[1] != g || nextPixel[2] != b ){
//Next pixel has new color
//The current segment ends with this pixel
segmentEnd = i + 1 //WLED wants the NEXT LED as the stop led...
segmentString = segmentStart + ',' + segmentEnd + ',';
}
} else {
//This is the last pixel, so the segment must end
segmentEnd = i + 1;
segmentString = segmentStart + ',' + segmentEnd + ',';
}
} else{
//Write every pixel
if (JSONledString == ''){
//If addressing is single, we ned to start every command with a starting possition
JSONledString = i + ', \'dummy\',';
}
segmentStart = i
segmentEnd = i
//Segment string should be empty for when addressing single. So no need to set it again.
}
if (a < 255){
hasTransparency = true; //If ANY pixel has alpha < 255 then this is set to true to warn the user
}
if (segmentEnd > -1){
//This is the last pixel in the segment, write to the JSONledString
//Return color value in selected format
let colorValueString = r + ',' + g + ',' + b ;
if (hexValueCheck){
const [red, green, blue] = [r, g, b];
colorValueString = `${[red, green, blue].map(x => x.toString(16).padStart(2, '0')).join('')}`;
} else{
//do nothing, allready set
}
JSONledString = JSONledString + segmentString + colorSeparatorStart + colorValueString + colorSeparatorEnd;
curentColorIndex = curentColorIndex + 1; // We've just added a new color to the string so up the count with one
if (curentColorIndex % maxNoOfColorsInCommandSting === 0 || i == maxi - 1) {
//If we have accumulated the max number of colors to send in a single command or if this is the last pixel, we should write the current colorstring to the array
commandArray.push(JSONledString);
JSONledString = ''; //Start on an new command string
} else
{
//Add a comma to continue the command string
JSONledString = JSONledString + ','
}
//Reset segment values
segmentStart = - 1;
}
}
JSONledString = ''
//For evry commandString in the array
for (let i = 0; i < commandArray.length; i++) {
let thisJSONledString = JSONledStringStart + document.getElementById('brightnessNumber').value + JSONledStringMid1 + commandArray[i] + JSONledStringEnd;
httpArray.push(thisJSONledString);
let thiscurlString = curlStart + document.getElementById('curlUrl').value + curlMid1 + thisJSONledString + curlEnd;
//Aggregated Strings That should be returned to the user
if (i > 0){
JSONledString = JSONledString + '\n';
curlString = curlString + ' && ';
}
JSONledString = JSONledString + thisJSONledString;
curlString = curlString + thiscurlString;
}
haString = haStart + document.getElementById('haID').value + haMid1 + document.getElementById('haName').value + haMid2 + document.getElementById('haUID').value + haMid3 +curlString + haMid3 + document.getElementById('curlUrl').value + haEnd;
if (formatSelection == 'wled'){
JSONled.value = JSONledString;
} else if (formatSelection == 'curl'){
JSONled.value = curlString;
} else if (formatSelection == 'ha'){
JSONled.value = haString;
} else {
JSONled.value = 'ERROR!/n' + formatSelection + ' is an unknown format.'
}
let infoDiv = document.getElementById('image-info');
let canvasDiv = document.getElementById('image-info');
if (hasTransparency){
imageInfo = imageInfo + '<p><b>WARNING!</b> Transparency info detected in image. Transparency (alpha) has been ignored. To ensure you get the result you desire, use only solid colors in your image.</p>'
}
infoDiv.innerHTML = imageInfo;
canvasDiv.style.display = "block"
//Drawing the image
drawBoxes(pixelRGBValues, image.width, image.width);
}
}
//BOXDRAW.JS
function drawBoxes(inputPixelArray, widthPixels, heightPixels) {
// Get a reference to the canvas element
var canvas = document.getElementById('pixelCanvas');
// Get the canvas context
var ctx = canvas.getContext('2d');
// Set the width and height of the canvas
if (window.innerHeight < window.innerWidth) {
canvas.width = Math.floor(window.innerHeight * 0.98);
}
else{
canvas.width = Math.floor(window.innerWidth * 0.98);
}
//canvas.height = window.innerWidth;
let pixelSize = Math.floor(canvas.width/widthPixels);
//Set the canvas height to fit the right number of pixelrows
canvas.height = (pixelSize * heightPixels) + 10
//Iterate through the matrix
for (let y = 0; y < heightPixels; y++) {
for (let x = 0; x < widthPixels; x++) {
// Calculate the index of the current pixel
let i = (y*widthPixels) + x;
//Gets the RGB of the current pixel
let pixel = inputPixelArray[i];
let pixelColor = 'rgb(' + pixel[0] + ', ' + pixel[1] + ', ' + pixel[2] + ')';
let r = pixel[0];
let g = pixel[1];
let b = pixel[2];
let pos = pixel[4];
let textColor = 'rgb(128,128,128)';
// Set the fill style to the pixel color
ctx.fillStyle = pixelColor;
//Draw the rectangle
ctx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
// Draw a border on the box
ctx.strokeStyle = '#888888';
ctx.lineWidth = 1;
ctx.strokeRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
//Write text to box
ctx.font = "10px Arial";
ctx.fillStyle = textColor;
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillText((pos + 1), (x * pixelSize) + (pixelSize /2), (y * pixelSize) + (pixelSize /2));
}
}
}
function drawBackground() {
const grid = document.createElement("div");
grid.id = "grid";
grid.classList.add("grid-class");
grid.style.cssText = "";
const boxSize = 20;
const boxCount = Math.ceil(window.innerWidth / boxSize) * Math.ceil(window.innerHeight / boxSize);;
for (let i = 0; i < boxCount; i++) {
const box = document.createElement("div");
box.classList.add("box");
box.style.backgroundColor = getRandomColor();
grid.appendChild(box);
}
grid.style.zIndex = -1;
document.body.appendChild(grid);
}
function getRandomColor() {
const letters = "0123456789ABCDEF";
let color = "rgba(";
for (let i = 0; i < 3; i++) {
color += Math.floor(Math.random() * 256) + ",";
}
color += "0.05)";
return color;
}
window.drawBackground = drawBackground;
</script>
</body>
</html>
+214
View File
File diff suppressed because one or more lines are too long
+7 -1
View File
@@ -15,4 +15,10 @@ const JSONledStringMid1 = ', "seg":{"i":[';
const JSONledShortStringStart = '{';
const JSONledShortStringMid1 = '"seg":{"i":[';
const JSONledStringEnd = ']}}';
var accentColor = '#7E4C80';
var accentTextColor = '#777';
var scaleToggleOffd = "M17,7H7A5,5 0 0,0 2,12A5,5 0 0,0 7,17H17A5,5 0 0,0 22,12A5,5 0 0,0 17,7M7,15A3,3 0 0,1 4,12A3,3 0 0,1 7,9A3,3 0 0,1 10,12A3,3 0 0,1 7,15Z";
var scaleToggleOnd = "M17,7H7A5,5 0 0,0 2,12A5,5 0 0,0 7,17H17A5,5 0 0,0 22,12A5,5 0 0,0 17,7M17,15A3,3 0 0,1 14,12A3,3 0 0,1 17,9A3,3 0 0,1 20,12A3,3 0 0,1 17,15Z";
+225 -159
View File
@@ -1,166 +1,232 @@
.box {
border: 2px solid white;
}
body {
font-family: 'Arcade', Arial, sans-serif;
background-color: #151515;
.box {
border: 2px solid white;
}
body {
font-family: 'Arcade', Arial, sans-serif;
background-color: #151515;
}
.top-part {
width: 600px;
margin: 0 auto;
}
.container {
max-width: 100% -40px;
border-radius: 0px;
padding: 20px;
text-align: center;
}
h1 {
font-size: 2.3em;
color: rgb(126, 76, 128);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
h2 {
font-size: 1.3em;
color: rgba(126, 76, 128, 0.61);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
p {
font-size: 1.2em;
color: rgb(119, 119, 119);
line-height: 1.5;
font-family: 'Arcade', Arial, sans-serif;
}
#fieldTable {
font-size: 1 em;
color: #777;
line-height: 1;
font-family: 'Arcade', Arial, sans-serif;
}
#scaleTable {
font-size: 1 em;
color: #777;
line-height: 1;
font-family: 'Arcade', Arial, sans-serif;
}
#drop-zone {
display: block;
width: 100%-40px;
border: 3px dashed #7E4C80;
border-radius: 0px;
text-align: center;
padding: 20px;
margin: 0px;
cursor: pointer;
}
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: #777;
}
.top-part {
width: 600px;
margin: 0 auto;
}
.container {
max-width: 100% -40px;
border-radius: 0px;
padding: 20px;
text-align: center;
}
h1 {
font-size: 2.3em;
color: rgb(126, 76, 128);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
h2 {
font-size: 1.3em;
color: rgba(126, 76, 128, 0.61);
margin: 20px 0;
font-family: 'Arcade', Arial, sans-serif;
line-height: 0.5;
text-align: center;
}
#file-picker {
display: none;
}
p {
font-size: 1.2em;
color: rgb(119, 119, 119);
line-height: 1.5;
font-family: 'Arcade', Arial, sans-serif;
}
#fieldTable {
font-size: 1 em;
color: #777;
line-height: 1;
font-family: 'Arcade', Arial, sans-serif;
}
* select {
background-color: #333333;
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-bottom: 0.5em;
padding: 0em;
width: 100%;
height: 27px;
font-size: 15px;
color: rgb(119, 119, 119);
border-radius: 0;
}
#drop-zone {
display: block;
width: 100%-40px;
border: 3px dashed #7E4C80;
border-radius: 0px;
text-align: center;
padding: 20px;
margin: 0px;
cursor: pointer;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: #777;
}
* input[type=range] {
-webkit-appearance:none;
flex-grow: 1;
border-radius: 0px;
background: linear-gradient(to right, #333333 0%, #333333 100%);
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-left: 0em;
}
*.button {
display: block;
width: 100% - 40px;
border: 2px dashed #ccc;
border-radius: 20px;
text-align: center;
padding: 20px;
margin: 0px;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance:none;
width: 25px;
height:25px;
background:#7E4C80;
position:relative;
z-index:3;
}
#file-picker {
display: none;
}
* select {
background-color: #333333;
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-bottom: 0.5em;
padding: 0em;
width: 100%;
height: 27px;
font-size: 15px;
color: rgb(119, 119, 119);
border-radius: 0;
}
* input[type=range] {
-webkit-appearance:none;
flex-grow: 1;
border-radius: 0px;
background: linear-gradient(to right, #333333 0%, #333333 100%);
color: #C0C0C0;
border: 1px solid #C0C0C0;
margin-top: 0.5em;
margin-left: 0em;
}
input[type="range"]::-webkit-slider-thumb{
-webkit-appearance:none;
width: 25px;
height:25px;
background:#7E4C80;
position:relative;
z-index:3;
}
.rangeNumber{
width: 20px;
vertical-align: middle;
}
* input[type=text] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding-inline-start: 5px;
margin-top: 10px;
width: 100%;
height: 27px;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: rgb(119, 119, 119);
}
* input[type="checkbox"] {
}
* input[type=submit] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0.5em;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
}
* button {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0.5em;
margin-bottom: 15px;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
}
* textarea {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0em;
margin-bottom: 10px;
width: 100%;
height: 200px;
border-radius: 0px;
font-family: 'Courier', Arial, sans-serif;
font-size: 1em;
color: rgb(119, 119, 119);
}
.hide {
display: none;
}
.rangeNumber{
width: 20px;
vertical-align: middle;
}
.fullTextField[type=text] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding-inline-start: 5px;
margin-top: 10px;
width: 100%;
height: 27px;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: rgb(119, 119, 119);
}
* input[type=submit] {
background-color: #333333;
border: 1px solid #C0C0C0;
padding: 0.5em;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
}
* button {
background-color: #333333;
border: 1px solid #C0C0C0;
padding-inline: 5px;
width: 100%;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 1.3em;
color: rgb(119, 119, 119);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
textarea {
grid-row: 1 / 2;
width: 100%;
height: 200px;
background-color: #333333;
border: 1px solid #C0C0C0;
color: #777;
}
.hide {
display: none;
}
.grids-class{
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: grid;
grid-template-columns: repeat(auto-fill, 20px);
grid-template-rows: repeat(auto-fill, 20px);
grid-gap: 0px;
}
.svg-icon {
vertical-align: middle;
}
.buttondiv-class {
flex: 1;
display: inline-block;
}
.buttondivmid-class {
width: 10px;
display: inline-block;
}
#image-container {
display: grid;
grid-template-rows: 1fr 1fr;
}
#button-container {
display: flex;
padding-bottom: 10px;
padding-top: 10px;
}
.buttonclass {
flex: 1;
padding-top: 5px;
padding-bottom: 5px;
}
.gap {
width: 10px;
}
#submitConvert::before {
content: "";
display: inline-block;
background-image: url('data:image/svg+xml;utf8, <svg style="width:24px;height:24px" viewBox="0 0 24 24" <path fill="currentColor" d="M12,6V9L16,5L12,1V4A8,8 0 0,0 4,12C4,13.57 4.46,15.03 5.24,16.26L6.7,14.8C6.25,13.97 6,13 6,12A6,6 0 0,1 12,6M18.76,7.74L17.3,9.2C17.74,10.04 18,11 18,12A6,6 0 0,1 12,18V15L8,19L12,23V20A8,8 0 0,0 20,12C20,10.43 19.54,8.97 18.76,7.74Z" /></svg>');
width: 36px;
height: 36px;
}
#sizeDiv * {
display: inline-block;
}
.sizeInputFields{
width: 50px;
background-color: #333333;
border: 1px solid #C0C0C0;
padding-inline-start: 5px;
margin-top: 10px;
height: 27px;
border-radius: 0px;
font-family: 'Arcade', Arial, sans-serif;
font-size: 15px;
color: rgb(119, 119, 119);
}