From 27d571b473cecd749adba6cb8fb39ea971a55665 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 14 Jun 2022 15:06:44 +0100 Subject: [PATCH] PicoGraphics: Tidy up dithering. --- libraries/pico_graphics/pico_graphics.cpp | 17 +----- libraries/pico_graphics/pico_graphics.hpp | 38 ++++++++++-- .../pico_graphics/pico_graphics_pen_p4.cpp | 58 ++++++++++++++++--- .../pico_graphics/pico_graphics_pen_p8.cpp | 49 ++++++++++++++-- micropython/modules/jpegdec/jpegdec.c | 5 ++ micropython/modules/jpegdec/jpegdec.cpp | 17 +----- 6 files changed, 134 insertions(+), 50 deletions(-) diff --git a/libraries/pico_graphics/pico_graphics.cpp b/libraries/pico_graphics/pico_graphics.cpp index fb4ab288..8bdd2eb3 100644 --- a/libraries/pico_graphics/pico_graphics.cpp +++ b/libraries/pico_graphics/pico_graphics.cpp @@ -53,22 +53,7 @@ namespace pimoroni { void PicoGraphics::remove_clip() { clip = bounds; } - - void PicoGraphics::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &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); } diff --git a/libraries/pico_graphics/pico_graphics.hpp b/libraries/pico_graphics/pico_graphics.hpp index 933638b8..765573e3 100644 --- a/libraries/pico_graphics/pico_graphics.hpp +++ b/libraries/pico_graphics/pico_graphics.hpp @@ -208,8 +208,6 @@ 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 &candidates); - void clear(); void pixel(const Point &p); void pixel_span(const Point &p, int32_t l); @@ -226,15 +224,28 @@ namespace pimoroni { class PicoGraphics_PenP4 : public PicoGraphics { public: + static const uint palette_size = 16; uint8_t color; - RGB palette[16]; + RGB palette[palette_size]; + bool used[palette_size]; + + const uint pattern[16] = // dither pattern + {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5}; + std::array, 512> candidate_cache; + bool cache_built = false; + std::array candidates; PicoGraphics_PenP4(uint16_t width, uint16_t height, void *frame_buffer); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override; + int create_pen(uint8_t r, uint8_t g, uint8_t b) override; + int reset_pen(uint8_t i) override; + void set_pixel(const Point &p) override; + void get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &candidates); void set_pixel_dither(const Point &p, const RGB &c) override; + void scanline_convert(PenType type, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h / 2; @@ -243,16 +254,28 @@ namespace pimoroni { class PicoGraphics_PenP8 : public PicoGraphics { public: + static const uint palette_size = 256; uint8_t color; - RGB palette[256]; - bool used[256]; + RGB palette[palette_size]; + bool used[palette_size]; + + const uint pattern[16] = // dither pattern + {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5}; + std::array, 512> candidate_cache; + bool cache_built = false; + std::array candidates; + PicoGraphics_PenP8(uint16_t width, uint16_t height, void *frame_buffer); void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; int reset_pen(uint8_t i) override; + void set_pixel(const Point &p) override; + void get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &candidates); + void set_pixel_dither(const Point &p, const RGB &c) override; + void scanline_convert(PenType type, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h; @@ -267,11 +290,14 @@ namespace pimoroni { void set_pen(uint c) override; void set_pen(uint8_t r, uint8_t g, uint8_t b) override; int create_pen(uint8_t r, uint8_t g, uint8_t b) override; + void set_pixel(const Point &p) override; void set_pixel_dither(const Point &p, const RGB &c) override; void set_pixel_dither(const Point &p, const RGB565 &c) override; - void scanline_convert(PenType type, conversion_callback_func callback) override; + void sprite(void* data, const Point &sprite, const Point &dest, const int scale, const int transparent) override; + + void scanline_convert(PenType type, conversion_callback_func callback) override; static size_t buffer_size(uint w, uint h) { return w * h; } diff --git a/libraries/pico_graphics/pico_graphics_pen_p4.cpp b/libraries/pico_graphics/pico_graphics_pen_p4.cpp index 10f54241..4a6d76c4 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p4.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p4.cpp @@ -7,19 +7,20 @@ namespace pimoroni { if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); } - for(auto i = 0u; i < 16; i++) { + for(auto i = 0u; i < palette_size; i++) { palette[i] = { uint8_t(i << 4), uint8_t(i << 4), uint8_t(i << 4) }; + used[i] = false; } } void PicoGraphics_PenP4::set_pen(uint c) { color = c & 0xf; } void PicoGraphics_PenP4::set_pen(uint8_t r, uint8_t g, uint8_t b) { - int pen = RGB(r, g, b).closest(palette, 16); + int pen = RGB(r, g, b).closest(palette, palette_size); if(pen != -1) color = pen; } int PicoGraphics_PenP4::update_pen(uint8_t i, uint8_t r, uint8_t g, uint8_t b) { @@ -27,6 +28,22 @@ namespace pimoroni { palette[i] = {r, g, b}; return i; } + int PicoGraphics_PenP4::create_pen(uint8_t r, uint8_t g, uint8_t b) { + // Create a colour and place it in the palette if there's space + for(auto i = 0u; i < palette_size; i++) { + if(!used[i]) { + palette[i] = {r, g, b}; + used[i] = true; + return i; + } + } + return -1; + } + int PicoGraphics_PenP4::reset_pen(uint8_t i) { + palette[i] = {0, 0, 0}; + used[i] = false; + return i; + } void PicoGraphics_PenP4::set_pixel(const Point &p) { // pointer to byte in framebuffer that contains this pixel uint8_t *buf = (uint8_t *)frame_buffer; @@ -39,26 +56,49 @@ namespace pimoroni { *f &= m; // clear bits *f |= b; // set value } + + void PicoGraphics_PenP4::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &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_PenP4::set_pixel_dither(const Point &p, const RGB &c) { 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 candidates; - get_dither_candidates(c, palette, 256, candidates); + if(!cache_built) { + for(uint i = 0; i < 512; i++) { + RGB cache_col((i & 0x1C0) >> 1, (i & 0x38) << 2, (i & 0x7) << 5); + get_dither_candidates(cache_col, palette, palette_size, candidate_cache[i]); + } + cache_built = true; + } + + uint cache_key = ((c.r & 0xE0) << 1) | ((c.g & 0xE0) >> 2) | ((c.b & 0xE0) >> 5); + //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]]; + //color = candidates[pattern[pattern_index]]; + color = candidate_cache[cache_key][pattern[pattern_index]]; set_pixel(p); } void PicoGraphics_PenP4::scanline_convert(PenType type, conversion_callback_func callback) { if(type == PEN_RGB565) { // Cache the RGB888 palette as RGB565 - RGB565 cache[16]; - for(auto i = 0u; i < 16; i++) { + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { cache[i] = palette[i].to_rgb565(); } diff --git a/libraries/pico_graphics/pico_graphics_pen_p8.cpp b/libraries/pico_graphics/pico_graphics_pen_p8.cpp index da0729bf..0cac304a 100644 --- a/libraries/pico_graphics/pico_graphics_pen_p8.cpp +++ b/libraries/pico_graphics/pico_graphics_pen_p8.cpp @@ -7,8 +7,8 @@ namespace pimoroni { if(this->frame_buffer == nullptr) { this->frame_buffer = (void *)(new uint8_t[buffer_size(width, height)]); } - for(auto i = 0u; i < 256; i++) { - palette[i] = {0, 0, 0}; + for(auto i = 0u; i < palette_size; i++) { + palette[i] = {uint8_t(i), uint8_t(i), uint8_t(i)}; used[i] = false; } } @@ -26,7 +26,7 @@ namespace pimoroni { } int PicoGraphics_PenP8::create_pen(uint8_t r, uint8_t g, uint8_t b) { // Create a colour and place it in the palette if there's space - for(auto i = 0u; i < 256u; i++) { + for(auto i = 0u; i < palette_size; i++) { if(!used[i]) { palette[i] = {r, g, b}; used[i] = true; @@ -44,11 +44,50 @@ namespace pimoroni { uint8_t *buf = (uint8_t *)frame_buffer; buf[p.y * bounds.w + p.x] = color; } + + void PicoGraphics_PenP8::get_dither_candidates(const RGB &col, const RGB *palette, size_t len, std::array &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_PenP8::set_pixel_dither(const Point &p, const RGB &c) { + if(!bounds.contains(p)) return; + + if(!cache_built) { + for(uint i = 0; i < 512; i++) { + RGB cache_col((i & 0x1C0) >> 1, (i & 0x38) << 2, (i & 0x7) << 5); + get_dither_candidates(cache_col, palette, palette_size, candidate_cache[i]); + } + cache_built = true; + } + + uint cache_key = ((c.r & 0xE0) << 1) | ((c.g & 0xE0) >> 2) | ((c.b & 0xE0) >> 5); + //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]]; + color = candidate_cache[cache_key][pattern[pattern_index]]; + set_pixel(p); + } + void PicoGraphics_PenP8::scanline_convert(PenType type, conversion_callback_func callback) { if(type == PEN_RGB565) { // Cache the RGB888 palette as RGB565 - RGB565 cache[256]; - for(auto i = 0u; i < 256; i++) { + RGB565 cache[palette_size]; + for(auto i = 0u; i < palette_size; i++) { cache[i] = palette[i].to_rgb565(); } diff --git a/micropython/modules/jpegdec/jpegdec.c b/micropython/modules/jpegdec/jpegdec.c index 4e31764c..cebb6fb5 100644 --- a/micropython/modules/jpegdec/jpegdec.c +++ b/micropython/modules/jpegdec/jpegdec.c @@ -32,6 +32,11 @@ const mp_obj_type_t JPEG_type = { STATIC const mp_map_elem_t JPEG_globals_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_jpegdec) }, { MP_OBJ_NEW_QSTR(MP_QSTR_JPEG), (mp_obj_t)&JPEG_type }, + + { MP_ROM_QSTR(MP_QSTR_JPEG_SCALE_FULL), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_JPEG_SCALE_HALF), MP_ROM_INT(2) }, + { MP_ROM_QSTR(MP_QSTR_JPEG_SCALE_QUARTER), MP_ROM_INT(4) }, + { MP_ROM_QSTR(MP_QSTR_JPEG_SCALE_EIGHTH), MP_ROM_INT(8) }, }; STATIC MP_DEFINE_CONST_DICT(mp_module_JPEG_globals, JPEG_globals_table); diff --git a/micropython/modules/jpegdec/jpegdec.cpp b/micropython/modules/jpegdec/jpegdec.cpp index 123d47e7..6bf9bc9a 100644 --- a/micropython/modules/jpegdec/jpegdec.cpp +++ b/micropython/modules/jpegdec/jpegdec.cpp @@ -47,15 +47,6 @@ MICROPY_EVENT_POLL_HOOK current_graphics->pixel({pDraw->x + x, pDraw->y + y}); } } - } else if(pDraw->iBpp == 8) { - uint8_t *pixels = (uint8_t *)pDraw->pPixels; - for(int y = 0; y < pDraw->iHeight; y++) { - for(int x = 0; x < pDraw->iWidth; x++) { - int i = y * pDraw->iWidth + x; - current_graphics->set_pen(pixels[i] >> (current_graphics->pen_type == PicoGraphics::PEN_P4 ? 4 : 0)); - current_graphics->pixel({pDraw->x + x, pDraw->y + y}); - } - } } else { for(int y = 0; y < pDraw->iHeight; y++) { for(int x = 0; x < pDraw->iWidth; x++) { @@ -65,6 +56,8 @@ MICROPY_EVENT_POLL_HOOK //current_graphics->pixel({pDraw->x + x, pDraw->y + y}); // TODO make dither optional current_graphics->set_pixel_dither({pDraw->x + x, pDraw->y + y}, (RGB565)(pDraw->pPixels[i])); + } else if (current_graphics->pen_type == PicoGraphics::PEN_P8 || current_graphics->pen_type == PicoGraphics::PEN_P4) { + 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}); @@ -107,13 +100,9 @@ static int _open(_JPEG_obj_t *self, void *buf, size_t len) { switch(self->graphics->graphics->pen_type) { case PicoGraphics::PEN_RGB332: case PicoGraphics::PEN_RGB565: - self->jpeg->setPixelType(RGB565_BIG_ENDIAN); - break; case PicoGraphics::PEN_P8: - self->jpeg->setPixelType(EIGHT_BIT_GRAYSCALE); - break; case PicoGraphics::PEN_P4: - self->jpeg->setPixelType(FOUR_BIT_DITHERED); + self->jpeg->setPixelType(RGB565_BIG_ENDIAN); break; // TODO 2-bit is currently unsupported case PicoGraphics::PEN_P2: