mirror of
https://github.com/RoCry/blozi-etag.git
synced 2025-12-06 09:02:49 +08:00
feat: add image uploader
This commit is contained in:
Binary file not shown.
@@ -15,7 +15,7 @@ extern uint8_t *epd_temp;
|
||||
return 0; \
|
||||
}
|
||||
|
||||
extern unsigned char epd_buffer[epd_buffer_size];
|
||||
extern uint8_t epd_buffer[epd_buffer_size];
|
||||
unsigned int byte_pos = 0;
|
||||
|
||||
int epd_ble_handle_write(void *p)
|
||||
@@ -47,16 +47,14 @@ int epd_ble_handle_write(void *p)
|
||||
return 0;
|
||||
// Write data to image buffer.
|
||||
case 0x03:
|
||||
if (byte_pos + payload_len - 1 >= epd_buffer_size + 1)
|
||||
if ((payload[1] << 8 | payload[2]) + payload_len - 3 >= epd_buffer_size + 1)
|
||||
{
|
||||
out_buffer[0] = 0x00;
|
||||
out_buffer[1] = 0x00;
|
||||
bls_att_pushNotifyData(EPD_BLE_CMD_OUT_DP_H, out_buffer, 2);
|
||||
return 0;
|
||||
}
|
||||
memcpy(epd_buffer + byte_pos, payload + 1, payload_len - 1);
|
||||
|
||||
byte_pos += payload_len - 1;
|
||||
memcpy(epd_buffer + (payload[1] << 8 | payload[2]), payload + 3, payload_len - 3);
|
||||
|
||||
out_buffer[0] = payload_len >> 8;
|
||||
out_buffer[1] = payload_len & 0xff;
|
||||
|
||||
@@ -63,9 +63,11 @@ Firmware CRC32: 0xe62d501e
|
||||
- [x] 添加蓝牙上传图片后notify
|
||||
- [x] 添加场景且支持切换
|
||||
- [x] 图片模式
|
||||
- [ ] web 支持图片切换
|
||||
- [x] web 支持图片切换
|
||||
- [x] 添加新的时间场景
|
||||
- [x] 支持设置年月日
|
||||
- [x] web 支持画图编辑,直接上传图片,抖动算法
|
||||
- [ ] 三色抖动算法、设备端三色显示支持,蓝牙传输支持
|
||||
|
||||
### 计划新增
|
||||
- [ ] 安卓端控制器
|
||||
|
||||
BIN
tools/data/images/car-sign.png
Normal file
BIN
tools/data/images/car-sign.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1 @@
|
||||
Pillow==9.1.0
|
||||
@@ -1,8 +1,10 @@
|
||||
from PIL import Image
|
||||
from PIL.Image import Dither # noqa
|
||||
|
||||
from tools.utils import image2hex, load_test_image
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# print(image2hex(load_test_image('test-01.bmp')))
|
||||
print(image2hex(load_test_image('test-02.bmp')))
|
||||
|
||||
print(image2hex(load_test_image('mao.bmp'), dither=Dither.FLOYDSTEINBERG))
|
||||
print(image2hex(load_test_image('car-sign.png'), dither=Dither.NONE))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
from PIL.Image import Dither # noqa
|
||||
|
||||
|
||||
def hex2bytes(hex_string):
|
||||
@@ -29,11 +30,13 @@ def hex2image(hex_string, width, height):
|
||||
image.show('test')
|
||||
|
||||
|
||||
def image2hex(image, width=296, height=128):
|
||||
def image2hex(image, width=296, height=128, dither=Dither.NONE):
|
||||
if isinstance(image, str):
|
||||
image = Image.open(image)
|
||||
|
||||
return bytes2hex(image.resize((width, height)).rotate(90, expand=True).resize((height, width)).convert('1').tobytes())
|
||||
image = image.resize((width, height)).rotate(90, expand=True)
|
||||
# image.show()
|
||||
return bytes2hex(image.resize((height, width)).convert('1', dither=dither).tobytes())
|
||||
|
||||
|
||||
def load_test_image(name):
|
||||
|
||||
File diff suppressed because one or more lines are too long
148
web_tools/js/dithering.js
Normal file
148
web_tools/js/dithering.js
Normal file
@@ -0,0 +1,148 @@
|
||||
const bwrPalette = [
|
||||
[0, 0, 0, 0],
|
||||
[255, 255, 255, 0],
|
||||
[255, 0, 0, 0]
|
||||
]
|
||||
|
||||
const bwPalette = [
|
||||
[0, 0, 0, 0],
|
||||
[255, 255, 255, 0],
|
||||
]
|
||||
|
||||
function get_near_color(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 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 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 ==="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);
|
||||
}
|
||||
|
||||
function canvas2bytes(canvas, rotate=1) {
|
||||
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;
|
||||
buffer.push(imageData.data[index] > 0 ? 1 : 0);
|
||||
if (buffer.length === 8) {
|
||||
arr.push(parseInt(buffer.join(''), 2));
|
||||
buffer = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function scaleImageData(canvas, scale) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const scaled = ctx.createImageData(imageData.width * scale, imageData.height * scale);
|
||||
const subLine = ctx.createImageData(scale, 1).data
|
||||
for (let row = 0; row < imageData.height; row++) {
|
||||
for (let col = 0; col < imageData.width; col++) {
|
||||
let sourcePixel = imageData.data.subarray(
|
||||
(row * imageData.width + col) * 4,
|
||||
(row * imageData.width + col) * 4 + 4
|
||||
);
|
||||
for (let x = 0; x < scale; x++) subLine.set(sourcePixel, x*4)
|
||||
for (let y = 0; y < scale; y++) {
|
||||
let destRow = row * scale + y;
|
||||
let destCol = col * scale;
|
||||
scaled.data.set(subLine, (destRow * scaled.width + destCol) * 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scaled;
|
||||
}
|
||||
17
web_tools/js/utils.js
Normal file
17
web_tools/js/utils.js
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user