Update cc2640r2_etag-2in13.html (#20)

https://github.com/ihopenot/ihopebleepd/ 搬运了一点代码,让2in3的非渐变屏更易用
This commit is contained in:
shuxin
2024-10-29 14:45:30 +08:00
committed by GitHub
parent 5d4210f292
commit 1258bf3310

View File

@@ -10,6 +10,239 @@
<body>
<script>
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, thresholds, 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 ==="somegray") {
imageData.data[currentPixel] = imageData.data[currentPixel] > thresholds[2] ? 255 : (imageData.data[currentPixel] > thresholds[1] ? 170 : (imageData.data[currentPixel] > thresholds[0] ? 85 : 0));
} else if (type ==="none") {
// No dithering
imageData.data[currentPixel] = imageData.data[currentPixel] < thresholds[0] ? 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 < thresholds[0]) ? 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] < thresholds[0] ? 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);
}
function canvas2bytes(canvas, type='bw', graysec=0) {
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const arr = [];
let buffer = [];
for (let x = canvas.width - 1; x >= 0; x--) {
for (let y = 0; y < canvas.height; y++) {
const index = (canvas.width * 4 * y) + x * 4;
if (type === 'bwr') {
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] === 0 && imageData.data[index+2] === 0 ? 1 : 0);
} else if(type === "bw") {
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] > 0 && imageData.data[index+2] > 0 ? 1 : 0);
} else if(type === "gray") {
buffer.push(imageData.data[index]/85 >= graysec ? 1 : 0);
}
if (buffer.length === 8) {
arr.push(parseInt(buffer.join(''), 2));
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);
}
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return new Uint8Array(bytes);
}
function bytesToHex(data) {
return new Uint8Array(data).reduce(
function (memo, i) {
return memo + ("0" + i.toString(16)).slice(-2);
}, "");
}
function intToHex(intIn, bytes=4) {
return intIn.toString(16).padStart(bytes * 2, '0');
}
</script>
<script>
let bleDevice;
let gattServer;
@@ -39,6 +272,20 @@
}
}
function etagFnEnable(flag) {
const ts = ['input','button','select']
ts.forEach(t => {
const eles = document.getElementById('etagFn').querySelectorAll(t);
eles.forEach(ele => {
if(flag){
ele.removeAttribute("disabled");
}else{
ele.setAttribute('disabled', true);
}
});
});
}
async function connect() {
if (epochCharacter == null) {
info("Connect to " + bleDevice.name)
@@ -48,7 +295,7 @@
info('> Found EPD service');
epochCharacter = await epdService.getCharacteristic(0xfff1);
document.getElementById("btnConnect").innerHTML = 'Disconnect';
document.getElementById('etagFn').style.visibility = '';
etagFnEnable(true);//document.getElementById('etagFn').style.visibility = '';
}
}
@@ -59,7 +306,7 @@
info('Disconnected.');
document.getElementById("btnConnect").innerHTML = 'Connect';
document.getElementById('etagFn').style.visibility = 'hidden';
etagFnEnable(false);//document.getElementById('etagFn').style.visibility = 'hidden';
}
async function doSetTime() {
@@ -91,7 +338,7 @@
// Temperature
var chr = await epdService.getCharacteristic(0xfff4);
var temp = (await chr.readValue()).getInt8(0, 1);
info(`# etag sensor: battery(${batt}mv), temperature(${temp})℃.`);
info(`# etag sensor: battery(${batt}mv), temperature(${temp}).`);
// RTC Collaborate
var chr = await epdService.getCharacteristic(0xfff5);
@@ -100,8 +347,8 @@
}
async function doRtcCollab() {
var col = prompt("对 32.768kHz 晶振补偿频漂,走时快补偿负数,走时慢补偿正数。可选范围 (-5 ~ 5)", -3);
if (col == null || col < -3 || col > 3) return;
var col = prompt("对 32.768kHz 晶振补偿频漂,走时快补偿负数,走时慢补偿正数。可选范围 (-5 ~ 5)", 0);
if (col == null || col < -5 || col > 5) return;
var chr = await epdService.getCharacteristic(0xfff5);
var buf = new ArrayBuffer(1);
var arr = new Int8Array(buf);
@@ -432,6 +679,22 @@
return arr;
}
async function doMyLut() {
const O = 0b00000000; // VSS
const B = 0b01000000; // VSH1 for Black
const W = 0b10000000; // VSL for white
const R = 0b11000000; // VSH2 for Red
const lut_gray8 = [
// RP A B C D SRAB SRCD
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUTC
0x01, R | 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTR
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // LUTW
0x01, B | 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTB
0x01, B | 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, // LUTB
];
await doCmd('lut', lut_gray8);
}
async function doFill(color) {
await doCmd('rst');
await sleep(2000);
@@ -498,12 +761,104 @@
}
</script>
<script>
async function update_image() {
const image_file = document.getElementById('image_file');
if (image_file.files.length > 0) {
const file = image_file.files[0];
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const image = new Image();
image.src = URL.createObjectURL(file);
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 get_position(canvas, x, y) {
let rect = canvas.getBoundingClientRect()
return {
x: x - rect.left * (canvas.width / rect.width),
y: y - rect.top * (canvas.height / rect.height)
}
}
function clear_canvas() {
if (confirm('确认清除屏幕?')) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
function horizon_clip_canvas() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
const sourceData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var newData = ctx.createImageData(canvas.width, canvas.height);
for (var i = 0, h = sourceData.height; i < h; i++) {
for (j = 0, w = sourceData.width; j < w; j++) {
newData.data[i * w * 4 + j * 4 + 0] =
sourceData.data[i * w * 4 + (w - j) * 4 + 0];
newData.data[i * w * 4 + j * 4 + 1] =
sourceData.data[i * w * 4 + (w - j) * 4 + 1];
newData.data[i * w * 4 + j * 4 + 2] =
sourceData.data[i * w * 4 + (w - j) * 4 + 2];
newData.data[i * w * 4 + j * 4 + 3] =
sourceData.data[i * w * 4 + (w - j) * 4 + 3];
}
}
ctx.putImageData(newData, 0, 0);
}
function vertial_clip_canvas() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
const sourceData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var newData = ctx.createImageData(canvas.width, canvas.height);
for (var i = 0, h = sourceData.height; i < h; i++) {
for (var j = 0, w = sourceData.width; j < w; j++) {
newData.data[i * w * 4 + j * 4 + 0] =
sourceData.data[(h - i) * w * 4 + j * 4 + 0];
newData.data[i * w * 4 + j * 4 + 1] =
sourceData.data[(h - i) * w * 4 + j * 4 + 1];
newData.data[i * w * 4 + j * 4 + 2] =
sourceData.data[(h - i) * w * 4 + j * 4 + 2];
newData.data[i * w * 4 + j * 4 + 3] =
sourceData.data[(h - i) * w * 4 + j * 4 + 3];
}
}
ctx.putImageData(newData, 0, 0);
}
function convert_dithering() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
const mode = document.getElementById('dithering').value;
const thresholds = []
for (let i = 0; i < 3; i++) {
thresholds.push(parseInt(document.getElementById(`threshold${i}`).value))
}
console.log(thresholds)
if (mode.startsWith('bwr')) {
ditheringCanvasByPalette(canvas, bwrPalette, mode);
} else if (mode.startsWith('somegray')) {
dithering(ctx, canvas.width, canvas.height, thresholds, mode);
} else {
dithering(ctx, canvas.width, canvas.height, thresholds, mode);
}
}
</script>
<div>
<label> Choose </label>
<button id="btnConnect" type="button" onclick="doConnect()">Connect</button>
</div>
<div id="etagFn" style="visibility:hidden;">
<div id="etagFn" style="_visibility_:hidden;">
<div>
<button type="button" onclick="doCmd('mode', 'clock')">时钟模式</button>
<button id="btnReadEtag" type="button" onclick="doReadEtag()">ReadEtag</button>
@@ -517,20 +872,250 @@
<button type="button" onclick="doCmd('clr')">清屏</button>
<button type="button" onclick="doFill('black')">全黑</button>
<button type="button" onclick="doFill('red')">全红</button>
</div>
<div id="canvas-box">
<input type="file" id="image_file" onchange="load_image()" accept=".png,.jpg,.bmp,.webp,.gif">
<input type="file" id="image_file" onchange="update_image()" accept=".png,.jpg,.bmp,.webp,.gif">
<br>
抖动算法:
<select id="dithering" title="抖动算法">
<optgroup label="黑白">
<option value="none">二值化</option>
<option value="bayer">bayer</option>
<option value="floydsteinberg">floydsteinberg</option>
<option value="Atkinson">Atkinson</option>
</optgroup>
<optgroup label="黑白红多色">
<option value="bwr_floydsteinberg">黑白红floydsteinberg</option>
<option value="bwr_Atkinson">黑白红Atkinson</option>
</optgroup>
<optgroup label="灰色">
<option value="somegray"></option>
</optgroup>
</select>
屏幕尺寸:
<select id="screensize" title="屏幕尺寸">
<option value="250*122">2.13(250*122)</option>
<option value="212*104">2.13(212*104)</option>
<option value="300*400">4.2(300*400)</option>
</select>
<br>
阈值:
<input type="number" max="255" min="0" value="20" id="threshold0">
<input type="number" max="255" min="0" value="120" id="threshold1">
<input type="number" max="255" min="0" value="220" id="threshold2">
<button onclick="update_image()">重新加载</button>
<br>
<div id="tool-box">
模式:
<select id="canvas-mode">
<option value="paint">画笔</option>
<option value="font" title="输入完成后 点击:保存文本框">输入文字</option>
</select>
画笔/文字大小:
<input type="number" max="13" min="1" step="1" value="3" id="paint-size">
画笔颜色:
<select id="paint-color">
<option value="red">红色</option>
<option value="white">白色</option>
<option value="black">黑色</option>
</select>
<select id="font" title="字体" style="display: none;">
<option value="微软雅黑">微软雅黑</option>
<option value="黑体">黑体</option>
<option value="仿宋">仿宋</option>
<option value="宋体">宋体</option>
<option value="楷体_GB2312">楷体_GB2312</option>
<option value="华文行楷">华文行楷</option>
</select>
<button id="update-text" style="display: none">保存文本框</button>
<button onclick="clear_canvas()">清屏</button>
<button onclick="horizon_clip_canvas()">水平翻转</button>
<button onclick="vertial_clip_canvas()">垂直翻转</button>
</div>
<input id="edit-font" style="max-width: 250; position: absolute; border: black solid 1px;background-color: rgba(0,0,0,0);display: none;overflow: auto" />
<canvas id="canvas" width="250" height="122" style="border: black solid 1px;"></canvas>
<br>
<button onclick="doUploadImage('full')">默认刷新</button>
<br>
</div>
</div>
</div>
<div id="log">
CC2640R2-ETAG Webtool. <br>
</div>
<script>etagFnEnable(false);</script>
<script>
document.body.onload = () => {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
let is_allow_drawing = false;
let is_allow_move_editor = false;
const image_mode = document.getElementById('canvas-mode');
const paint_size = document.getElementById('paint-size');
const paint_color = document.getElementById('paint-color');
const editor = document.getElementById('edit-font');
const font = document.getElementById('font');
document.getElementById('dithering').value = 'bwr_Atkinson';
image_mode.value = 'paint';
paint_color.value = 'black';
font.value = '黑体';
editor.onmousemove = function (e) {
editor.style.fontSize = `${paint_size.value * 10}px`;
editor.style.color = paint_color.value;
editor.style.fontFamily = font.value;
editor.style.fontWeight = 'bold';
if (is_allow_move_editor) {
const { x, y } = get_position(canvas, e.clientX, e.clientY);
if (x < 0 || y < 0 || x > canvas.width || y > canvas.height) {
return;
}
editor.style.left = `${e.clientX - 20}px`;
editor.style.top = `${e.clientY - 20}px`;
}
}
editor.onmousedown = function (e) {
is_allow_move_editor = true;
}
editor.onmouseup = function (e) {
is_allow_move_editor = false;
}
document.getElementById('update-text').onclick = function () {
if (!editor.value.length) {
alert('请先输入文字');
return;
}
editor.style.display = 'none';
ctx.beginPath();
ctx.font = `bold ${paint_size.value * 10}px ${font.value}`;
ctx.fillStyle = paint_color.value;
const { x, y } = get_position(canvas, parseInt(editor.style.left), parseInt(editor.style.top) + paint_size.value * 10);
ctx.fillText(editor.value, x, y);
}
document.getElementById('screensize').onchange = function () {
const size = document.getElementById('screensize').value;
console.log(size)
width = parseInt(size.split('*')[0])
height = parseInt(size.split('*')[1])
document.getElementById('canvas').width = width;
document.getElementById('canvas').height = height;
}
image_mode.onchange = function (e) {
if (image_mode.value === 'font') {
document.getElementById('update-text').style.display = 'inline-block';
document.getElementById('font').style.display = 'inline-block';
editor.style.display = 'block';
editor.style.left = `${e.clientX}px`;
editor.style.top = `${e.clientY}px`;
return;
}
document.getElementById('update-text').style.display = 'none';
document.getElementById('font').style.display = 'none';
editor.style.display = 'none';
}
paint_size.onchange = function () {
if (image_mode.value === 'font') {
editor.style.fontSize = `${paint_size.value * 10}px`;
}
}
paint_color.onchange = function () {
if (image_mode.value === 'font') {
editor.style.color = paint_color.value;
}
}
font.onchange = function () {
if (image_mode.value === 'font') {
editor.style.fontFamily = font.value;
}
}
canvas.onmousedown = function (e) {
let ele = get_position(canvas, e.clientX, e.clientY)
let { x, y } = ele
switch (image_mode.value) {
case 'paint':
is_allow_drawing = true;
ctx.beginPath();
ctx.moveTo(x, y);
break;
case 'font':
editor.style.display = 'block';
editor.style.left = `${e.clientX}px`;
editor.style.top = `${e.clientY}px`;
editor.style.fontSize = `${paint_size.value * 10}px`;
editor.style.color = paint_color.value;
editor.style.fontFamily = font.value;
editor.style.fontWeight = 'bold';
break
default:
break;
}
};
canvas.onmousemove = (e) => {
let ele = get_position(canvas, e.clientX, e.clientY)
let { x, y } = ele;
switch (image_mode.value) {
case 'paint':
if (is_allow_drawing) {
ctx.lineWidth = paint_size.value;
ctx.strokeStyle = paint_color.value;
ctx.lineTo(x, y);
ctx.stroke();
}
break;
case 'font':
break;
default:
break;
}
}
canvas.onmouseup = function () {
switch (image_mode.value) {
case 'paint':
is_allow_drawing = false;
break;
case 'font':
editor.focus();
is_allow_move_editor = false;
break;
default:
break;
}
}
canvas.onmouseleave = function () {
if (image_mode.value === 'paint') {
is_allow_drawing = false;
}
}
}
</script>
</body>
</html>
</html>