mirror of
https://github.com/jam422470459/EPD-nRF52-hema213.git
synced 2025-12-07 00:52:49 +08:00
drop old firmware support
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>电子墨水屏蓝牙控制器</title>
|
<title>电子墨水屏蓝牙控制器</title>
|
||||||
<link rel="stylesheet" href="css/main.css?v=20250519">
|
<link rel="stylesheet" href="css/main.css?v=20250607">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -198,9 +198,9 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="text/javascript" src="js/dithering.js?v=20250519"></script>
|
<script type="text/javascript" src="js/dithering.js?v=20250607"></script>
|
||||||
<script type="text/javascript" src="js/paint.js?v=20250519"></script>
|
<script type="text/javascript" src="js/paint.js?v=20250607"></script>
|
||||||
<script type="text/javascript" src="js/main.js?v=20250519"></script>
|
<script type="text/javascript" src="js/main.js?v=20250607"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -94,31 +94,7 @@ async function write(cmd, data, withResponse = true) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function epdWrite(cmd, data) {
|
async function writeImage(data, step = 'bw') {
|
||||||
const chunkSize = document.getElementById('mtusize').value - 1;
|
|
||||||
const interleavedCount = document.getElementById('interleavedcount').value;
|
|
||||||
const count = Math.round(data.length / chunkSize);
|
|
||||||
let chunkIdx = 0;
|
|
||||||
let noReplyCount = interleavedCount;
|
|
||||||
|
|
||||||
if (typeof data == 'string') data = hex2bytes(data);
|
|
||||||
|
|
||||||
await write(EpdCmd.SEND_CMD, [cmd]);
|
|
||||||
for (let i = 0; i < data.length; i += chunkSize) {
|
|
||||||
let currentTime = (new Date().getTime() - startTime) / 1000.0;
|
|
||||||
setStatus(`命令:0x${cmd.toString(16)}, 数据块: ${chunkIdx + 1}/${count + 1}, 总用时: ${currentTime}s`);
|
|
||||||
if (noReplyCount > 0) {
|
|
||||||
await write(EpdCmd.SEND_DATA, data.slice(i, i + chunkSize), false);
|
|
||||||
noReplyCount--;
|
|
||||||
} else {
|
|
||||||
await write(EpdCmd.SEND_DATA, data.slice(i, i + chunkSize), true);
|
|
||||||
noReplyCount = interleavedCount;
|
|
||||||
}
|
|
||||||
chunkIdx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function epdWriteImage(data, step = 'bw') {
|
|
||||||
const chunkSize = document.getElementById('mtusize').value - 2;
|
const chunkSize = document.getElementById('mtusize').value - 2;
|
||||||
const interleavedCount = document.getElementById('interleavedcount').value;
|
const interleavedCount = document.getElementById('interleavedcount').value;
|
||||||
const count = Math.round(data.length / chunkSize);
|
const count = Math.round(data.length / chunkSize);
|
||||||
@@ -201,38 +177,24 @@ async function sendimg() {
|
|||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
const processedData = processImageData(imageData);
|
const processedData = processImageData(imageData);
|
||||||
|
|
||||||
let dataSent = true;
|
|
||||||
updateButtonStatus(true);
|
updateButtonStatus(true);
|
||||||
if (appVersion < 0x16) {
|
|
||||||
const driver = epdDriverSelect.value;
|
|
||||||
if (ditherMode === 'threeColor') {
|
|
||||||
const halfLength = Math.floor(processedData.length / 2);
|
|
||||||
await epdWrite(driver === "02" ? 0x24 : 0x10, processedData.slice(0, halfLength));
|
|
||||||
await epdWrite(driver === "02" ? 0x26 : 0x13, processedData.slice(halfLength));
|
|
||||||
} else if (ditherMode === 'blackWhiteColor') {
|
|
||||||
await epdWrite(driver === "04" ? 0x24 : 0x13, processedData);
|
|
||||||
} else {
|
|
||||||
addLog("当前固件不支持此颜色模式。");
|
|
||||||
dataSent = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (ditherMode === 'fourColor') {
|
if (ditherMode === 'fourColor') {
|
||||||
await epdWriteImage(processedData, 'color');
|
await writeImage(processedData, 'color');
|
||||||
} else if (ditherMode === 'threeColor') {
|
} else if (ditherMode === 'threeColor') {
|
||||||
const halfLength = Math.floor(processedData.length / 2);
|
const halfLength = Math.floor(processedData.length / 2);
|
||||||
await epdWriteImage(processedData.slice(0, halfLength), 'bw');
|
await writeImage(processedData.slice(0, halfLength), 'bw');
|
||||||
await epdWriteImage(processedData.slice(halfLength), 'red');
|
await writeImage(processedData.slice(halfLength), 'red');
|
||||||
} else if (ditherMode === 'blackWhiteColor') {
|
} else if (ditherMode === 'blackWhiteColor') {
|
||||||
await epdWriteImage(processedData, 'bw');
|
await writeImage(processedData, 'bw');
|
||||||
} else {
|
} else {
|
||||||
addLog("当前固件不支持此颜色模式。");
|
addLog("当前固件不支持此颜色模式。");
|
||||||
dataSent = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateButtonStatus();
|
updateButtonStatus();
|
||||||
if (!dataSent) return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await write(EpdCmd.REFRESH);
|
await write(EpdCmd.REFRESH);
|
||||||
|
updateButtonStatus();
|
||||||
|
|
||||||
const sendTime = (new Date().getTime() - startTime) / 1000.0;
|
const sendTime = (new Date().getTime() - startTime) / 1000.0;
|
||||||
addLog(`发送完成!耗时: ${sendTime}s`);
|
addLog(`发送完成!耗时: ${sendTime}s`);
|
||||||
@@ -382,6 +344,15 @@ async function connect() {
|
|||||||
appVersion = 0x15;
|
appVersion = 0x15;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appVersion < 0x16) {
|
||||||
|
const oldURL = "https://tsl0922.github.io/EPD-nRF5/v1.5";
|
||||||
|
alert("!!!注意!!!\n当前固件版本过低,可能无法正常使用部分功能,建议升级到最新版本。");
|
||||||
|
if (confirm('是否访问旧版本上位机?')) location.href = oldURL;
|
||||||
|
setTimeout(() => {
|
||||||
|
addLog(`如遇到问题,可访问旧版本上位机: ${oldURL}`);
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await epdCharacteristic.startNotifications();
|
await epdCharacteristic.startNotifications();
|
||||||
epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
|
epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
|||||||
370
html/v1.5/css/main.css
Normal file
370
html/v1.5/css/main.css
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
:root {
|
||||||
|
--primary-color: #0d6efd;
|
||||||
|
--primary-hover: #0b5ed7;
|
||||||
|
--secondary-color: #6c757d;
|
||||||
|
--secondary-hover: #5c636a;
|
||||||
|
|
||||||
|
--dark-bg: #121212;
|
||||||
|
--dark-text: #e0e0e0;
|
||||||
|
--dark-fieldset-bg: #1e1e1e;
|
||||||
|
--dark-border: #333;
|
||||||
|
--dark-code-bg: #2d2d2d;
|
||||||
|
--dark-log-bg: #2a2a2a;
|
||||||
|
--dark-input-bg: #2d2d2d;
|
||||||
|
--dark-input-text: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .debug {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode {
|
||||||
|
background-color: var(--dark-bg);
|
||||||
|
color: var(--dark-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .main {
|
||||||
|
background-color: var(--dark-bg);
|
||||||
|
color: var(--dark-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode fieldset {
|
||||||
|
background-color: var(--dark-fieldset-bg);
|
||||||
|
box-shadow: 0 .5rem 0.5rem rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode h3 {
|
||||||
|
border-bottom: 1px solid var(--dark-border);
|
||||||
|
color: var(--dark-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode code {
|
||||||
|
background: var(--dark-code-bg);
|
||||||
|
color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode #log {
|
||||||
|
background: var(--dark-log-bg);
|
||||||
|
border: 1px solid var(--dark-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode #log .time {
|
||||||
|
color: #8bc34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode #log .action {
|
||||||
|
color: #03a9f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode input[type=text],
|
||||||
|
body.debug-mode input[type=number],
|
||||||
|
body.debug-mode select {
|
||||||
|
background-color: var(--dark-input-bg);
|
||||||
|
color: var(--dark-input-text);
|
||||||
|
border-color: var(--dark-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode input[type=file] {
|
||||||
|
color: var(--dark-input-text);
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: var(--dark-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode input[type=file]::file-selector-button {
|
||||||
|
background-color: var(--dark-fieldset-bg);
|
||||||
|
color: var(--dark-input-text);
|
||||||
|
border-color: var(--dark-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode input[type=file]::file-selector-button:hover {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode fieldset legend {
|
||||||
|
color: #64b5f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 950px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
background: #fff;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .links {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .links a {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .links a:first-child {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer .links a:not(:last-child)::after {
|
||||||
|
content: "•";
|
||||||
|
position: absolute;
|
||||||
|
right: -4px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer a:hover {
|
||||||
|
color: #0d6efd;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .footer .links a:not(:last-child)::after {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .footer {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .footer a {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.debug-mode .footer a:hover {
|
||||||
|
color: #64b5f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
padding-bottom: .3em;
|
||||||
|
border-bottom: 1px solid #CCC;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 .5rem 0.5rem rgba(0, 0, 0, 0.2);
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset legend {
|
||||||
|
font-weight: bold;
|
||||||
|
color: rgba(0, 0, 255, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: .2em .4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
background: #CCC;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100px;
|
||||||
|
max-height: 300px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px;
|
||||||
|
background: #DDD;
|
||||||
|
overflow: auto;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log .time {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log .action {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas-box {
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#canvas {
|
||||||
|
border: black solid 1px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.primary:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--primary-hover);
|
||||||
|
background-color: var(--primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
border-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--secondary-hover);
|
||||||
|
background-color: var(--secondary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text],
|
||||||
|
input[type=number],
|
||||||
|
select {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
padding: .2rem .75rem;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=file] {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::file-selector-button {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: .3rem 2.25rem .3rem .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus {
|
||||||
|
border: 1px solid #86b7fe;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 120, 215, 0.8);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-right: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bar {
|
||||||
|
display: none;
|
||||||
|
font-size: 85%;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 1px dotted #AAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.flex-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-controls,
|
||||||
|
.right-controls {
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-log-container {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#log {
|
||||||
|
height: 150px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text],
|
||||||
|
input[type=number],
|
||||||
|
select {
|
||||||
|
max-width: 100%;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
html/v1.5/index.html
Normal file
106
html/v1.5/index.html
Normal file
File diff suppressed because one or more lines are too long
212
html/v1.5/js/dithering.js
Normal file
212
html/v1.5/js/dithering.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
const bwrPalette = [
|
||||||
|
[0, 0, 0, 255],
|
||||||
|
[255, 255, 255, 255],
|
||||||
|
[255, 0, 0, 255]
|
||||||
|
]
|
||||||
|
|
||||||
|
const bwPalette = [
|
||||||
|
[0, 0, 0, 255],
|
||||||
|
[255, 255, 255, 255],
|
||||||
|
]
|
||||||
|
|
||||||
|
function dithering(ctx, width, height, threshold, type) {
|
||||||
|
const bayerThresholdMap = [
|
||||||
|
[ 15, 135, 45, 165 ],
|
||||||
|
[ 195, 75, 225, 105 ],
|
||||||
|
[ 60, 180, 30, 150 ],
|
||||||
|
[ 240, 120, 210, 90 ]
|
||||||
|
];
|
||||||
|
|
||||||
|
const lumR = [];
|
||||||
|
const lumG = [];
|
||||||
|
const lumB = [];
|
||||||
|
for (let i=0; i<256; i++) {
|
||||||
|
lumR[i] = i*0.299;
|
||||||
|
lumG[i] = i*0.587;
|
||||||
|
lumB[i] = i*0.114;
|
||||||
|
}
|
||||||
|
const imageData = ctx.getImageData(0, 0, width, height);
|
||||||
|
|
||||||
|
const imageDataLength = imageData.data.length;
|
||||||
|
|
||||||
|
// Greyscale luminance (sets r pixels to luminance of rgb)
|
||||||
|
for (let i = 0; i <= imageDataLength; i += 4) {
|
||||||
|
imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i+1]] + lumB[imageData.data[i+2]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const w = imageData.width;
|
||||||
|
let newPixel, err;
|
||||||
|
|
||||||
|
for (let currentPixel = 0; currentPixel <= imageDataLength; currentPixel+=4) {
|
||||||
|
if (type === "gray") {
|
||||||
|
const factor = 255 / (threshold - 1);
|
||||||
|
imageData.data[currentPixel] = Math.round(imageData.data[currentPixel] / factor) * factor;
|
||||||
|
} else if (type ==="none") {
|
||||||
|
// No dithering
|
||||||
|
imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255;
|
||||||
|
} else if (type ==="bayer") {
|
||||||
|
// 4x4 Bayer ordered dithering algorithm
|
||||||
|
var x = currentPixel/4 % w;
|
||||||
|
var y = Math.floor(currentPixel/4 / w);
|
||||||
|
var map = Math.floor( (imageData.data[currentPixel] + bayerThresholdMap[x%4][y%4]) / 2 );
|
||||||
|
imageData.data[currentPixel] = (map < threshold) ? 0 : 255;
|
||||||
|
} else if (type ==="floydsteinberg") {
|
||||||
|
// Floyda€"Steinberg dithering algorithm
|
||||||
|
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255;
|
||||||
|
err = Math.floor((imageData.data[currentPixel] - newPixel) / 16);
|
||||||
|
imageData.data[currentPixel] = newPixel;
|
||||||
|
|
||||||
|
imageData.data[currentPixel + 4 ] += err*7;
|
||||||
|
imageData.data[currentPixel + 4*w - 4 ] += err*3;
|
||||||
|
imageData.data[currentPixel + 4*w ] += err*5;
|
||||||
|
imageData.data[currentPixel + 4*w + 4 ] += err*1;
|
||||||
|
} else {
|
||||||
|
// Bill Atkinson's dithering algorithm
|
||||||
|
newPixel = imageData.data[currentPixel] < threshold ? 0 : 255;
|
||||||
|
err = Math.floor((imageData.data[currentPixel] - newPixel) / 8);
|
||||||
|
imageData.data[currentPixel] = newPixel;
|
||||||
|
|
||||||
|
imageData.data[currentPixel + 4 ] += err;
|
||||||
|
imageData.data[currentPixel + 8 ] += err;
|
||||||
|
imageData.data[currentPixel + 4*w - 4 ] += err;
|
||||||
|
imageData.data[currentPixel + 4*w ] += err;
|
||||||
|
imageData.data[currentPixel + 4*w + 4 ] += err;
|
||||||
|
imageData.data[currentPixel + 8*w ] += err;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set g and b pixels equal to r
|
||||||
|
imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel];
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// white: 1, black/red: 0
|
||||||
|
function canvas2bytes(canvas, type='bw', invert = false) {
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const arr = [];
|
||||||
|
let buffer = [];
|
||||||
|
|
||||||
|
for (let y = 0; y < canvas.height; y++) {
|
||||||
|
for (let x = 0; x < canvas.width; x++) {
|
||||||
|
const i = (canvas.width * y + x) * 4;
|
||||||
|
if (type !== 'red') {
|
||||||
|
buffer.push(imageData.data[i] === 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1);
|
||||||
|
} else {
|
||||||
|
buffer.push(imageData.data[i] > 0 && imageData.data[i+1] === 0 && imageData.data[i+2] === 0 ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.length === 8) {
|
||||||
|
const data = parseInt(buffer.join(''), 2);
|
||||||
|
arr.push(invert ? ~data : data);
|
||||||
|
buffer = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorDistance(rgba1, rgba2) {
|
||||||
|
const [r1, b1, g1] = rgba1;
|
||||||
|
const [r2, b2, g2] = rgba2;
|
||||||
|
|
||||||
|
const rm = (r1 + r2 ) / 2;
|
||||||
|
|
||||||
|
const r = r1 - r2;
|
||||||
|
const g = g1 - g2;
|
||||||
|
const b = b1 - b2;
|
||||||
|
|
||||||
|
return Math.sqrt((2 + rm / 256) * r * r + 4 * g * g + (2 + (255 - rm) / 256) * b * b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNearColor(pixel, palette) {
|
||||||
|
let minDistance = 255 * 255 * 3 + 1;
|
||||||
|
let paletteIndex = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < palette.length; i++) {
|
||||||
|
const targetColor = palette[i];
|
||||||
|
const distance = getColorDistance(pixel, targetColor);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
paletteIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return palette[paletteIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getNearColorV2(color, palette) {
|
||||||
|
let minDistanceSquared = 255*255 + 255*255 + 255*255 + 1;
|
||||||
|
|
||||||
|
let bestIndex = 0;
|
||||||
|
for (let i = 0; i < palette.length; i++) {
|
||||||
|
let rdiff = (color[0] & 0xff) - (palette[i][0] & 0xff);
|
||||||
|
let gdiff = (color[1] & 0xff) - (palette[i][1] & 0xff);
|
||||||
|
let bdiff = (color[2] & 0xff) - (palette[i][2] & 0xff);
|
||||||
|
let distanceSquared = rdiff*rdiff + gdiff*gdiff + bdiff*bdiff;
|
||||||
|
if (distanceSquared < minDistanceSquared) {
|
||||||
|
minDistanceSquared = distanceSquared;
|
||||||
|
bestIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return palette[bestIndex];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updatePixel(imageData, index, color) {
|
||||||
|
imageData[index] = color[0];
|
||||||
|
imageData[index+1] = color[1];
|
||||||
|
imageData[index+2] = color[2];
|
||||||
|
imageData[index+3] = color[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getColorErr(color1, color2, rate) {
|
||||||
|
const res = [];
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
res.push(Math.floor((color1[i] - color2[i]) / rate));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePixelErr(imageData, index, err, rate) {
|
||||||
|
imageData[index] += err[0] * rate;
|
||||||
|
imageData[index+1] += err[1] * rate;
|
||||||
|
imageData[index+2] += err[2] * rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ditheringCanvasByPalette(canvas, palette, type) {
|
||||||
|
palette = palette || bwrPalette;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||||
|
const w = imageData.width;
|
||||||
|
|
||||||
|
for (let currentPixel = 0; currentPixel <= imageData.data.length; currentPixel+=4) {
|
||||||
|
const newColor = getNearColorV2(imageData.data.slice(currentPixel, currentPixel+4), palette);
|
||||||
|
|
||||||
|
if (type === "bwr_floydsteinberg") {
|
||||||
|
const err = getColorErr(imageData.data.slice(currentPixel, currentPixel+4), newColor, 16);
|
||||||
|
|
||||||
|
updatePixel(imageData.data, currentPixel, newColor);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +4, err, 7);
|
||||||
|
updatePixelErr(imageData.data, currentPixel + 4*w - 4, err, 3);
|
||||||
|
updatePixelErr(imageData.data, currentPixel + 4*w, err, 5);
|
||||||
|
updatePixelErr(imageData.data, currentPixel + 4*w + 4, err, 1);
|
||||||
|
} else {
|
||||||
|
const err = getColorErr(imageData.data.slice(currentPixel, currentPixel+4), newColor, 8);
|
||||||
|
|
||||||
|
updatePixel(imageData.data, currentPixel, newColor);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +4, err, 1);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +8, err, 1);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +4 * w - 4, err, 1);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +4 * w, err, 1);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +4 * w + 4, err, 1);
|
||||||
|
updatePixelErr(imageData.data, currentPixel +8 * w, err, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
}
|
||||||
369
html/v1.5/js/main.js
Normal file
369
html/v1.5/js/main.js
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
let bleDevice, gattServer;
|
||||||
|
let epdService, epdCharacteristic;
|
||||||
|
let startTime, msgIndex;
|
||||||
|
let canvas, ctx, textDecoder;
|
||||||
|
|
||||||
|
const EpdCmd = {
|
||||||
|
SET_PINS: 0x00,
|
||||||
|
INIT: 0x01,
|
||||||
|
CLEAR: 0x02,
|
||||||
|
SEND_CMD: 0x03,
|
||||||
|
SEND_DATA: 0x04,
|
||||||
|
REFRESH: 0x05,
|
||||||
|
SLEEP: 0x06,
|
||||||
|
|
||||||
|
SET_TIME: 0x20,
|
||||||
|
|
||||||
|
SET_CONFIG: 0x90,
|
||||||
|
SYS_RESET: 0x91,
|
||||||
|
SYS_SLEEP: 0x92,
|
||||||
|
CFG_ERASE: 0x99,
|
||||||
|
};
|
||||||
|
|
||||||
|
function resetVariables() {
|
||||||
|
gattServer = null;
|
||||||
|
epdService = null;
|
||||||
|
epdCharacteristic = null;
|
||||||
|
msgIndex = 0;
|
||||||
|
document.getElementById("log").value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function write(cmd, data, withResponse=true) {
|
||||||
|
if (!epdCharacteristic) {
|
||||||
|
addLog("服务不可用,请检查蓝牙连接");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let payload = [cmd];
|
||||||
|
if (data) {
|
||||||
|
if (typeof data == 'string') data = hex2bytes(data);
|
||||||
|
if (data instanceof Uint8Array) data = Array.from(data);
|
||||||
|
payload.push(...data)
|
||||||
|
}
|
||||||
|
addLog(`<span class="action">⇑</span> ${bytes2hex(payload)}`);
|
||||||
|
try {
|
||||||
|
if (withResponse)
|
||||||
|
await epdCharacteristic.writeValueWithResponse(Uint8Array.from(payload));
|
||||||
|
else
|
||||||
|
await epdCharacteristic.writeValueWithoutResponse(Uint8Array.from(payload));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e.message) addLog("write: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function epdWrite(cmd, data) {
|
||||||
|
const chunkSize = document.getElementById('mtusize').value - 1;
|
||||||
|
const interleavedCount = document.getElementById('interleavedcount').value;
|
||||||
|
const count = Math.round(data.length / chunkSize);
|
||||||
|
let chunkIdx = 0;
|
||||||
|
let noReplyCount = interleavedCount;
|
||||||
|
|
||||||
|
if (typeof data == 'string') data = hex2bytes(data);
|
||||||
|
|
||||||
|
await write(EpdCmd.SEND_CMD, [cmd]);
|
||||||
|
for (let i = 0; i < data.length; i += chunkSize) {
|
||||||
|
let currentTime = (new Date().getTime() - startTime) / 1000.0;
|
||||||
|
setStatus(`命令:0x${cmd.toString(16)}, 数据块: ${chunkIdx+1}/${count+1}, 总用时: ${currentTime}s`);
|
||||||
|
if (noReplyCount > 0) {
|
||||||
|
await write(EpdCmd.SEND_DATA, data.slice(i, i + chunkSize), false);
|
||||||
|
noReplyCount--;
|
||||||
|
} else {
|
||||||
|
await write(EpdCmd.SEND_DATA, data.slice(i, i + chunkSize), true);
|
||||||
|
noReplyCount = interleavedCount;
|
||||||
|
}
|
||||||
|
chunkIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setDriver() {
|
||||||
|
await write(EpdCmd.SET_PINS, document.getElementById("epdpins").value);
|
||||||
|
await write(EpdCmd.INIT, document.getElementById("epddriver").value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncTime(mode) {
|
||||||
|
const timestamp = new Date().getTime() / 1000;
|
||||||
|
const data = new Uint8Array([
|
||||||
|
(timestamp >> 24) & 0xFF,
|
||||||
|
(timestamp >> 16) & 0xFF,
|
||||||
|
(timestamp >> 8) & 0xFF,
|
||||||
|
timestamp & 0xFF,
|
||||||
|
-(new Date().getTimezoneOffset() / 60),
|
||||||
|
mode
|
||||||
|
]);
|
||||||
|
if(await write(EpdCmd.SET_TIME, data)) {
|
||||||
|
addLog("时间已同步!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearScreen() {
|
||||||
|
if(confirm('确认清除屏幕内容?')) {
|
||||||
|
await write(EpdCmd.CLEAR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendcmd() {
|
||||||
|
const cmdTXT = document.getElementById('cmdTXT').value;
|
||||||
|
if (cmdTXT == '') return;
|
||||||
|
const bytes = hex2bytes(cmdTXT);
|
||||||
|
await write(bytes[0], bytes.length > 1 ? bytes.slice(1) : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendimg() {
|
||||||
|
const status = document.getElementById("status");
|
||||||
|
const driver = document.getElementById("epddriver").value;
|
||||||
|
const mode = document.getElementById('dithering').value;
|
||||||
|
|
||||||
|
if (mode === '') {
|
||||||
|
alert('请选择一种取模算法!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime = new Date().getTime();
|
||||||
|
status.parentElement.style.display = "block";
|
||||||
|
|
||||||
|
if (mode.startsWith('bwr')) {
|
||||||
|
const invert = (driver === '02') || (driver === '05');
|
||||||
|
await epdWrite(driver === "02" ? 0x24 : 0x10, canvas2bytes(canvas, 'bw'));
|
||||||
|
await epdWrite(driver === "02" ? 0x26 : 0x13, canvas2bytes(canvas, 'red', invert));
|
||||||
|
} else {
|
||||||
|
await epdWrite(driver === "04" ? 0x24 : 0x13, canvas2bytes(canvas, 'bw'));
|
||||||
|
}
|
||||||
|
|
||||||
|
await write(EpdCmd.REFRESH);
|
||||||
|
|
||||||
|
const sendTime = (new Date().getTime() - startTime) / 1000.0;
|
||||||
|
addLog(`发送完成!耗时: ${sendTime}s`);
|
||||||
|
setStatus(`发送完成!耗时: ${sendTime}s`);
|
||||||
|
setTimeout(() => {
|
||||||
|
status.parentElement.style.display = "none";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButtonStatus() {
|
||||||
|
const connected = gattServer != null && gattServer.connected;
|
||||||
|
const status = connected ? null : 'disabled';
|
||||||
|
document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null;
|
||||||
|
document.getElementById("sendcmdbutton").disabled = status;
|
||||||
|
document.getElementById("calendarmodebutton").disabled = status;
|
||||||
|
document.getElementById("clockmodebutton").disabled = status;
|
||||||
|
document.getElementById("clearscreenbutton").disabled = status;
|
||||||
|
document.getElementById("sendimgbutton").disabled = status;
|
||||||
|
document.getElementById("setDriverbutton").disabled = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnect() {
|
||||||
|
updateButtonStatus();
|
||||||
|
resetVariables();
|
||||||
|
addLog('已断开连接.');
|
||||||
|
document.getElementById("connectbutton").innerHTML = '连接';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function preConnect() {
|
||||||
|
if (gattServer != null && gattServer.connected) {
|
||||||
|
if (bleDevice != null && bleDevice.gatt.connected) {
|
||||||
|
bleDevice.gatt.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetVariables();
|
||||||
|
try {
|
||||||
|
bleDevice = await navigator.bluetooth.requestDevice({
|
||||||
|
optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'],
|
||||||
|
acceptAllDevices: true
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e.message) addLog("requestDevice: " + e.message);
|
||||||
|
addLog("请检查蓝牙是否已开启,且使用的浏览器支持蓝牙!建议使用以下浏览器:");
|
||||||
|
addLog("• 电脑: Chrome/Edge");
|
||||||
|
addLog("• Android: Chrome/Edge");
|
||||||
|
addLog("• iOS: Bluefy 浏览器");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bleDevice.addEventListener('gattserverdisconnected', disconnect);
|
||||||
|
setTimeout(async function () { await connect(); }, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reConnect() {
|
||||||
|
if (bleDevice != null && bleDevice.gatt.connected)
|
||||||
|
bleDevice.gatt.disconnect();
|
||||||
|
resetVariables();
|
||||||
|
addLog("正在重连");
|
||||||
|
setTimeout(async function () { await connect(); }, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleNotify(value, idx) {
|
||||||
|
const data = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
||||||
|
if (idx == 0) {
|
||||||
|
addLog(`收到配置:${bytes2hex(data)}`);
|
||||||
|
const epdpins = document.getElementById("epdpins");
|
||||||
|
const epddriver = document.getElementById("epddriver");
|
||||||
|
epdpins.value = bytes2hex(data.slice(0, 7));
|
||||||
|
if (data.length > 10) epdpins.value += bytes2hex(data.slice(10, 11));
|
||||||
|
epddriver.value = bytes2hex(data.slice(7, 8));
|
||||||
|
filterDitheringOptions();
|
||||||
|
} else {
|
||||||
|
if (textDecoder == null) textDecoder = new TextDecoder();
|
||||||
|
const msg = textDecoder.decode(data);
|
||||||
|
addLog(`<span class="action">⇓</span> ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function connect() {
|
||||||
|
if (bleDevice == null || epdCharacteristic != null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
addLog("正在连接: " + bleDevice.name);
|
||||||
|
gattServer = await bleDevice.gatt.connect();
|
||||||
|
addLog(' 找到 GATT Server');
|
||||||
|
epdService = await gattServer.getPrimaryService('62750001-d828-918d-fb46-b6c11c675aec');
|
||||||
|
addLog(' 找到 EPD Service');
|
||||||
|
epdCharacteristic = await epdService.getCharacteristic('62750002-d828-918d-fb46-b6c11c675aec');
|
||||||
|
addLog(' 找到 Characteristic');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e.message) addLog("connect: " + e.message);
|
||||||
|
disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await epdCharacteristic.startNotifications();
|
||||||
|
epdCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
|
||||||
|
handleNotify(event.target.value, msgIndex++);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e.message) addLog("startNotifications: " + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
await write(EpdCmd.INIT);
|
||||||
|
|
||||||
|
document.getElementById("connectbutton").innerHTML = '断开';
|
||||||
|
updateButtonStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(statusText) {
|
||||||
|
document.getElementById("status").innerHTML = statusText;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLog(logTXT) {
|
||||||
|
const log = document.getElementById("log");
|
||||||
|
const now = new Date();
|
||||||
|
const time = String(now.getHours()).padStart(2, '0') + ":" +
|
||||||
|
String(now.getMinutes()).padStart(2, '0') + ":" +
|
||||||
|
String(now.getSeconds()).padStart(2, '0') + " ";
|
||||||
|
log.innerHTML += '<span class="time">' + time + '</span>' + logTXT + '<br>';
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
while ((log.innerHTML.match(/<br>/g) || []).length > 20) {
|
||||||
|
var logs_br_position = log.innerHTML.search("<br>");
|
||||||
|
log.innerHTML = log.innerHTML.substring(logs_br_position + 4);
|
||||||
|
log.scrollTop = log.scrollHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLog() {
|
||||||
|
document.getElementById("log").innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hex2bytes(hex) {
|
||||||
|
for (var bytes = [], c = 0; c < hex.length; c += 2)
|
||||||
|
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||||
|
return new Uint8Array(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function bytes2hex(data) {
|
||||||
|
return new Uint8Array(data).reduce(
|
||||||
|
function (memo, i) {
|
||||||
|
return memo + ("0" + i.toString(16)).slice(-2);
|
||||||
|
}, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function intToHex(intIn) {
|
||||||
|
let stringOut = ("0000" + intIn.toString(16)).substr(-4)
|
||||||
|
return stringOut.substring(2, 4) + stringOut.substring(0, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update_image() {
|
||||||
|
let image = new Image();;
|
||||||
|
const image_file = document.getElementById('image_file');
|
||||||
|
if (image_file.files.length > 0) {
|
||||||
|
const file = image_file.files[0];
|
||||||
|
image.src = URL.createObjectURL(file);
|
||||||
|
} else {
|
||||||
|
image.src = document.getElementById('demo-img').src;
|
||||||
|
}
|
||||||
|
|
||||||
|
image.onload = function(event) {
|
||||||
|
URL.revokeObjectURL(this.src);
|
||||||
|
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
|
||||||
|
convert_dithering()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear_canvas() {
|
||||||
|
if(confirm('确认清除画布内容?')) {
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convert_dithering() {
|
||||||
|
const mode = document.getElementById('dithering').value;
|
||||||
|
if (mode === '') return;
|
||||||
|
|
||||||
|
if (mode.startsWith('bwr')) {
|
||||||
|
ditheringCanvasByPalette(canvas, bwrPalette, mode);
|
||||||
|
} else {
|
||||||
|
dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterDitheringOptions() {
|
||||||
|
const driver = document.getElementById('epddriver').value;
|
||||||
|
const dithering = document.getElementById('dithering');
|
||||||
|
for (let optgroup of dithering.getElementsByTagName('optgroup')) {
|
||||||
|
const drivers = optgroup.getAttribute('data-driver').split('|');
|
||||||
|
const show = drivers.includes(driver);
|
||||||
|
for (option of optgroup.getElementsByTagName('option')) {
|
||||||
|
if (show)
|
||||||
|
option.removeAttribute('disabled');
|
||||||
|
else
|
||||||
|
option.setAttribute('disabled', 'disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dithering.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDebugMode() {
|
||||||
|
const link = document.getElementById('debug-toggle');
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const debugMode = urlParams.get('debug');
|
||||||
|
|
||||||
|
if (debugMode === 'true') {
|
||||||
|
document.body.classList.add('debug-mode');
|
||||||
|
link.innerHTML = '正常模式';
|
||||||
|
link.setAttribute('href', window.location.pathname);
|
||||||
|
addLog("注意:开发模式功能已开启!不懂请不要随意修改,否则后果自负!");
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('debug-mode');
|
||||||
|
link.innerHTML = '开发模式';
|
||||||
|
link.setAttribute('href', window.location.pathname + '?debug=true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.onload = () => {
|
||||||
|
textDecoder = null;
|
||||||
|
canvas = document.getElementById('canvas');
|
||||||
|
ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
updateButtonStatus();
|
||||||
|
update_image();
|
||||||
|
filterDitheringOptions();
|
||||||
|
|
||||||
|
checkDebugMode();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user