pimoroni / badger2040

Examples and firmware for Badger 2040 and Badger 2040 W
MIT License
150 stars 50 forks source link

Broken Badger2040.image() method #41

Open niutech opened 1 year ago

niutech commented 1 year ago

In version 0.0.2, the Badger2040.image() method looked like this:

for(auto y = 0; y < dh; y++) {
  for(auto x = 0; x < dw; x++) {
    // work out byte offset in source data
    uint32_t o = ((y + sy) * (stride >> 3)) + ((x + sx) >> 3);
    // extract bitmask for this pixel
    uint32_t bm = 0b10000000 >> ((x + sx) & 0b111);
    // draw the pixel
    uc8151_legacy.pixel(dx + x, dy + y, data[o] & bm);
  }
}

However, in version 0.0.3 it looks like this:

for oy in range(h):
    row = data[oy]
    for ox in range(w):
        if row & 0b1 == 0:
            self.display.pixel(x + ox, y + oy)
        row >>= 1

So putting the same image data results in garbled pixels after upgrade to 0.0.3. Please make it compatible, like this:

for oy in range(h):
    for ox in range(w):
        o = oy * (w >> 3) + (ox >> 3)
        bm = 0b10000000 >> (ox & 0b111)
        if data[o] & bm:
            self.display.pixel(x + ox, y + oy)
Gadgetoid commented 1 year ago

I'm not sure what happened here, but this is a slow and awful kludge that's likely to be completely deprecated in favour of PNG support in the next release.

niutech commented 1 year ago

PNG is a nice addition, but isn't PNG decoding overcomplicated when displaying many small icons on the Badger2040 screen? Drawing a bytearray should be much faster.

Message ID: @.***>

Gadgetoid commented 1 year ago

Faster, maybe, but accessible to the vast majority of users, not especially.

The implementation currently in Badger 2040 is probably slower than PNG, it's really about the worst way to do it.

There might be a case to be made for supporting this in PicoGraphics, but there's no guarantee the bytes in the drawing surface will be in any particular order, endianess, type or size.

Anyway the best way to do this in the interim is to implement the function in your own code, and use memoryview(display.display) to access the raw buffer as quickly as possible, eg:

import io
from badger2040 import Badger2040

display = Badger2040()

display.set_pen(15)
display.clear()

def display_bitmap(o_x, o_y, width, height, data):
    WIDTH, HEIGHT = display.display.get_bounds()

    y_bytes = int(height // 8)

    if len(data) < width * height / 8:
        raise ValueError("Data undersized")

    for x in range(width):
        src = x * y_bytes
        dst = (x + o_x) * int(HEIGHT // 8) + int(o_y // 8)
        b = bytes(data[src:src + y_bytes])
        memoryview(display.display)[dst:dst + y_bytes] = b

display_bitmap(10, 10, 8, 16, [
    0b01111000, 0b11111110,
    0b10111110, 0b11111110,
    0b11011110, 0b11111110,
    0b11101111, 0b11111110,
    0b11110111, 0b11111110,
    0b11111011, 0b11111110,
    0b00111101, 0b11111110,
    0b00111110, 0b11111110,
])

display.update()

Noting that this limits you to heights and Y positions in multiples of 8 pixels, since that's how the internal data is packed, but if you want fast that's about as fast as you can get without dropping into C.

niutech commented 1 year ago

Thank you!