Skip to content

Commit

Permalink
4 color grayscale support (samsonmking#13)
Browse files Browse the repository at this point in the history
* Implemented 4 Color Grayscale
* Add Waveshare 4.2" horizontal and vertical grayscale configurations
  • Loading branch information
webphax authored Nov 25, 2020
1 parent c7848eb commit 173b7e4
Show file tree
Hide file tree
Showing 8 changed files with 477 additions and 106 deletions.
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ init(devices.waveshare4in2, {
websocketPort: 8080, // WebSocket API Port
staticDirectory: 'static', // Directory to serve frontend from
url: `http://localhost:3000/index.html` // Initial URL to load
};)
});
```

**Extend the server side WebSocket API**
Expand Down Expand Up @@ -132,9 +132,8 @@ sudo apt-get install -y build-essential chromium-browser

**Node.js**\
Install ePaper.js

```bash
npm install -s epaperjs
``` bash
npm install -S epaperjs
```

## Supported Hardware
Expand All @@ -151,10 +150,9 @@ It's easy to extend ePaper.js to support additional Waveshare devices. Displays
If you would like to request support for another display, please open an issue with the title 'Add support for <Device Make \ Model>'. If you're a developer and have extended support yourself, put up a pull request!

## Feature Backlog

- [x] Add support for portrait or landscape display (rotate 90 deg)
- [ ] Add support for remaining Waveshare SPI ePaper displays
- [ ] Implement 4 Color Grayscale
- [ ] Implement Red / White / Black Color Mode
- [ ] Implement Yellow / White / Black Color Mode
- [ ] Implement Partial Refresh
- [x] Add support for portrait or landscape display (rotate 90 deg)
- [ ] Add support for remaining Waveshare SPI ePaper displays
- [x] Implement 4 Color Grayscale
- [ ] Implement Red / White / Black Color Mode
- [ ] Implement Yellow / White / Black Color Mode
- [ ] Implement Partial Refresh
17 changes: 17 additions & 0 deletions c/EPD_4in2_node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,26 @@ Napi::Value Init(const Napi::CallbackInfo& info) {
return env.Undefined();
}

Napi::Value Init_4Gray(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
EPD_4IN2_Init_4Gray();
return env.Undefined();
}

Napi::Value Display(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Buffer<uint8_t> jsBuffer = info[0].As<Napi::Buffer<uint8_t>>();
EPD_4IN2_Display(reinterpret_cast<uint8_t *>(jsBuffer.Data()));
return env.Undefined();
}

Napi::Value Display_4GrayDisplay(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Buffer<uint8_t> jsBuffer = info[0].As<Napi::Buffer<uint8_t>>();
EPD_4IN2_4GrayDisplay(reinterpret_cast<uint8_t *>(jsBuffer.Data()));
return env.Undefined();
}

