PicoGraphics: Attempt at dither and scanline interrupts.

Currently both unused and both very slow.

Dither may only be feasible for <=8 colour displays.

JPEGDEC will dither to 4-bit greyscale quite happily.
This commit is contained in:
Phil Howard 2022-06-10 00:58:54 +01:00
parent 656d69399a
commit 602d1b41dd
6 changed files with 124 additions and 11 deletions

View File

@ -8,6 +8,7 @@ namespace pimoroni {
int PicoGraphics::reset_pen(uint8_t i) {return -1;};
int PicoGraphics::create_pen(uint8_t r, uint8_t g, uint8_t b) {return -1;};
void PicoGraphics::set_pixel(const Point &p) {};
void PicoGraphics::set_pixel_dither(const Point &p, const RGB &c) {};
void PicoGraphics::scanline_convert(PenType type, conversion_callback_func callback) {};
void PicoGraphics::set_dimensions(int width, int height) {
@ -51,6 +52,21 @@ namespace pimoroni {
clip = bounds;
}
void PicoGraphics::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array<uint8_t, 16> &candidates) {
RGB error;
for(size_t i = 0; i < candidates.size(); i++) {
candidates[i] = (col + error).closest(palette, len);
error += (col - palette[candidates[i]]);
}
// sort by a rough approximation of luminance, this ensures that neighbouring
// pixels in the dither matrix are at extreme opposites of luminence
// giving a more balanced output
std::sort(candidates.begin(), candidates.end(), [palette](int a, int b) {
return palette[a].luminance() > palette[b].luminance();
});
}
void PicoGraphics::clear() {
rectangle(clip);
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <array>
#include <cstdint>
#include <algorithm>
#include <vector>
@ -24,7 +25,7 @@ namespace pimoroni {
typedef uint8_t RGB332;
typedef uint16_t RGB565;
struct RGB {
uint8_t r, g, b;
int16_t r, g, b;
constexpr RGB() : r(0), g(0), b(0) {}
constexpr RGB(RGB332 c) :
@ -37,25 +38,31 @@ namespace pimoroni {
b((__builtin_bswap16(c) & 0b0000000000011111) << 3) {}
constexpr RGB(uint8_t r, uint8_t g, uint8_t b) : r(r), g(g), b(b) {}
constexpr RGB operator+ (const RGB& c) const {return RGB(r + c.r, g + c.g, b + c.b);}
constexpr RGB& operator+=(const RGB& c) {r += c.r; g += c.g; b += c.b; return *this;}
constexpr RGB operator- (const RGB& c) const {return RGB(r - c.r, g - c.g, b - c.b);}
// a rough approximation of how bright a colour is used to compare the
// relative brightness of two colours
int luminance() {
int luminance() const {
// weights based on https://www.johndcook.com/blog/2009/08/24/algorithms-convert-color-grayscale/
return r * 21 + g * 72 * b * 7;
return r * 21 + g * 72 + b * 7;
}
// a relatively low cost approximation of how "different" two colours are
// perceived which avoids expensive colour space conversions.
// described in detail at https://www.compuphase.com/cmetric.htm
int distance(RGB c) {
int rmean = ((int)r + c.r) / 2;
int rx = (int)r - c.r, gx = (int)g - c.g, bx = (int)b - c.b;
int distance(const RGB& c) const {
int rmean = (r + c.r) / 2;
int rx = r - c.r;
int gx = g - c.g;
int bx = b - c.b;
return abs((int)(
(((512 + rmean) * rx * rx) >> 8) + 4 * gx * gx + (((767 - rmean) * bx * bx) >> 8)
));
}
int closest(const RGB *palette, size_t len) {
int closest(const RGB *palette, size_t len) const {
int d = INT_MAX, m = -1;
for(size_t i = 0; i < len; i++) {
int dc = distance(palette[i]);
@ -127,6 +134,9 @@ namespace pimoroni {
Rect clip;
typedef std::function<void(void *data, size_t length)> conversion_callback_func;
//typedef std::function<void(int y)> scanline_interrupt_func;
//scanline_interrupt_func scanline_interrupt = nullptr;
const bitmap::font_t *bitmap_font;
const hershey::font_t *hershey_font;
@ -172,6 +182,7 @@ namespace pimoroni {
virtual int reset_pen(uint8_t i);
virtual int create_pen(uint8_t r, uint8_t g, uint8_t b);
virtual void set_pixel(const Point &p);
virtual void set_pixel_dither(const Point &p, const RGB &c);
virtual void scanline_convert(PenType type, conversion_callback_func callback);
void set_font(const bitmap::font_t *font);
@ -187,6 +198,8 @@ namespace pimoroni {
void set_clip(const Rect &r);
void remove_clip();
void get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array<uint8_t, 16> &candidates);
void clear();
void pixel(const Point &p);
void pixel_span(const Point &p, int32_t l);
@ -244,6 +257,21 @@ namespace pimoroni {
*f &= m; // clear bits
*f |= b; // set value
}
void set_pixel_dither(const Point &p, const RGB &c) override {
if(!bounds.contains(p)) return;
static uint pattern[16] = // dither pattern
{0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
static std::array<uint8_t, 16> candidates;
get_dither_candidates(c, palette, 256, candidates);
// find the pattern coordinate offset
uint pattern_index = (p.x & 0b11) | ((p.y & 0b11) << 2);
// set the pixel
color = candidates[pattern[pattern_index]];
set_pixel(p);
}
void scanline_convert(PenType type, conversion_callback_func callback) override {
if(type == PEN_RGB565) {
// Cache the RGB888 palette as RGB565
@ -258,6 +286,14 @@ namespace pimoroni {
// Allocate a per-row temporary buffer
uint16_t row_buf[bounds.w];
for(auto y = 0; y < bounds.h; y++) {
/*if(scanline_interrupt != nullptr) {
scanline_interrupt(y);
// Cache the RGB888 palette as RGB565
for(auto i = 0u; i < 16; i++) {
cache[i] = palette[i].to_rgb565();
}
}*/
for(auto x = 0; x < bounds.w; x++) {
uint8_t c = src[(bounds.w * y / 2) + (x / 2)];
uint8_t o = (~x & 0b1) * 4; // bit offset within byte
@ -322,6 +358,21 @@ namespace pimoroni {
uint8_t *buf = (uint8_t *)frame_buffer;
buf[p.y * bounds.w + p.x] = color;
}
void set_pixel_dither(const Point &p, const RGB &c) override {
if(!bounds.contains(p)) return;
static uint pattern[16] = // dither pattern
{0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
static std::array<uint8_t, 16> candidates;
get_dither_candidates(c, palette, 256, candidates);
// find the pattern coordinate offset
uint pattern_index = (p.x & 0b11) | ((p.y & 0b11) << 2);
// set the pixel
color = candidates[pattern[pattern_index]];
set_pixel(p);
}
void scanline_convert(PenType type, conversion_callback_func callback) override {
if(type == PEN_RGB565) {
// Cache the RGB888 palette as RGB565
@ -376,6 +427,21 @@ namespace pimoroni {
uint8_t *buf = (uint8_t *)frame_buffer;
buf[p.y * bounds.w + p.x] = color;
}
void set_pixel_dither(const Point &p, const RGB &c) override {
if(!bounds.contains(p)) return;
static uint pattern[16] = // dither pattern
{0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
static std::array<uint8_t, 16> candidates;
get_dither_candidates(c, palette, 256, candidates);
// find the pattern coordinate offset
uint pattern_index = (p.x & 0b11) | ((p.y & 0b11) << 2);
// set the pixel
color = candidates[pattern[pattern_index]];
set_pixel(p);
}
void scanline_convert(PenType type, conversion_callback_func callback) override {
if(type == PEN_RGB565) {
// Cache the RGB888 palette as RGB565

View File

@ -29,10 +29,13 @@ typedef struct _JPEG_obj_t {
PicoGraphics *current_graphics = nullptr;
int JPEGDraw(JPEGDRAW *pDraw) {
#ifdef MICROPY_EVENT_POLL_HOOK
MICROPY_EVENT_POLL_HOOK
#endif
// "pixel" is slow and clipped,
// guaranteeing we wont draw jpeg data out of the framebuffer..
// Can we clip beforehand and make this faster?
if(pDraw->iBpp == 4) { // TODO 4-bit pixel unpacking isn't working. What's up?
if(pDraw->iBpp == 4) {
uint8_t *pixels = (uint8_t *)pDraw->pPixels;
for(int y = 0; y < pDraw->iHeight; y++) {
for(int x = 0; x < pDraw->iWidth; x++) {
@ -58,11 +61,14 @@ int JPEGDraw(JPEGDRAW *pDraw) {
for(int x = 0; x < pDraw->iWidth; x++) {
int i = y * pDraw->iWidth + x;
if (current_graphics->pen_type == PicoGraphics::PEN_RGB332) {
current_graphics->set_pen(PicoGraphics::rgb565_to_rgb332(pDraw->pPixels[i]));
current_graphics->set_pen(RGB((RGB565)pDraw->pPixels[i]).to_rgb332());
current_graphics->pixel({pDraw->x + x, pDraw->y + y});
// FIXME VERY, VERY SLOW!
//current_graphics->set_pixel_dither({pDraw->x + x, pDraw->y + y}, RGB((RGB565)(pDraw->pPixels[i])));
} else {
current_graphics->set_pen(pDraw->pPixels[i]);
current_graphics->pixel({pDraw->x + x, pDraw->y + y});
}
current_graphics->pixel({pDraw->x + x, pDraw->y + y});
}
}
}
@ -106,7 +112,6 @@ static int _open(_JPEG_obj_t *self, void *buf, size_t len) {
case PicoGraphics::PEN_P8:
self->jpeg->setPixelType(EIGHT_BIT_GRAYSCALE);
break;
// TODO currently uses EIGHT_BIT_GREYSCALE and shifts down should this use a 4-bit dither?
case PicoGraphics::PEN_P4:
self->jpeg->setPixelType(FOUR_BIT_DITHERED);
break;

View File

@ -36,6 +36,7 @@ MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_triangle_obj, 7, 7, ModPicoG
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(ModPicoGraphics_line_obj, 5, 5, ModPicoGraphics_line);
// Utility
//MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_scanline_callback_obj, ModPicoGraphics_set_scanline_callback);
MP_DEFINE_CONST_FUN_OBJ_1(ModPicoGraphics_get_bounds_obj, ModPicoGraphics_get_bounds);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_font_obj, ModPicoGraphics_set_font);
MP_DEFINE_CONST_FUN_OBJ_2(ModPicoGraphics_set_framebuffer_obj, ModPicoGraphics_set_framebuffer);
@ -65,6 +66,7 @@ STATIC const mp_rom_map_elem_t ModPicoGraphics_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&ModPicoGraphics_set_backlight_obj) },
//{ MP_ROM_QSTR(MP_QSTR_set_scanline_callback), MP_ROM_PTR(&ModPicoGraphics_set_scanline_callback_obj) },
{ MP_ROM_QSTR(MP_QSTR_get_bounds), MP_ROM_PTR(&ModPicoGraphics_get_bounds_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&ModPicoGraphics_set_font_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_framebuffer), MP_ROM_PTR(&ModPicoGraphics_set_framebuffer_obj) },

View File

@ -25,6 +25,7 @@ typedef struct _ModPicoGraphics_obj_t {
PicoGraphics *graphics;
DisplayDriver *display;
void *buffer;
//mp_obj_t scanline_callback; // Not really feasible in MicroPython
} ModPicoGraphics_obj_t;
bool get_display_resolution(PicoGraphicsDisplay display, int &width, int &height) {
@ -166,6 +167,8 @@ mp_obj_t ModPicoGraphics_make_new(const mp_obj_type_t *type, size_t n_args, size
break;
}
//self->scanline_callback = mp_const_none;
// Clear the buffer
self->graphics->set_pen(0);
self->graphics->clear();
@ -235,8 +238,28 @@ mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in) {
return mp_obj_new_tuple(2, tuple);
}
/*
mp_obj_t ModPicoGraphics_set_scanline_callback(mp_obj_t self_in, mp_obj_t cb_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
self->scanline_callback = cb_in;
return mp_const_none;
}
*/
mp_obj_t ModPicoGraphics_update(mp_obj_t self_in) {
ModPicoGraphics_obj_t *self = MP_OBJ_TO_PTR2(self_in, ModPicoGraphics_obj_t);
/*
if(self->scanline_callback != mp_const_none) {
self->graphics->scanline_interrupt = [self](int y){
mp_obj_t args[] = {
mp_obj_new_int(y)
};
mp_call_function_n_kw(self->scanline_callback, MP_ARRAY_SIZE(args), 0, args);
};
} else {
self->graphics->scanline_interrupt = nullptr;
}
*/
self->display->update(self->graphics);
return mp_const_none;

View File

@ -61,6 +61,7 @@ extern mp_obj_t ModPicoGraphics_triangle(size_t n_args, const mp_obj_t *args);
extern mp_obj_t ModPicoGraphics_line(size_t n_args, const mp_obj_t *args);
// Utility
//extern mp_obj_t ModPicoGraphics_set_scanline_callback(mp_obj_t self_in, mp_obj_t cb_in);
extern mp_obj_t ModPicoGraphics_set_font(mp_obj_t self_in, mp_obj_t font);
extern mp_obj_t ModPicoGraphics_get_bounds(mp_obj_t self_in);
extern mp_obj_t ModPicoGraphics_set_framebuffer(mp_obj_t self_in, mp_obj_t framebuffer);