mirror of
https://github.com/tsl0922/EPD-nRF5.git
synced 2025-12-05 15:32:48 +08:00
update html
This commit is contained in:
@@ -202,6 +202,10 @@ code {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-group.right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
#status {
|
||||
margin: 10px 0;
|
||||
}
|
||||
@@ -436,6 +440,10 @@ body.debug-mode select:disabled {
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.flex-group.right {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.canvas-tools.flex-container {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>电子墨水屏蓝牙控制器</title>
|
||||
<link rel="stylesheet" href="css/main.css?v=20250607">
|
||||
<title>墨水屏日历</title>
|
||||
<link rel="stylesheet" href="css/main.css?v=20250731">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="main">
|
||||
<h3>电子墨水屏蓝牙控制器</h3>
|
||||
<h3>墨水屏日历</h3>
|
||||
<fieldset>
|
||||
<legend>蓝牙连接</legend>
|
||||
<div class="flex-container">
|
||||
@@ -18,9 +18,9 @@
|
||||
<button id="reconnectbutton" type="button" class="secondary" onclick="reConnect()">重连</button>
|
||||
<button type="button" class="secondary" onclick="clearLog()">清空日志</button>
|
||||
</div>
|
||||
<div class="flex-group debug">
|
||||
<div class="flex-group right debug">
|
||||
<label for="epddriver">驱动</label>
|
||||
<select id="epddriver" onchange="updateDitcherOptions()">
|
||||
<select id="epddriver">
|
||||
<option value="01" data-color="blackWhiteColor" data-size="4.2_400_300">UC8176/UC8276(黑白)</option>
|
||||
<option value="03" data-color="threeColor" data-size="4.2_400_300">UC8176/UC8276(三色)</option>
|
||||
<option value="04" data-color="blackWhiteColor" data-size="4.2_400_300">SSD1619/SSD1683(黑白)</option>
|
||||
@@ -33,28 +33,32 @@
|
||||
<input id="epdpins" type="text" value="">
|
||||
<button id="setDriverbutton" type="button" class="primary" onclick="setDriver()">确认</button>
|
||||
</div>
|
||||
<div class="flex-group">
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>设备控制</legend>
|
||||
<div class="flex-container">
|
||||
<div class="flex-group">
|
||||
<button id="calendarmodebutton" type="button" class="primary" onclick="syncTime(1)">日历模式</button>
|
||||
<button id="clockmodebutton" type="button" class="primary" onclick="syncTime(2)">时钟模式</button>
|
||||
<button id="clearscreenbutton" type="button" class="secondary" onclick="clearScreen()">清除屏幕</button>
|
||||
</div>
|
||||
<div class="flex-group debug">
|
||||
<div class="flex-group right debug">
|
||||
<input type="text" id="cmdTXT" value="">
|
||||
<button id="sendcmdbutton" type="button" class="primary" onclick="sendcmd()">发送命令</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="log"></div>
|
||||
</fieldset>
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>蓝牙传图</legend>
|
||||
<legend>图片处理</legend>
|
||||
<div class="flex-container">
|
||||
<input type="file" id="image_file" onchange="updateImage(true)" accept=".png,.jpg,.bmp,.webp,.jpeg">
|
||||
<input type="file" id="imageFile" accept=".png,.jpg,.bmp,.webp,.jpeg">
|
||||
</div>
|
||||
<div class="flex-container options">
|
||||
<div class="flex-group debug">
|
||||
<label for="canvasSize">画布尺寸:</label>
|
||||
<select id="canvasSize" onchange="updateCanvasSize()">
|
||||
<label for="canvasSize">画布尺寸:</label>
|
||||
<select id="canvasSize">
|
||||
<option value="1.54_152_152">1.54 (152x152)</option>
|
||||
<option value="1.54_200_200">1.54 (200x200)</option>
|
||||
<option value="2.13_212_104">2.13 (212x104)</option>
|
||||
@@ -76,8 +80,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-group debug">
|
||||
<label for="ditherMode">颜色模式:</label>
|
||||
<select id="ditherMode" onchange="updateImage(false)">
|
||||
<label for="ditherMode">颜色模式:</label>
|
||||
<select id="ditherMode">
|
||||
<option value="blackWhiteColor">双色(黑白)</option>
|
||||
<option value="threeColor">三色(黑白红)</option>
|
||||
<option value="fourColor">四色(黑白红黄)</option>
|
||||
@@ -85,8 +89,8 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-group">
|
||||
<label for="ditherType">抖动算法:</label>
|
||||
<select id="ditherType" onchange="updateImage(false)">
|
||||
<label for="ditherAlg">抖动算法:</label>
|
||||
<select id="ditherAlg">
|
||||
<option value="floydSteinberg">Floyd-Steinberg</option>
|
||||
<option value="atkinson">Atkinson</option>
|
||||
<option value="bayer">Bayer</option>
|
||||
@@ -95,19 +99,21 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex-group">
|
||||
<label for="ditherStrength">抖动强度:</label>
|
||||
<input type="range" min="0" max="5" step="0.1" value="1.0" id="ditherStrength" oninput="updateImage(false)">
|
||||
<label for="ditherStrength">抖动强度:</label>
|
||||
<input type="range" min="0" max="5" step="0.1" value="1.0" id="ditherStrength">
|
||||
<label id="ditherStrengthValue">1.0</label>
|
||||
</div>
|
||||
<div class="flex-group">
|
||||
<label for="contrast">对比度:</label>
|
||||
<input type="range" min="0.5" max="2" step="0.1" value="1.2" id="contrast" oninput="updateImage(false)">
|
||||
<label for="ditherContrast">对比度:</label>
|
||||
<input type="range" min="0.5" max="2" step="0.1" value="1.2" id="ditherContrast">
|
||||
<label id="ditherContrastValue">1.2</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container options">
|
||||
<div class="flex-group debug">
|
||||
<label for="mtusize">MTU:</label>
|
||||
<label for="mtusize">MTU:</label>
|
||||
<input type="number" id="mtusize" value="20" min="0" max="255">
|
||||
<label for="interleavedcount">确认间隔:</label>
|
||||
<label for="interleavedcount">确认间隔:</label>
|
||||
<input type="number" id="interleavedcount" value="50" min="0" max="500">
|
||||
</div>
|
||||
</div>
|
||||
@@ -129,7 +135,7 @@
|
||||
<button id="text-mode" title="添加文字" class="tool-button">T</button>
|
||||
</div>
|
||||
<div class="flex-group brush-tools">
|
||||
<label for="brush-color">画笔:</label>
|
||||
<label for="brush-color">颜色:</label>
|
||||
<select id="brush-color">
|
||||
<option value="#000000">黑色</option>
|
||||
<option value="#FF0000">红色</option>
|
||||
@@ -138,13 +144,13 @@
|
||||
<option value="#0000FF">蓝色</option>
|
||||
<option value="#FFFFFF">白色</option>
|
||||
</select>
|
||||
<label for="brush-size">粗细:</label>
|
||||
<label for="brush-size">粗细:</label>
|
||||
<input type="number" id="brush-size" value="2" min="1" max="100">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container canvas-tools">
|
||||
<div class="flex-group text-tools">
|
||||
<label for="font-family">字体:</label>
|
||||
<label for="font-family">字体:</label>
|
||||
<select id="font-family">
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="sans-serif">Sans-serif</option>
|
||||
@@ -176,7 +182,7 @@
|
||||
<option value="WenQuanYi Micro Hei">文泉驿微米黑</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<label for="font-size">大小:</label>
|
||||
<label for="font-size">大小:</label>
|
||||
<input type="number" id="font-size" value="16" min="1" max="100">
|
||||
</div>
|
||||
<div class="flex-group text-tools">
|
||||
@@ -197,9 +203,9 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript" src="js/dithering.js?v=20250607"></script>
|
||||
<script type="text/javascript" src="js/paint.js?v=20250607"></script>
|
||||
<script type="text/javascript" src="js/main.js?v=20250607"></script>
|
||||
<script type="text/javascript" src="js/dithering.js?v=20250731"></script>
|
||||
<script type="text/javascript" src="js/paint.js?v=20250731"></script>
|
||||
<script type="text/javascript" src="js/main.js?v=20250731"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -74,8 +74,7 @@ function labDistance(lab1, lab2) {
|
||||
return Math.sqrt(0.2 * dl * dl + 3 * da * da + 3 * db * db);
|
||||
}
|
||||
|
||||
function findClosestColor(r, g, b) {
|
||||
const mode = document.getElementById('ditherMode').value;
|
||||
function findClosestColor(r, g, b, mode) {
|
||||
let palette;
|
||||
|
||||
if (mode === 'fourColor') {
|
||||
@@ -118,7 +117,7 @@ function findClosestColor(r, g, b) {
|
||||
return closestColor;
|
||||
}
|
||||
|
||||
function floydSteinbergDither(imageData, strength) {
|
||||
function floydSteinbergDither(imageData, strength, mode) {
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
const data = imageData.data;
|
||||
@@ -131,7 +130,7 @@ function floydSteinbergDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
|
||||
const errR = (r - closest.r) * strength;
|
||||
const errG = (g - closest.g) * strength;
|
||||
@@ -171,7 +170,7 @@ function floydSteinbergDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
data[idx] = closest.r;
|
||||
data[idx + 1] = closest.g;
|
||||
data[idx + 2] = closest.b;
|
||||
@@ -181,7 +180,7 @@ function floydSteinbergDither(imageData, strength) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function atkinsonDither(imageData, strength) {
|
||||
function atkinsonDither(imageData, strength, mode) {
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
const data = imageData.data;
|
||||
@@ -194,7 +193,7 @@ function atkinsonDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
|
||||
data[idx] = closest.r;
|
||||
data[idx + 1] = closest.g;
|
||||
@@ -248,7 +247,7 @@ function atkinsonDither(imageData, strength) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function stuckiDither(imageData, strength) {
|
||||
function stuckiDither(imageData, strength, mode) {
|
||||
// 执行Stucki错误扩散算法以处理图像
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
@@ -262,7 +261,7 @@ function stuckiDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
|
||||
const errR = (r - closest.r) * strength;
|
||||
const errG = (g - closest.g) * strength;
|
||||
@@ -352,7 +351,7 @@ function stuckiDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
data[idx] = closest.r;
|
||||
data[idx + 1] = closest.g;
|
||||
data[idx + 2] = closest.b;
|
||||
@@ -362,7 +361,7 @@ function stuckiDither(imageData, strength) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function jarvisDither(imageData, strength) {
|
||||
function jarvisDither(imageData, strength, mode) {
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
const data = imageData.data;
|
||||
@@ -375,7 +374,7 @@ function jarvisDither(imageData, strength) {
|
||||
const g = tempData[idx + 1];
|
||||
const b = tempData[idx + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
|
||||
data[idx] = closest.r;
|
||||
data[idx + 1] = closest.g;
|
||||
@@ -465,7 +464,7 @@ function jarvisDither(imageData, strength) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function bayerDither(imageData, strength) {
|
||||
function bayerDither(imageData, strength, mode) {
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
const data = imageData.data;
|
||||
@@ -508,7 +507,7 @@ function bayerDither(imageData, strength) {
|
||||
const clampedB = Math.min(255, Math.max(0, adjustedB));
|
||||
|
||||
// Find closest color in palette
|
||||
const closest = findClosestColor(clampedR, clampedG, clampedB);
|
||||
const closest = findClosestColor(clampedR, clampedG, clampedB, mode);
|
||||
|
||||
data[idx] = closest.r;
|
||||
data[idx + 1] = closest.g;
|
||||
@@ -519,21 +518,18 @@ function bayerDither(imageData, strength) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function ditherImage(imageData) {
|
||||
const ditherType = document.getElementById('ditherType').value;
|
||||
const ditherStrength = parseFloat(document.getElementById('ditherStrength').value);
|
||||
|
||||
switch (ditherType) {
|
||||
function ditherImage(imageData, alg, strength, mode) {
|
||||
switch (alg) {
|
||||
case 'floydSteinberg':
|
||||
return floydSteinbergDither(imageData, ditherStrength);
|
||||
return floydSteinbergDither(imageData, strength, mode);
|
||||
case 'atkinson':
|
||||
return atkinsonDither(imageData, ditherStrength);
|
||||
return atkinsonDither(imageData, strength, mode);
|
||||
case 'stucki':
|
||||
return stuckiDither(imageData, ditherStrength);
|
||||
return stuckiDither(imageData, strength, mode);
|
||||
case 'jarvis':
|
||||
return jarvisDither(imageData, ditherStrength);
|
||||
return jarvisDither(imageData, strength, mode);
|
||||
case 'bayer':
|
||||
return bayerDither(imageData, ditherStrength);
|
||||
return bayerDither(imageData, strength, mode);
|
||||
default:
|
||||
return imageData;
|
||||
}
|
||||
@@ -620,11 +616,10 @@ function decodeProcessedData(processedData, width, height, mode) {
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function processImageData(imageData) {
|
||||
function processImageData(imageData, mode) {
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
const data = imageData.data;
|
||||
const mode = document.getElementById('ditherMode').value;
|
||||
|
||||
let processedData;
|
||||
|
||||
@@ -637,7 +632,7 @@ function processImageData(imageData) {
|
||||
const g = data[index + 1];
|
||||
const b = data[index + 2];
|
||||
|
||||
const closest = findClosestColor(r, g, b);
|
||||
const closest = findClosestColor(r, g, b, mode);
|
||||
const newIndex = (x * height) + (height - 1 - y);
|
||||
processedData[newIndex] = closest.value;
|
||||
}
|
||||
@@ -650,7 +645,7 @@ function processImageData(imageData) {
|
||||
const r = data[index];
|
||||
const g = data[index + 1];
|
||||
const b = data[index + 2];
|
||||
const closest = findClosestColor(r, g, b); // 使用 fourColorPalette
|
||||
const closest = findClosestColor(r, g, b, mode); // 使用 fourColorPalette
|
||||
const colorValue = closest.value; // 0x00 (黑), 0x01 (白), 0x02 (红), 0x03 (黄)
|
||||
const newIndex = (y * width + x) / 4 | 0;
|
||||
const shift = 6 - ((x % 4) * 2);
|
||||
|
||||
@@ -176,7 +176,7 @@ async function sendimg() {
|
||||
status.parentElement.style.display = "block";
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const processedData = processImageData(imageData);
|
||||
const processedData = processImageData(imageData, ditherMode);
|
||||
|
||||
updateButtonStatus(true);
|
||||
|
||||
@@ -207,9 +207,9 @@ async function sendimg() {
|
||||
}
|
||||
|
||||
function downloadDataArray() {
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const processedData = processImageData(imageData);
|
||||
const mode = document.getElementById('ditherMode').value;
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const processedData = processImageData(imageData, mode);
|
||||
|
||||
if (mode === 'sixColor' && processedData.length !== canvas.width * canvas.height) {
|
||||
console.log(`错误:预期${canvas.width * canvas.height}字节,但得到${processedData.length}字节`);
|
||||
@@ -440,13 +440,13 @@ function updateDitcherOptions() {
|
||||
}
|
||||
|
||||
function updateImage(clear = false) {
|
||||
const image_file = document.getElementById('image_file');
|
||||
if (image_file.files.length == 0) return;
|
||||
const imageFile = document.getElementById('imageFile');
|
||||
if (imageFile.files.length == 0) return;
|
||||
const file = imageFile.files[0];
|
||||
|
||||
if (clear) clearCanvas();
|
||||
|
||||
const file = image_file.files[0];
|
||||
let image = new Image();;
|
||||
let image = new Image();
|
||||
image.src = URL.createObjectURL(file);
|
||||
image.onload = function (event) {
|
||||
URL.revokeObjectURL(this.src);
|
||||
@@ -472,7 +472,7 @@ function clearCanvas() {
|
||||
}
|
||||
|
||||
function convertDithering() {
|
||||
const contrast = parseFloat(document.getElementById('contrast').value);
|
||||
const contrast = parseFloat(document.getElementById('ditherContrast').value);
|
||||
const currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const imageData = new ImageData(
|
||||
new Uint8ClampedArray(currentImageData.data),
|
||||
@@ -482,12 +482,30 @@ function convertDithering() {
|
||||
|
||||
adjustContrast(imageData, contrast);
|
||||
|
||||
const alg = document.getElementById('ditherAlg').value;
|
||||
const strength = parseFloat(document.getElementById('ditherStrength').value);
|
||||
const mode = document.getElementById('ditherMode').value;
|
||||
const processedData = processImageData(ditherImage(imageData));
|
||||
const processedData = processImageData(ditherImage(imageData, alg, strength, mode), mode);
|
||||
const finalImageData = decodeProcessedData(processedData, canvas.width, canvas.height, mode);
|
||||
ctx.putImageData(finalImageData, 0, 0);
|
||||
}
|
||||
|
||||
function initEventHandlers() {
|
||||
document.getElementById("epddriver").addEventListener("change", updateDitcherOptions);
|
||||
document.getElementById("imageFile").addEventListener("change", function () { updateImage(true); });
|
||||
document.getElementById("ditherMode").addEventListener("change", function () { updateImage(false); });
|
||||
document.getElementById("ditherAlg").addEventListener("change", function () { updateImage(false); });
|
||||
document.getElementById("ditherStrength").addEventListener("input", function () {
|
||||
updateImage(false);
|
||||
document.getElementById("ditherStrengthValue").innerText = parseFloat(this.value).toFixed(1);
|
||||
});
|
||||
document.getElementById("ditherContrast").addEventListener("input", function () {
|
||||
updateImage(false);
|
||||
document.getElementById("ditherContrastValue").innerText = parseFloat(this.value).toFixed(1);
|
||||
});
|
||||
document.getElementById("canvasSize").addEventListener("change", updateCanvasSize);
|
||||
}
|
||||
|
||||
function checkDebugMode() {
|
||||
const link = document.getElementById('debug-toggle');
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
@@ -514,6 +532,7 @@ document.body.onload = () => {
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
initPaintTools();
|
||||
initEventHandlers();
|
||||
updateButtonStatus();
|
||||
checkDebugMode();
|
||||
}
|
||||
@@ -96,8 +96,10 @@ function updateToolUI() {
|
||||
|
||||
// Show/hide brush tools
|
||||
document.querySelectorAll('.brush-tools').forEach(el => {
|
||||
el.style.display = ['brush', 'text'].includes(currentTool) ? 'flex' : 'none';
|
||||
el.style.display = ['brush', 'eraser', 'text'].includes(currentTool) ? 'flex' : 'none';
|
||||
});
|
||||
document.getElementById('brush-color').disabled = currentTool === 'eraser';
|
||||
document.getElementById('brush-size').disabled = currentTool === 'text';
|
||||
|
||||
// Show/hide text tools
|
||||
document.querySelectorAll('.text-tools').forEach(el => {
|
||||
|
||||
Reference in New Issue
Block a user