Napi::Value Clear(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
EPD_4IN2_Clear();
Expand All @@ -40,8 +53,12 @@ Napi::Object SetupNapi(Napi::Env env, Napi::Object exports) {
Napi::Function::New(env, DEV_Init));
exports.Set(Napi::String::New(env, "init"),
Napi::Function::New(env, Init));
exports.Set(Napi::String::New(env, "init_4Gray"),
Napi::Function::New(env, Init));
exports.Set(Napi::String::New(env, "display"),
Napi::Function::New(env, Display));
exports.Set(Napi::String::New(env, "display_4GrayDisplay"),
Napi::Function::New(env, Display_4GrayDisplay));
exports.Set(Napi::String::New(env, "clear"),
Napi::Function::New(env, Clear));
exports.Set(Napi::String::New(env, "sleep"),
Expand Down
56 changes: 48 additions & 8 deletions devices.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { convertPNGto1BitBW, convertPNGto1BitBWRotated } = require('./image.js');
const image = require('./image.js');
const waveshare4In2Driver = require('bindings')('waveshare4in2.node');
const waveshare7in5v2Driver = require('bindings')('waveshare7in5v2.node');

Expand All @@ -7,50 +7,90 @@ const waveshare4in2Horizontal = {
width: 400,
driver: waveshare4In2Driver,
displayPNG: async function (imgContents) {
const buffer = await convertPNGto1BitBW(imgContents);
const buffer = await image.convertPNGto1BitBW(imgContents);
this.driver.display(buffer);
},
init: function () {
this.driver.init();
}
};

const waveshare4in2Vertical = {
height: 400,
width: 300,
driver: waveshare4In2Driver,
displayPNG: async function (imgContents) {
const buffer = await convertPNGto1BitBWRotated(imgContents);
this.driver.display(buffer);
const buffer = await image.convertPNGto1BitBWRotated(imgContents);
this.driver.display(buffer)
},
init: function () {
this.driver.init();
}
};

const waveshare4in2HorizontalGray = {
height: 300,
width: 400,
driver: waveshare4In2Driver,
displayPNG: async function (imgContents) {
const buffer = await image.convertPNGto1Bit4Grey(imgContents);
this.driver.display_4GrayDisplay(buffer)
},
init: function () {
this.driver.init_4Gray();
}
};

const waveshare4in2VerticalGray = {
height: 400,
width: 300,
driver: waveshare4In2Driver,
displayPNG: async function (imgContents, color_depth) {
const buffer = await image.convertPNGto1Bit4GreyRotated(imgContents);
this.driver.display_4GrayDisplay(buffer)
},
init: function () {
this.driver.init_4Gray();
}
};

const waveshare7in5v2Horizontal = {
height: 480,
width: 800,
driver: waveshare7in5v2Driver,
displayPNG: async function (imgContents) {
const buffer = await convertPNGto1BitBW(imgContents);
const buffer = await convertPNG(imgContents);
this.driver.display(buffer);
},
init: function () {
this.driver.init();
}
};

const waveshare7in2v2Vertical = {
height: 800,
width: 480,
driver: waveshare7in5v2Driver,
displayPNG: async function (imgContents) {
const buffer = await convertPNGto1BitBWRotated(imgContents);
const buffer = await convertPNG(imgContents);
this.driver.display(buffer);
},
init: function () {
this.driver.init();
}
};

const devices = {
// default waveshare4in2 kept for backwards compatibility with release 1.0.0
waveshare4in2: waveshare4in2Horizontal,
waveshare4in2Horizontal,
waveshare4in2HorizontalGray,
waveshare4in2Vertical,
waveshare4in2VerticalGray,
// default waveshare7in5v2 kept for backwards compatibility with releaes 1.1.0
waveshare7in5v2: waveshare7in5v2Horizontal,
waveshare7in5v2Horizontal,
waveshare7in2v2Vertical,
};
waveshare7in2v2Vertical
}

module.exports = devices;
94 changes: 88 additions & 6 deletions image.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const PNGReader = require('png.js');
const sharp = require('sharp');

// https://www.w3.org/TR/AERT/#color-contrast
const getLuma = (r, g, b) => r * 0.299 + g * 0.587 + b * 0.114;
const allocBuffer = (devWidth, devHeight) =>
Buffer.alloc(Math.ceil(devWidth / 8) * devHeight, 0xff);
const getLuma = (r, g, b) => (r * 0.299) + (g * 0.587) + (b * 0.114);
const allocBuffer_8 = (devWidth, devHeight) => Buffer.alloc(Math.ceil(devWidth / 8) * devHeight, 0xff);
const allocBuffer_4 = (devWidth, devHeight) => Buffer.alloc(Math.ceil(devWidth / 4) * devHeight, 0xff);

function convertPNGto1BitBW(pngBytes) {
const reader = new PNGReader(pngBytes);
Expand All @@ -14,7 +15,7 @@ function convertPNGto1BitBW(pngBytes) {
}
const height = png.getHeight();
const width = png.getWidth();
const outBuffer = allocBuffer(width, height);
const outBuffer = allocBuffer_8(width, height);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const [r, g, b, alpha] = png.getPixel(x, y);
Expand All @@ -41,7 +42,7 @@ function convertPNGto1BitBWRotated(pngBytes) {
const width = png.getWidth();
const devHeight = width;
const devWidth = height;
const outBuffer = allocBuffer(devWidth, devHeight);
const outBuffer = allocBuffer_8(devWidth, devHeight);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const outX = y;
Expand All @@ -59,4 +60,85 @@ function convertPNGto1BitBWRotated(pngBytes) {
});
}

module.exports = { convertPNGto1BitBW, convertPNGto1BitBWRotated };
async function convertPNGto1Bit4Grey(pngBytes) {
const pngBytes_L = await sharp(pngBytes).greyscale().png().toBuffer();
const reader = new PNGReader(pngBytes_L);
return new Promise((resolve, reject) => {
reader.parse((err, png) => {
if (err) {
return reject(err);
}
const height = png.getHeight();
const width = png.getWidth();
const outBuffer = allocBuffer_4(width, height);
var i = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
i++;
if (i % 4 == 0) {
out_index = Math.floor((x + y * width) / 4)
outBuffer[out_index] = (
(RGBAToHex(png.getPixel(x - 3, y)) & 0xc0) |
(RGBAToHex(png.getPixel(x - 2, y)) & 0xc0) >> 2 |
(RGBAToHex(png.getPixel(x - 1, y)) & 0xc0) >> 4 |
(RGBAToHex(png.getPixel(x, y)) & 0xc0) >> 6
)
}
}
}
resolve(outBuffer);
});
});
}

async function convertPNGto1Bit4GreyRotated(pngBytes) {
const pngBytes_L = await sharp(pngBytes).greyscale().png().toBuffer();
const reader = new PNGReader(pngBytes_L);
return new Promise((resolve, reject) => {
reader.parse((err, png) => {
if (err) {
return reject(err);
}
const height = png.getHeight();
const width = png.getWidth();
const devHeight = width;
const devWidth = height;
const outBuffer = allocBuffer_4(devWidth, devHeight);
var i = 0;
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const outX = y;
const outY = devHeight - x - 1;
i++;
if (i % 4 == 0) {
out_index = Math.floor((outX + outY * devWidth) / 4)
outBuffer[out_index] = (
(RGBAToHex(png.getPixel(x, y - 3)) & 0xc0) |
(RGBAToHex(png.getPixel(x, y - 2)) & 0xc0) >> 2 |
(RGBAToHex(png.getPixel(x, y - 1)) & 0xc0) >> 4 |
(RGBAToHex(png.getPixel(x, y)) & 0xc0) >> 6
)
}
}
}
resolve(outBuffer);
});
});
}

function RGBAToHex(rgba) {
let r = (+rgba[0]).toString(16),
g = (+rgba[1]).toString(16),
b = (+rgba[2]).toString(16)

if (r.length == 1)
r = "0" + r;
if (g.length == 1)
g = "0" + g;
if (b.length == 1)
b = "0" + b;

return Number("0x" + r + g + b);
}

module.exports = { convertPNGto1BitBW, convertPNGto1BitBWRotated, convertPNGto1Bit4Grey, convertPNGto1Bit4GreyRotated };
Loading

0 comments on commit 173b7e4

Please sign in to comment.