peterhinch / micropython-micro-gui

A lightweight MicroPython GUI library for display drivers based on framebuf, allows input via pushbuttons. See also micropython-touch.
MIT License
247 stars 37 forks source link

Custom graphics #4

Closed petrkr closed 1 year ago

petrkr commented 2 years ago

Is there any way how to window custom graphics such as QR code? I know I can do base ssd.pixel() and draw it over everything, but I feel it is not concept right. So I wonder if I can use some object where I can draw QR and use it as windows/widget/anything

peterhinch commented 2 years ago

It would be possible to design a widget that could render arbitrary bitmapped graphics. One approach would be to create a FrameBuffer instance with the correct dimensions for the graphic, which would be read (typically from a file) and rendered to that FrameBuffer. The image could then be placed anywhere on the display's buffer using the blit method.

I have no plans to do this, but would treat any PR on its merits.

petrkr commented 2 years ago

Prerequest uqr native library

class QRWidget(Widget):
    def __init__(self, writer, row, col, value, scale = 4, border = None):
        self._qr = uqr.make(value)
        self._border = border or scale * 2
        self._scale = scale

        self._size = (self._qr.width()*self._scale)+self._border
        super().__init__(writer, row, col, self._size, self._size, BLACK, WHITE, WHITE)

        self.x = col + self._border // 2
        self.y = row + self._border // 2

    def show(self):
        if not super().show(False):  # Draw border, fill background
            return

        for x in range(self._qr.width()):
            for y in range(self._qr.width()):
                if self._qr.get(x,y):
                    display.fill_rect(self.x+x*self._scale, self.y+y*self._scale, self._scale, self._scale, self.fgcolor)
class TestQR(Screen):
    def __init__(self):
        super().__init__()
        wribtn = CWriter(ssd, arial10, WHITE, BLACK)
        QRWidget(wribtn, 10, 10, "Test uQR on Micro GUI")
        CloseButton(wribtn)
Screen.change(TestQR)

IMG_20220116_212624927_HDR

petrkr commented 2 years ago

Parse BMP image problem with pixel color.. Because my LCD is not RGB. That is why what should be orange is pink now...

But maybe if I will fill ti framebuffer with framebuf.RGB565 then it could work... But more memory will be needed :(

class BMPWidget(Widget):
    def __init__(self, writer, row, col, file):
        self._file = open(file, "rb")

        magic, size, res1, res2, self._imgoffset = struct.unpack('<2sIHHI', self._file.read(14))
        headersize, self._w, self._h, planes, bits, comp, imgdatasize, xres, yres, ncol, icol = struct.unpack('<IiiHHIIiiII', self._file.read(40))

        self._rowSize = (self._w * 2 + 2) & ~2

        self._fr = self._file.read
        self._fs = self._file.seek
        super().__init__(writer, row, col, self._h, self._w, None, None, False)

    def show(self):
        if not super().show(False):  # Draw border, fill background
            return

        dp = ssd.pixel

        for row in range(0, self._h):
            pos = self._imgoffset + (self._h - 1 - row) * self._rowSize
            self._fs(pos)
            for col in range(0, self._w):
                data = self._fr(2)
                dp(col, row, (data[0] << 8) + data[1])
class TestBMP(Screen):
    def __init__(self):
        super().__init__()
        wribtn = CWriter(ssd, arial10, WHITE, BLACK)
        BMPWidget(wribtn, 10, 10, "octopuslogo.bmp")
        CloseButton(wribtn)

IMG_20220116_234609129_HDR

petrkr commented 2 years ago

Well... I recognized, library uses 8bit for all three colors (not RGB565 as I thought)... So I did (read found on google) covnert 565 to 888 then used ssd.rgb() to convert to 8bit...

Works, but 3bits per color is really low for some gradient down-scales images, but I think we can work with that, just we will prepare better BMP

        def rgb565to8bit(color):
            r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6
            g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6
            b = (((color & 0x1F) * 527) + 23) >> 6
            return ssd.rgb(r,g,b)

        self._fs(self._imgoffset)

        for row in range(0, self._h):
            for col in range(0, self._w):
                data = self._fr(2)
                dp(col, self._h - 1 - row, rgb565to8bit((data[0] << 8) + data[1]))

EDIT: changed bmp read

IMG_20220117_003755598_HDR

peterhinch commented 2 years ago

That looks good.

self._qr = uqr.make(value)

Do you plan to open source the QR code generation? Or is it already out there?

petrkr commented 2 years ago

That looks good.

self._qr = uqr.make(value)

Do you plan to open source the QR code generation? Or is it already out there?

It's out, but since developers of uPy says "If you can write it in python, do it in python". Thus they did not embedded this C++ native uQR library to micropython. And that python's version is very slow. You wait up to 3 secods to just generate QR matrix. This C++ native it took less than 100ms.

So I patched uPy and compiled own version... I also thought support that python's version as fallback.

Here is native MPY module https://github.com/coinkite/mpy-qr This is that very slow python's implementation https://github.com/JASchilz/uQR

and Here we talked about that 3 years ago: https://github.com/micropython/micropython/issues/4594 (actually you commented there too)

petrkr commented 2 years ago

I think I can close this already, because I was able to do custom components already. Thanks