diff --git a/ESP32_AP-Flasher/data/fonts/bahnschrift80.vlw b/ESP32_AP-Flasher/data/fonts/bahnschrift80.vlw deleted file mode 100644 index 84d11fff..00000000 Binary files a/ESP32_AP-Flasher/data/fonts/bahnschrift80.vlw and /dev/null differ diff --git a/ESP32_AP-Flasher/data/fonts/calibrib30.vlw b/ESP32_AP-Flasher/data/fonts/calibrib30.vlw index f2da8ab3..6c813489 100644 Binary files a/ESP32_AP-Flasher/data/fonts/calibrib30.vlw and b/ESP32_AP-Flasher/data/fonts/calibrib30.vlw differ diff --git a/ESP32_AP-Flasher/data/fonts/calibrib35.vlw b/ESP32_AP-Flasher/data/fonts/calibrib35.vlw deleted file mode 100644 index f6a1d70d..00000000 Binary files a/ESP32_AP-Flasher/data/fonts/calibrib35.vlw and /dev/null differ diff --git a/ESP32_AP-Flasher/data/fonts/calibrib40.vlw b/ESP32_AP-Flasher/data/fonts/calibrib40.vlw deleted file mode 100644 index 4ed81027..00000000 Binary files a/ESP32_AP-Flasher/data/fonts/calibrib40.vlw and /dev/null differ diff --git a/ESP32_AP-Flasher/data/fonts/calibrib50.vlw b/ESP32_AP-Flasher/data/fonts/calibrib50.vlw index 6c0b0dd1..b2b327fa 100644 Binary files a/ESP32_AP-Flasher/data/fonts/calibrib50.vlw and b/ESP32_AP-Flasher/data/fonts/calibrib50.vlw differ diff --git a/ESP32_AP-Flasher/data/fonts/calibrib62.vlw b/ESP32_AP-Flasher/data/fonts/calibrib62.vlw index 97ea7ad3..fb235b10 100644 Binary files a/ESP32_AP-Flasher/data/fonts/calibrib62.vlw and b/ESP32_AP-Flasher/data/fonts/calibrib62.vlw differ diff --git a/ESP32_AP-Flasher/data/www/index.html b/ESP32_AP-Flasher/data/www/index.html index 9fb22b36..ea9c7520 100644 --- a/ESP32_AP-Flasher/data/www/index.html +++ b/ESP32_AP-Flasher/data/www/index.html @@ -38,6 +38,7 @@ +
@@ -80,7 +81,7 @@
diff --git a/ESP32_AP-Flasher/data/www/main.css b/ESP32_AP-Flasher/data/www/main.css index be547bdc..c0459d9f 100644 --- a/ESP32_AP-Flasher/data/www/main.css +++ b/ESP32_AP-Flasher/data/www/main.css @@ -92,14 +92,17 @@ input { border-radius: 0px; } -input[type=button] { +input[type=button], button { border: 0px; padding: 4px 10px; cursor:pointer; } -input[type=button]:hover { + +input[type=button]:hover, +button:hover { background-color:#aaaaaa; } + select { padding: 4px; border-radius: 0px; @@ -128,7 +131,7 @@ select { #configbox input, #apconfigbox input { border: solid 1px #666666; - padding: 4px; + /*padding: 4px;*/ } #configbox label, #apconfigbox label { @@ -326,6 +329,81 @@ ul.messages li.new { color: red; } +#paintbutton { + padding: 1px 3px; + border: 1px solid black; + font-size: 1.3em; + vertical-align: top; + margin-left:12px; + cursor: pointer; +} + +#paintbutton:hover { + background-color: #aaaaaa; +} + +/* painter */ + +#canvasdiv { + padding: 5px; +} + +#canvasdiv canvas { + border: 1px solid black; +} + +#buttonbar { + padding: 5px; + display: flex; + gap: 5px; +} + +#buttonbar button, +#layersdiv button { + padding: 1px 2px; + border: 1px solid #cccccc; + background-color: #dddddd; + width: 40px; +} +#buttonbar button { + font-size: 1.2em; + font-weight: bold; +} + +#buttonbar .active { + background-color: #ffffff; + cursor: pointer; +} + +#buttonbar button:hover, +#layersdiv button:hover { + background-color: #cccccc; + cursor: pointer; +} + +#layersdiv { + padding: 0px 5px; +} + +#layersdiv>div { + display: flex; + gap: 5px; + margin-bottom: 5px; +} + +#layersdiv input, +#layersdiv select { + padding: 2px; +} + +#font-select { + width: 150px; +} + +#savebar button { + border: solid 1px #666666; +} + @media(max-width: 460px) { .messages li div, ul.messages li div.date, ul.messages li div.message { diff --git a/ESP32_AP-Flasher/data/www/main.js b/ESP32_AP-Flasher/data/www/main.js index 3b7dbd6f..9049b2ab 100644 --- a/ESP32_AP-Flasher/data/www/main.js +++ b/ESP32_AP-Flasher/data/www/main.js @@ -31,6 +31,7 @@ contentModeOptions[12] = []; const imageQueue = []; let isProcessing = false; let servertimediff = 0; +let paintLoaded = false, paintShow = false; let socket; connect(); @@ -343,6 +344,40 @@ $('#apcfgsave').onclick = function () { $('#apconfigbox').style.display = 'none'; } +$('#paintbutton').onclick = function () { + if (paintShow) { + paintShow = false; + $('#cfgsave').parentNode.style.display = 'block'; + contentselected(); + } else { + paintShow = true; + $('#cfgsave').parentNode.style.display = 'none'; + $('#customoptions').innerHTML = ""; + const mac = $('#cfgmac').dataset.mac + const hwtype = $('#tag' + mac).dataset.hwtype; + var [width, height] = displaySizeLookup[hwtype] || [0, 0]; + if (height > width) [width, height] = [height, width]; + if (paintLoaded) { + startPainter(mac, width, height); + } else { + loadScript('painter.js', function () { + startPainter(mac, width, height); + }); + } + } +} + +function loadScript(url, callback) { + var script = document.createElement('script'); + script.src = url; + script.onload = function () { + if (callback) { + callback(); + } + }; + document.head.appendChild(script); +} + function contentselected() { let contentMode = $('#cfgcontent').value; let extraoptions = contentModeOptions[contentMode]; @@ -352,6 +387,7 @@ function contentselected() { obj = JSON.parse($('#cfgcontent').dataset.json); } if (contentMode) { + $('#paintbutton').style.display = (contentMode == 0 ? 'inline-block' : 'none'); extraoptions.forEach(element => { var label = document.createElement("label"); label.innerHTML = element; @@ -366,6 +402,8 @@ function contentselected() { $('#customoptions').appendChild(p); }); } + paintShow = false; + $('#cfgsave').parentNode.style.display = 'block'; } function showMessage(message,iserr) { diff --git a/ESP32_AP-Flasher/data/www/painter.js b/ESP32_AP-Flasher/data/www/painter.js new file mode 100644 index 00000000..1244dff2 --- /dev/null +++ b/ESP32_AP-Flasher/data/www/painter.js @@ -0,0 +1,360 @@ +function startPainter(mac, width, height) { + let isDrawing = false; + let lastX = 0; + let lastY = 0; + let color = 'black'; + let linewidth = 3; + let cursor = 'auto'; + let isAddingText = false; + let layerDiv, intervalId, showCursor, input, textX, textY, font, sizeSelect, isDragging; + + var fonts = ['Roboto', 'Open Sans', 'Lato', 'Montserrat', 'PT Sans', 'Barlow Condensed', 'Headland One', 'Sofia Sans Extra Condensed', 'Mynerve', 'Lilita One', 'Passion One', 'Big Shoulders Display']; + + loadGoogleFonts(fonts); + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.imageSmoothingEnabled = false; + + $("#canvasdiv").appendChild(canvas); + canvas.style.imageRendering = 'pixelated'; + + canvas.addEventListener('mousedown', startDrawing); + canvas.addEventListener('mouseup', stopDrawing); + canvas.addEventListener('mousemove', draw); + + canvas.addEventListener('touchstart', startDrawing, { passive: true }); + canvas.addEventListener('touchend', stopDrawing); + canvas.addEventListener('touchmove', draw, { passive: true }); + + const bgCanvas = document.createElement('canvas'); + bgCanvas.width = canvas.width; + bgCanvas.height = canvas.height; + const bgCtx = bgCanvas.getContext('2d'); + + bgCtx.fillStyle = '#ffffff'; + bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height); + + const txtButton = document.createElement('button'); + txtButton.innerHTML = 'tT'; + txtButton.style.fontStyle = 'italic'; + txtButton.addEventListener('click', addText); + + const blackButton = document.createElement('button'); + blackButton.innerHTML = '﹏🖌'; + blackButton.style.color = 'black'; + blackButton.addEventListener('click', () => { + color = 'black'; + linewidth = 3; + cursor = 'url("data:image/svg+xml;utf8,") 2 2, auto'; + blackButton.classList.add('active'); + redButton.classList.remove('active'); + whiteButton.classList.remove('active'); + }); + blackButton.classList.add('active'); + + const redButton = document.createElement('button'); + redButton.innerHTML = '﹏🖌'; + redButton.style.color = 'red'; + redButton.addEventListener('click', () => { + color = 'red'; + linewidth = 3; + cursor = 'url("data:image/svg+xml;utf8,") 2 2, auto'; + blackButton.classList.remove('active'); + redButton.classList.add('active'); + whiteButton.classList.remove('active'); + }); + + const whiteButton = document.createElement('button'); + whiteButton.innerHTML = '⬤'; + whiteButton.style.color = 'white'; + whiteButton.addEventListener('click', () => { + color = 'white'; + linewidth = 20; + cursor = 'url("data:image/svg+xml;utf8,") 10 10, auto'; + blackButton.classList.remove('active'); + redButton.classList.remove('active'); + whiteButton.classList.add('active'); + }); + + const clearButton = document.createElement('button'); + clearButton.innerHTML = '🖵'; + clearButton.addEventListener('click', () => { + if (isAddingText) handleFinish(false); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }); + + const uploadButton = document.createElement('button'); + uploadButton.innerHTML = 'Upload'; + uploadButton.addEventListener('click', () => { + if (isAddingText) handleFinish(true); + const dataURL = canvas.toDataURL('image/jpeg'); + const binaryImage = dataURLToBlob(dataURL); + const formData = new FormData(); + formData.append('mac', mac); + formData.append('dither', '0'); + formData.append('file', binaryImage, 'image.jpg'); + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/imgupload'); + xhr.send(formData); + $('#configbox').style.display = 'none'; + }); + + $("#buttonbar").appendChild(blackButton); + $("#buttonbar").appendChild(redButton); + $("#buttonbar").appendChild(whiteButton); + $("#buttonbar").appendChild(txtButton); + $("#buttonbar").appendChild(clearButton); + $("#savebar").appendChild(uploadButton); + + canvas.addEventListener('mouseenter', function () { + if (!isAddingText) { + canvas.style.cursor = cursor; + } else { + canvas.style.cursor = 'move'; + } + }); + + canvas.addEventListener('mouseleave', function () { + canvas.style.cursor = 'auto'; + }); + + function startDrawing(e) { + if (isAddingText) return; + isDrawing = true; + var rect = canvas.getBoundingClientRect(); + lastX = e.pageX - rect.left - window.pageXOffset; + lastY = e.pageY - rect.top - window.pageYOffset; + } + + function stopDrawing() { + if (isAddingText) return; + isDrawing = false; + } + + function draw(e) { + if (isAddingText) return; + if (!isDrawing) return; + var rect = canvas.getBoundingClientRect(); + ctx.beginPath(); + ctx.moveTo(lastX, lastY); + ctx.lineTo(e.pageX - rect.left - window.pageXOffset, e.pageY - rect.top - window.pageYOffset); + ctx.strokeStyle = color; + ctx.lineWidth = linewidth; + ctx.lineCap = "round"; + ctx.stroke(); + lastX = e.pageX - rect.left - window.pageXOffset; + lastY = e.pageY - rect.top - window.pageYOffset; + } + + function addText() { + if (isAddingText) { + handleFinish(true); + return; + } + txtButton.classList.add('active'); + bgCtx.drawImage(canvas, 0, 0); + + const defaultX = 5; + const defaultY = 40; + isDragging = false; + let startX, startY; + showCursor = true; + + textX = defaultX; + textY = defaultY; + font = '24px ' + fonts[0]; + + input = document.createElement('textarea'); + input.type = 'text'; + input.placeholder = 'Type text here'; + input.style.opacity = '0'; + input.style.position = 'absolute'; + input.style.left = '-200px' + + input.addEventListener('input', () => { + drawText(input.value, textX, textY); + }); + input.addEventListener('keyup', () => { + input.selectionStart = input.selectionEnd = input.value.length; + }); + + input.addEventListener('blur', function () { + input.focus(); + }); + + intervalId = setInterval(function () { + showCursor = !showCursor; + drawText(input.value, textX, textY); + }, 300); + + canvas.addEventListener('mouseup', handleMouseUp); + canvas.addEventListener('mousedown', handleMouseDown); + canvas.addEventListener('mousemove', handleMouseMove); + + canvas.addEventListener('touchstart', handleTouchStart, { passive: true }); + canvas.addEventListener('touchend', handleTouchEnd); + canvas.addEventListener('touchmove', handleTouchMove, { passive: true }); + + var sizes = [10,11,12,13,14,16,18,20,24,28,32,36,40,48,56,64,72,84]; + + const fontSelect = document.createElement('select'); + fontSelect.id = 'font-select'; + for (var i = 0; i < fonts.length; i++) { + const option = document.createElement('option'); + option.value = fonts[i]; + option.text = fonts[i]; + option.style.fontFamily = fonts[i]; + fontSelect.appendChild(option); + } + + sizeSelect = document.createElement('select'); + sizeSelect.id = 'size-select'; + for (var i = 0; i < sizes.length; i++) { + const option = document.createElement('option'); + option.value = sizes[i]; + option.text = sizes[i] + ' px'; + sizeSelect.appendChild(option); + } + + function updateFont() { + var selectedFont = fontSelect.value; + var selectedSize = sizeSelect.value; + fontSelect.style.fontFamily = selectedFont; + font = selectedSize + 'px ' + selectedFont; + drawText(input.value, textX, textY); + } + + fontSelect.value = fonts[0]; + sizeSelect.value = '24'; + fontSelect.addEventListener('change', updateFont); + sizeSelect.addEventListener('change', updateFont); + + const finishButton = document.createElement('button'); + finishButton.innerHTML = '✔'; + finishButton.addEventListener('click', clickHandleFinish); + + layerDiv = document.createElement('div'); + + layerDiv.appendChild(input); + layerDiv.appendChild(fontSelect); + layerDiv.appendChild(sizeSelect); + layerDiv.appendChild(finishButton); + $("#layersdiv").appendChild(layerDiv); + input.focus(); + + isAddingText = true; + //cursor = 'move'; + blackButton.innerHTML = 'aA' + redButton.innerHTML = 'aA' + if (color=='white') { + whiteButton.classList.remove('active'); + blackButton.classList.add('active'); + color='black'; + } + } + + function handleFinish(apply) { + canvas.removeEventListener('mousedown', handleMouseDown); + canvas.removeEventListener('mouseup', handleMouseUp); + canvas.removeEventListener('mousemove', handleMouseMove); + + canvas.removeEventListener('touchstart', handleTouchStart); + canvas.removeEventListener('touchend', handleTouchEnd); + canvas.removeEventListener('touchmove', handleTouchMove); + isAddingText = false; + cursor = 'auto'; + layerDiv.remove(); + clearInterval(intervalId); + showCursor = false; + if (apply) drawText(input.value, textX, textY); + txtButton.classList.remove('active'); + blackButton.innerHTML = '﹏🖌' + redButton.innerHTML = '﹏🖌' + } + + function drawText(text, x, y) { + ctx.drawImage(bgCanvas, 0, 0); + ctx.save(); + ctx.translate(x, y); + ctx.font = font; + ctx.fillStyle = color; + const lines = text.split('\n'); + lines.forEach((line, index) => { + ctx.fillText(line + (showCursor && index === lines.length - 1 ? '|' : ''), 0, index * (sizeSelect.value * 1.1)); + }); + ctx.restore(); + } + + function handleMouseDown(e) { + isDragging = true; + startX = textX; + startY = textY; + ({ clientX: lastMouseX, clientY: lastMouseY } = e); + } + + function handleMouseMove(e) { + if (isDragging) { + const { clientX, clientY } = e; + textX = startX + clientX - lastMouseX; + textY = startY + clientY - lastMouseY; + drawText(input.value, textX, textY); + } + } + + function handleTouchStart(e) { + isDragging = true; + startX = textX; + startY = textY; + ({ clientX: lastTouchX, clientY: lastTouchY } = e.touches[0]); + } + + function handleTouchMove(e) { + if (isDragging) { + const { clientX, clientY } = e.touches[0]; + textX = startX + clientX - lastTouchX; + textY = startY + clientY - lastTouchY; + drawText(input.value, textX, textY); + } + } + + function handleMouseUp(e) { + isDragging = false; + } + + function handleTouchEnd(e) { + isDragging = false; + } + + function clickHandleFinish() { + handleFinish(true); + } + +} + +function loadGoogleFonts(fonts) { + var link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = 'https://fonts.googleapis.com/css?family=' + fonts.join('|'); + document.head.appendChild(link); +} + +function dataURLToBlob(dataURL) { + const byteString = atob(dataURL.split(',')[1]); + const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]; + const arrayBuffer = new ArrayBuffer(byteString.length); + const uint8Array = new Uint8Array(arrayBuffer); + for (let i = 0; i < byteString.length; i++) { + uint8Array[i] = byteString.charCodeAt(i); + } + return new Blob([arrayBuffer], { type: mimeString }); +} + +paintLoaded = true; diff --git a/ESP32_AP-Flasher/data/www/upload-test.html b/ESP32_AP-Flasher/data/www/upload-test.html index dc460b2f..a21e671a 100644 --- a/ESP32_AP-Flasher/data/www/upload-test.html +++ b/ESP32_AP-Flasher/data/www/upload-test.html @@ -10,6 +10,8 @@