feat: add support for render BWR

This commit is contained in:
reece
2022-06-26 16:42:14 +08:00
parent 09168baa53
commit ce37997642
11 changed files with 332 additions and 126 deletions

Binary file not shown.

View File

@@ -7,7 +7,7 @@
#include "epd_bw_213.h"
#include "epd_bwr_213.h"
#include "epd_bw_213_ice.h"
#include "epd_bwr_154.h"
//#include "epd_bwr_154.h"
#include "epd_bwr_296.h"
#include "drivers.h"
#include "stack/ble/ble.h"
@@ -82,10 +82,10 @@ _attribute_ram_code_ void EPD_detect_model(void)
{
epd_model = 2;
}
else if (EPD_BWR_154_detect())// Right now this will never trigger, the 154 is same to 213BWR right now.
{
epd_model = 3;
}
// else if (EPD_BWR_154_detect())// Right now this will never trigger, the 154 is same to 213BWR right now.
// {
// epd_model = 3;
// }
else if (EPD_BW_213_ice_detect())
{
epd_model = 4;
@@ -120,8 +120,8 @@ _attribute_ram_code_ uint8_t EPD_read_temp(void)
epd_temperature = EPD_BW_213_read_temp();
else if (epd_model == 2)
epd_temperature = EPD_BWR_213_read_temp();
else if (epd_model == 3)
epd_temperature = EPD_BWR_154_read_temp();
// else if (epd_model == 3)
// epd_temperature = EPD_BWR_154_read_temp();
else if (epd_model == 4 || epd_model == 5)
epd_temperature = EPD_BW_213_ice_read_temp();
@@ -132,7 +132,7 @@ _attribute_ram_code_ uint8_t EPD_read_temp(void)
return epd_temperature;
}
_attribute_ram_code_ void EPD_Display(unsigned char *image, int size, uint8_t full_or_partial)
_attribute_ram_code_ void EPD_Display(unsigned char *image, unsigned char *red_image, int size, uint8_t full_or_partial)
{
if (!epd_model)
EPD_detect_model();
@@ -151,12 +151,13 @@ _attribute_ram_code_ void EPD_Display(unsigned char *image, int size, uint8_t fu
epd_temperature = EPD_BW_213_Display(image, size, full_or_partial);
else if (epd_model == 2)
epd_temperature = EPD_BWR_213_Display(image, size, full_or_partial);
else if (epd_model == 3)
epd_temperature = EPD_BWR_154_Display(image, size, full_or_partial);
// else if (epd_model == 3)
// epd_temperature = EPD_BWR_154_Display(image, size, full_or_partial);
else if (epd_model == 4)
epd_temperature = EPD_BW_213_ice_Display(image, size, full_or_partial);
else if (epd_model == 5)
epd_temperature = EPD_BWR_296_Display(image, size, full_or_partial);
epd_temperature = EPD_BWR_296_Display_BWR(image, red_image, size, full_or_partial);
//epd_temperature = EPD_BWR_296_Display(image, size, full_or_partial);
epd_temperature_is_read = 1;
epd_update_state = 1;
@@ -171,8 +172,8 @@ _attribute_ram_code_ void epd_set_sleep(void)
EPD_BW_213_set_sleep();
else if (epd_model == 2)
EPD_BWR_213_set_sleep();
else if (epd_model == 3)
EPD_BWR_154_set_sleep();
// else if (epd_model == 3)
// EPD_BWR_154_set_sleep();
else if (epd_model == 4 || epd_model == 5)
EPD_BW_213_ice_set_sleep();
@@ -248,12 +249,12 @@ _attribute_ram_code_ void TIFFDraw(TIFFDRAW *pDraw)
_attribute_ram_code_ void epd_display_tiff(uint8_t *pData, int iSize)
{
// test G4 decoder
memset(epd_buffer, 0xff, epd_buffer_size); // clear to white
epd_clear();
TIFF_openRAW(&tiff, 250, 122, BITDIR_MSB_FIRST, pData, iSize, TIFFDraw);
TIFF_setDrawParameters(&tiff, 65536, TIFF_PIXEL_1BPP, 0, 0, 250, 122, NULL);
TIFF_decode(&tiff);
TIFF_close(&tiff);
EPD_Display(epd_buffer, epd_buffer_size, 1);
EPD_Display(epd_buffer, NULL, epd_buffer_size, 1);
}
extern uint8_t mac_public[6];
@@ -296,6 +297,8 @@ _attribute_ram_code_ void epd_display(struct date_time _time, uint16_t battery_m
resolution_h = 128;
}
epd_clear();
obdCreateVirtualDisplay(&obd, resolution_w, resolution_h, epd_temp);
obdFill(&obd, 0, 0); // fill with white
@@ -312,7 +315,7 @@ _attribute_ram_code_ void epd_display(struct date_time _time, uint16_t battery_m
sprintf(buff, "Battery %dmV %d%%", battery_mv, battery_level);
obdWriteStringCustom(&obd, (GFXfont *)&Dialog_plain_16, 10, 120, (char *)buff, 1);
FixBuffer(epd_temp, epd_buffer, resolution_w, resolution_h);
EPD_Display(epd_buffer, resolution_w * resolution_h / 8, full_or_partial);
EPD_Display(epd_buffer, NULL, resolution_w * resolution_h / 8, full_or_partial);
}
_attribute_ram_code_ void epd_display_char(uint8_t data)
@@ -322,12 +325,13 @@ _attribute_ram_code_ void epd_display_char(uint8_t data)
{
epd_buffer[i] = data;
}
EPD_Display(epd_buffer, epd_buffer_size, 1);
EPD_Display(epd_buffer, NULL, epd_buffer_size, 1);
}
_attribute_ram_code_ void epd_clear(void)
{
memset(epd_buffer, 0x00, epd_buffer_size);
memset(epd_temp, 0x00, epd_buffer_size);
}
void update_time_scene(struct date_time _time, uint16_t battery_mv, int16_t temperature, void (*scene)(struct date_time, uint16_t, int16_t, uint8_t)) {
// default scene: show default time, battery, ble address, temperature
@@ -375,6 +379,8 @@ void epd_update(struct date_time _time, uint16_t battery_mv, int16_t temperature
void epd_display_time_with_date(struct date_time _time, uint16_t battery_mv, int16_t temperature, uint8_t full_or_partial) {
uint16_t battery_level;
epd_clear();
obdCreateVirtualDisplay(&obd, epd_width, epd_height, epd_temp);
obdFill(&obd, 0, 0); // fill with white
@@ -408,5 +414,6 @@ void epd_display_time_with_date(struct date_time _time, uint16_t battery_mv, int
FixBuffer(epd_temp, epd_buffer, epd_width, epd_height);
EPD_Display(epd_buffer, epd_width * epd_height / 8, full_or_partial);
EPD_Display(epd_buffer, NULL, epd_width * epd_height / 8, full_or_partial);
}

View File

@@ -10,7 +10,7 @@ void set_EPD_wait_flush();
void init_epd(void);
uint8_t EPD_read_temp(void);
void EPD_Display(unsigned char *image, int size, uint8_t full_or_partial);
void EPD_Display(unsigned char *image, unsigned char * red_image, int size, uint8_t full_or_partial);
void epd_display_tiff(uint8_t *pData, int iSize);
void epd_set_sleep(void);
uint8_t epd_state_handler(void);

View File

@@ -7,7 +7,7 @@
#include "epd.h"
#include "ble.h"
extern uint8_t *epd_temp;
extern uint8_t epd_temp[epd_buffer_size];
#define ASSERT_MIN_LEN(val, min_len) \
if (val < min_len) \
@@ -31,14 +31,15 @@ int epd_ble_handle_write(void *p)
{
// Clear EPD display.
case 0x00:
ASSERT_MIN_LEN(payload_len, 2);
memset(epd_buffer, payload[1], sizeof(epd_buffer));
ASSERT_MIN_LEN(payload_len, 2);
memset(epd_buffer, payload[1], epd_buffer_size);
memset(epd_temp, payload[1], epd_buffer_size);
ble_set_connection_speed(40);
return 0;
// Push buffer to display.
case 0x01:
ble_set_connection_speed(200);
EPD_Display(epd_buffer, epd_buffer_size, 1);
EPD_Display(epd_buffer, epd_temp, epd_buffer_size, payload[1]);
return 0;
// Set byte_pos.
case 0x02:
@@ -47,14 +48,18 @@ int epd_ble_handle_write(void *p)
return 0;
// Write data to image buffer.
case 0x03:
if ((payload[1] << 8 | payload[2]) + payload_len - 3 >= epd_buffer_size + 1)
if ((payload[2] << 8 | payload[3]) + payload_len - 4 >= 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 + (payload[1] << 8 | payload[2]), payload + 3, payload_len - 3);
if (payload[1] == 0xff) {
memcpy(epd_buffer + (payload[2] << 8 | payload[3]), payload + 4, payload_len - 4);
} else {
memcpy(epd_temp + (payload[2] << 8 | payload[3]), payload + 4, payload_len - 4);
}
out_buffer[0] = payload_len >> 8;
out_buffer[1] = payload_len & 0xff;

View File

@@ -257,6 +257,125 @@ _attribute_ram_code_ uint8_t EPD_BWR_296_Display(unsigned char *image, int size,
return epd_temperature;
}
_attribute_ram_code_ uint8_t EPD_BWR_296_Display_BWR(unsigned char *image, unsigned char *red_image, int size, uint8_t full_or_partial) {
if (red_image == NULL) {
return EPD_BWR_296_Display(image, size, full_or_partial);
}
uint8_t epd_temperature = 0 ;
// SW Reset
EPD_WriteCmd(0x12);
EPD_CheckStatus_inverted(100);
// Set Analog Block control
EPD_WriteCmd(0x74);
EPD_WriteData(0x54);
// Set Digital Block control
EPD_WriteCmd(0x7E);
EPD_WriteData(0x3B);
// Booster soft start
EPD_WriteCmd(0x0C);
EPD_WriteData(0x8B);
EPD_WriteData(0x9C);
EPD_WriteData(0x96);
EPD_WriteData(0x0F);
// Driver output control
EPD_WriteCmd(0x01);
EPD_WriteData(0x28);
EPD_WriteData(0x01);
EPD_WriteData(0x01);
// Data entry mode setting
EPD_WriteCmd(0x11);
EPD_WriteData(0x01);
// Set RAM X- Address Start/End
EPD_WriteCmd(0x44);
EPD_WriteData(0x00);
EPD_WriteData(0x0F);
// Set RAM Y- Address Start/End
EPD_WriteCmd(0x45);
EPD_WriteData(0x28); //0x0127-->(295+1)=296
EPD_WriteData(0x01);
EPD_WriteData(0x00);
EPD_WriteData(0x00);
// Border waveform control
EPD_WriteCmd(0x3C);
EPD_WriteData(0x05);
// Display update control
EPD_WriteCmd(0x21);
EPD_WriteData(0x00);
EPD_WriteData(0x80);
// Temperature sensor control
EPD_WriteCmd(0x18);
EPD_WriteData(0x80);
// Display update control
EPD_WriteCmd(0x22);
EPD_WriteData(0xB1);
// Master Activation
EPD_WriteCmd(0x20);
EPD_CheckStatus_inverted(100);
// Temperature sensor read from register
EPD_WriteCmd(0x1B);
epd_temperature = EPD_SPI_read();
EPD_SPI_read();
WaitMs(5);
// Set RAM X address
EPD_WriteCmd(0x4E);
EPD_WriteData(0x00);
// Set RAM Y address
EPD_WriteCmd(0x4F);
EPD_WriteData(0x28);
EPD_WriteData(0x01);
EPD_LoadImage(image, size, 0x24);
// Set RAM X address
EPD_WriteCmd(0x4E);
EPD_WriteData(0x00);
// Set RAM Y address
EPD_WriteCmd(0x4F);
EPD_WriteData(0x28);
EPD_WriteData(0x01);
EPD_LoadImage(red_image, size, 0x26);
int i;
if (!full_or_partial)
{
EPD_WriteCmd(0x32);
for (i = 0; i < sizeof(LUT_bwr_296_part); i++)
{
EPD_WriteData(LUT_bwr_296_part[i]);
}
}
// Display update control
EPD_WriteCmd(0x22);
EPD_WriteData(0xC7);
// Master Activation
EPD_WriteCmd(0x20);
return epd_temperature;
}
_attribute_ram_code_ void EPD_BWR_296_set_sleep(void)
{
// deep sleep

View File

@@ -3,4 +3,5 @@
uint8_t EPD_BWR_296_detect(void);
uint8_t EPD_BWR_296_read_temp(void);
uint8_t EPD_BWR_296_Display(unsigned char *image, int size, uint8_t full_or_partial);
uint8_t EPD_BWR_296_Display_BWR(unsigned char *image, unsigned char *red_image, int size, uint8_t full_or_partial);
void EPD_BWR_296_set_sleep(void);

View File

@@ -14,7 +14,6 @@ $(OUT_PATH)/epd_bw_213.o \
$(OUT_PATH)/epd_bwr_296.o \
$(OUT_PATH)/epd_bwr_213.o \
$(OUT_PATH)/epd_bw_213_ice.o \
$(OUT_PATH)/epd_bwr_154.o \
$(OUT_PATH)/ota.o \
$(OUT_PATH)/led.o \
$(OUT_PATH)/uart.o \

View File

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

View File

@@ -19,6 +19,7 @@
}
body {
padding: 20px;
overflow-y: hidden;
}
</style>
<script type="application/javascript" src="js/dithering.js"></script>
@@ -112,27 +113,36 @@
addLog(`上传tiff完成耗时${(new Date().getTime() - startTime)/1000}s`);
}
async function sendBufferData(value, type) {
addLog(`开始发送图片模式:${type}, 大小 ${value.length/2/1024}KB`);
let code = 'ff';
if (type === 'bwr') {
code = '00';
}
const step = 480;
let partIndex = 0;
for (let i = 0; i < value.length; i += step) {
addLog(`正在发送第${partIndex+1}块. 块大小: ${step/2 + 4}byte. 起始位置: ${i/2}`);
await sendCommand(hexToBytes("03" + code + intToHex(i / 2, 2) + value.substring(i, i + step)));
partIndex += 1;
}
}
async function upload_image() {
const canvas = document.getElementById('canvas');
const value = bytesToHex(canvas2bytes(canvas));
const startTime = new Date().getTime();
addLog(`开始刷新屏幕内存, 大小 ${value.length/2/1024}KB`);
await sendCommand(hexToBytes("0000"));
await sendCommand(hexToBytes("020000"));
const step = 480;
let partIndex = 0;
for (let i = 0; i < value.length; i += step) {
addLog(`正在发送第${partIndex+1}块. 块大小: ${step/2 + 3}byte. 起始位置: ${i/2}`);
await sendCommand(hexToBytes("03" + intToHex(i / 2, 2) + value.substring(i, i + step)));
partIndex += 1;
}
await sendBufferData(bytesToHex(canvas2bytes(canvas)), 'bw')
await sendBufferData(bytesToHex(canvas2bytes(canvas, 'bwr')), 'bwr')
await sendCommand(hexToBytes("01"))
await sendCommand(hexToBytes("0101"))
addLog(`刷新完成,耗时${(new Date().getTime() - startTime)/1000}s`);
}
@@ -147,16 +157,11 @@
await sendCommand(hexToBytes("020000"));
const step = 480;
let partIndex = 0;
for (let i = 0; i < value.length; i += step) {
addLog(`正在发送第${partIndex+1}块. 块大小: ${step/2 + 3}byte. 起始位置: ${i/2}`);
await sendCommand(hexToBytes("03" + intToHex(i / 2, 2) + value.substring(i, i + step)));
partIndex += 1;
}
await sendBufferData(value, 'bw');
await delay(150);
await sendCommand(hexToBytes("01"))
await sendCommand(hexToBytes("0101"))
addLog(`刷新完成,耗时${(new Date().getTime() - startTime)/1000}s`);
}
@@ -301,14 +306,20 @@
if(confirm('确认清除屏幕?')) {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
}
function convert_dithering() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext("2d");
dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), document.getElementById('dithering').value);
const mode = document.getElementById('dithering').value;
if (mode.startsWith('bwr')) {
ditheringCanvasByPalette(canvas, bwrPalette, mode);
} else {
dithering(ctx, canvas.width, canvas.height, parseInt(document.getElementById('threshold').value), mode);
}
}
document.body.onload = () => {
@@ -318,6 +329,9 @@
}, 1000);
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;
@@ -326,7 +340,7 @@
const paint_color = document.getElementById('paint-color');
const editor = document.getElementById('edit-font');
const font = document.getElementById('font');
document.getElementById('dithering').value = 'Atkinson';
document.getElementById('dithering').value = 'bwr_Atkinson';
image_mode.value = 'paint';
paint_color.value = 'black';
font.value = '黑体';
@@ -335,6 +349,7 @@
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);
@@ -342,8 +357,8 @@
return;
}
editor.style.left = `${e.clientX-50}px`;
editor.style.top = `${e.clientY-50}px`;
editor.style.left = `${e.clientX-20}px`;
editor.style.top = `${e.clientY-20}px`;
}
}
@@ -362,9 +377,8 @@
return;
}
editor.style.display = 'none';
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.font = `${paint_size.value * 10}px ${font.value}`;
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);
@@ -407,7 +421,6 @@
canvas.onmousedown = function(e) {
let ele = get_position(canvas, e.clientX, e.clientY)
let { x, y } = ele
const ctx = canvas.getContext("2d");
switch (image_mode.value) {
case 'paint':
@@ -416,14 +429,13 @@
ctx.moveTo(x, y);
break;
case 'font':
if (editor.style.display === 'none') {
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.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:
@@ -432,7 +444,6 @@
};
canvas.onmousemove = (e) => {
const ctx = canvas.getContext("2d");
let ele = get_position(canvas, e.clientX, e.clientY)
let { x, y } = ele;
switch (image_mode.value) {
@@ -475,9 +486,9 @@
}
}
</script>
<h2>电子价签蓝牙控制器</h2>
<a href="/uart_flasher.html">串口升级</a><a href="/ATC_TLSR_Paper%20BLE%20control.html">串口升级</a>
<br>
<h2>电子价签蓝牙控制器<a style="margin-left: 130px;" href="/uart_flasher.html">串口升级</a><a href="/ATC_TLSR_Paper%20BLE%20control.html">蓝牙OTA升级</a>
</h2>
<button id="connectbutton" type="button" onclick="preConnect();">链接</button>
<button type="button" onclick="reConnect();">重新链接</button>
@@ -488,7 +499,6 @@
<input type="text" id="cmdTXT" value="0055">
<button type="button" onclick="triggerEpdCmd(document.getElementById(&quot;cmdTXT&quot;).value);">发送指令</button>
<br>
<br>
<button type="button" onclick="triggerRxTxCmd('e100')" title="点击该按钮,然后上传图片。图片将永久显示到屏幕上">设置为图片模式</button>
<button type="button" onclick="triggerRxTxCmd('e101')" title="点击该按钮, 切换为时钟模式">设置为时钟模式1</button>
<button type="button" onclick="triggerRxTxCmd('e102')" title="点击该按钮, 切换为时钟模式">设置为时钟模式2</button>
@@ -497,13 +507,19 @@
<br><br>
<h3>上传图片到屏幕</h3>
<input type="file" id="image_file" onchange="update_image()">
<input type="file" id="image_file" onchange="update_image()" accept=".png,.jpg,.bmp,.webp">
抖动算法:
<select id="dithering" title="抖动算法">
<option value="none">二值化</option>
<option value="bayer">bayer</option>
<option value="floydsteinberg">floydsteinberg</option>
<option value="Atkinson">Atkinson</option>
<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>
</select>
阈值:
<input type="number" max="255" min="0" value="125" id="threshold">
@@ -511,7 +527,7 @@
<br>
<div id="canvas-box">
<div id="tool-box" style="margin-bottom: 5px; margin-top: 10px;">
<div id="tool-box">
模式:
<select id="canvas-mode">
<option value="paint">画笔</option>
@@ -532,12 +548,11 @@
<option value="仿宋">仿宋</option>
<option value="宋体">宋体</option>
<option value="楷体_GB2312">楷体_GB2312</option>
<option value="仿宋_GB2312">仿宋_GB2312</option>
<option value="华文行楷">华文行楷</option>
</select>
<button id="update-text" style="display: none">保存文本框</button>
<button onclick="clear_canvas()">清屏</button>
</div>
<br>
<input id="edit-font" style="max-width: 296px; position: absolute; border: black solid 1px;background-color: rgba(0,0,0,0);display: none;overflow: auto" />
<canvas id="canvas" width="296" height="128" style="border: black solid 1px;"></canvas>
<br>

View File

@@ -1,39 +1,14 @@
const bwrPalette = [
[0, 0, 0, 0],
[255, 255, 255, 0],
[255, 0, 0, 0]
[0, 0, 0, 255],
[255, 255, 255, 255],
[255, 0, 0, 255]
]
const bwPalette = [
[0, 0, 0, 0],
[255, 255, 255, 0],
[0, 0, 0, 255],
[255, 255, 255, 255],
]
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 ],
@@ -104,7 +79,7 @@ function dithering(ctx, width, height, threshold, type) {
ctx.putImageData(imageData, 0, 0);
}
function canvas2bytes(canvas, rotate=1) {
function canvas2bytes(canvas, type='bw') {
const ctx = canvas.getContext("2d");
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
@@ -114,7 +89,12 @@ function canvas2bytes(canvas, rotate=1) {
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 (type !== 'bwr') {
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] > 0 && imageData.data[index+2] > 0 ? 1 : 0);
} else {
buffer.push(imageData.data[index] > 0 && imageData.data[index+1] === 0 && imageData.data[index+2] === 0 ? 1 : 0);
}
if (buffer.length === 8) {
arr.push(parseInt(buffer.join(''), 2));
buffer = [];
@@ -124,25 +104,105 @@ function canvas2bytes(canvas, rotate=1) {
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)
}
}
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 scaled;
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);
}