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:
parent
656d69399a
commit
602d1b41dd
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue