feat: add image uploader

This commit is contained in:
reece
2022-06-26 01:54:00 +08:00
parent 53119b9acc
commit 09168baa53
11 changed files with 494 additions and 51 deletions

Binary file not shown.

View File

@@ -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;

View File

@@ -63,9 +63,11 @@ Firmware CRC32: 0xe62d501e
- [x] 添加蓝牙上传图片后notify
- [x] 添加场景且支持切换
- [x] 图片模式
- [ ] web 支持图片切换
- [x] web 支持图片切换
- [x] 添加新的时间场景
- [x] 支持设置年月日
- [x] web 支持画图编辑,直接上传图片,抖动算法
- [ ] 三色抖动算法、设备端三色显示支持,蓝牙传输支持
### 计划新增
- [ ] 安卓端控制器

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1 @@
Pillow==9.1.0

View File

@@ -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))

View File

@@ -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
View 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
View 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');
}