Xinyuan-LilyGO / lilygo-micropython

Micropython for LILYGO boards
GNU General Public License v3.0
88 stars 28 forks source link

Framebuffer example crashes micropython #4

Closed scumola closed 2 years ago

scumola commented 2 years ago

Getting a reboot when trying to run this:

import epd
import framebuf
epd47 = epd.EPD47()
epd47.power(True)
epd47.clear()
fbuf = framebuf.FrameBuffer(bytearray(100*10*2), 100, 10, framebuf.GS4_HMSB)
fbuf.fill(0)
fbuf.text('MicroPython!', 0, 0, 0xffff)
fbuf.hline(0, 9, 96, 0xffff)
epd47.bitmap(fbuf,0,0,100,100)

(dies on the epd47.bitmap() call)

Output:

>>> %Run -c $EDITOR_CONTENT
Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.
Core 0 register dump:
PC      : 0x400d2953  PS      : 0x00060d30  A0      : 0x800e3bdc  A1      : 0x3ffcf1d0  
A2      : 0x000a0064  A3      : 0x000a0064  A4      : 0x3ffcf1ec  A5      : 0x00000004  
A6      : 0x00000060  A7      : 0x3ffcf1d0  A8      : 0x800d2940  A9      : 0x3ffcf1b0  
A10     : 0x000a0064  A11     : 0x3ffcf1e8  A12     : 0x3ffcf1ec  A13     : 0x00000006  
A14     : 0x0000000a  A15     : 0x0000ffff  SAR     : 0x0000001c  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x000a0064  LBEG    : 0x4009320e  LEND    : 0x40093219  LCOUNT  : 0x00000000  

ELF file SHA256: 59e057f1656a3cc9

Backtrace: 0x400d2950:0x3ffcf1d0 0x400e3bd9:0x3ffcf220 0x400e9a69:0x3ffcf250 0x400e9bba:0x3ffcf270 0x400ec245:0x3ffcf290 0x400e3c64:0x3ffcf330 0x400e9a69:0x3ffcf390 0x400e9a92:0x3ffcf3b0 0x40117a3e:0x3ffcf3d0 0x40117cd5:0x3ffcf460 0x400fbb55:0x3ffcf4b0

Rebooting...
ets Jul 29 2019 12:21:46
lbuque commented 2 years ago

The following is the correct use of epd47.bitmap(), it will not restart.

LilyGo-EPD47 using in micropython

import epd
import framebuf
epd47 = epd.EPD47()
epd47.power(True)
epd47.clear()
bitmap = bytearray(100*10*2)
fbuf = framebuf.FrameBuffer(bitmap, 100, 10, framebuf.GS4_HMSB)
fbuf.fill(0)
fbuf.text('MicroPython!', 0, 0, 0xffff)
fbuf.hline(0, 9, 96, 0xffff)
epd47.bitmap(list(bitmap),0,0,100,100)

But what is displayed is not correct.

scumola commented 2 years ago

Ahh, I wasn't passing in the list of the one bytearray! I am able to see the black blob that gets sent to the screen (not what's expected). I tried different options for the framebuf.GS4_HMSB (all of the mono options too) and none of them seem to work as expected. Is this a bug or am I just not using it properly?

image

scumola commented 2 years ago

I think that if we could come up with good examples for working full-screen 1-bit framebuffer and 4-bit framebuffer, we could close this issue, but I've not been able to get that working yet.

scumola commented 2 years ago

Also, every other time I try to get this to work, it reboots the board it seems

lbuque commented 2 years ago

I think that if we could come up with good examples for working full-screen 1-bit framebuffer and 4-bit framebuffer, we could close this issue, but I've not been able to get that working yet.

from math import floor

class FrameBuffer():
    def __init__(self, buffer, width, height):
        self.fb = buffer
        self.width = width
        self.height = height

    def fill(self, color):
        '''Fill the entire FrameBuffer with the specified color.
        '''
        for i in range(0, floor(self.width * self.height / 2)):
            self.fb[i] = color

    def pixel(self, x, y, color):
        '''Draw a pixel a given framebuffer.

        Args:
            x: Horizontal position in pixels.
            y: Vertical position in pixels.
            color: The gray value of the line (0-255).
        '''
        if x < 0 or x >= self.width:
            return
        if y < 0 or y >= self.height:
            return
        pos = y * floor(self.width / 2) + floor(x / 2)
        if floor(x % 2):
             self.fb[pos] = self.fb[pos] & 0x0F | color & 0xF0
        else:
            self.fb[pos] = self.fb[pos] & 0xF0 | color >> 4

    def hline(self, x, y, length, color):
        '''Draw a horizontal line to a given framebuffer.

        Args:
            x: Horizontal start position in pixels.
            y: Vertical start position in pixels.
            length: Length of the line in pixels.
            color: The gray value of the line (0-255);
        '''
        for i in range(0, length):
            self.pixel(x + i, y, color)

    def vline(self, x, y, length, color):
        '''Draw a horizontal line to a given framebuffer.

        Args:
            x: Horizontal start position in pixels.
            y: Vertical start position in pixels.
            length: Length of the line in pixels.
            color: The gray value of the line (0-255);
        '''
        for i in range(0, length):
            self.pixel(x, y + i, color)

    def circle(self, x, y, r, color):
        '''Draw a circle with given center and radius

        Args:
            x: Center-point x coordinate
            y: Center-point y coordinate
            r: Radius of the circle in pixels
            color: The gray value of the line (0-255)
        '''
        f = 1 - r
        ddF_x = 1
        ddF_y = -2 * r
        xx = 0
        yy = r

        self.pixel(x,     y + r, color)
        self.pixel(x,     y - r, color)
        self.pixel(x + r,     y, color)
        self.pixel(x - r,     y, color)

        while xx < yy:
            if (f >= 0):
                yy -= 1
                ddF_y += 2
                f += ddF_y
            xx += 1
            ddF_x += 2
            f += ddF_x

            self.pixel(x + xx, y + yy, color)
            self.pixel(x - xx, y + yy, color)
            self.pixel(x + xx, y - yy, color)
            self.pixel(x - xx, y - yy, color)
            self.pixel(x + yy, y + xx, color)
            self.pixel(x - yy, y + xx, color)
            self.pixel(x + yy, y - xx, color)
            self.pixel(x - yy, y - xx, color)

    def fill_circle(self, x, y, r, color):
        '''Draw a circle with fill with given center and radius

        Args:
            x: Center-point x coordinate
            y: Center-point y coordinate
            r: Radius of the circle in pixels
            color: The gray value of the line (0-255)
        '''
        self.vline(x, y - r, 2 * r + 1, color)
        self._fill_circle_helper(x, y, r, 3, 0, color)

    def rect(self, x, y, w, h, color):
        '''Draw a rectanle with no fill color

        Args:
            x: Top left corner x coordinate
            y: Top left corner y coordinate
            w: Width in pixels
            h: Height in pixels
            color: The gray value of the line (0-255);
        '''
        self.hline(x,                 y, w, color)
        self.hline(x,         y + h - 1, w, color)
        self.vline(x,                 y, h, color)
        self.vline(x + w - 1,         y, h, color)

    def fill_rect(self, x, y, w, h, color):
        '''Draw a rectanle with fill color

        Args:
            x: Top left corner x coordinate
            y: Top left corner y coordinate
            w: Width in pixels
            h: Height in pixels
            color: The gray value of the line (0-255)
        '''
        for i in range(x, x + w):
            self.vline(i, y, h, color)

    def line(self, x0, y0, x1, y1, color):
        '''Draw a line

        Args:
            x0  Start point x coordinate
            y0  Start point y coordinate
            x1  End point x coordinate
            y1  End point y coordinate
            color: The gray value of the line (0-255)
        '''
        if x0 == x1:
            if (y0 > y1):
                y0, y1 = self._swap_int(y0, y1)
            self.vline(x0, y0, y1 - y0 + 1, color)
        elif y0 == y1:
            if x0 > x1:
                x0, x1 = self._swap_int(x0, x1)
            self.hline(x0, y0, x1 - x0 + 1, color)
        else:
            self._write_line(x0, y0, x1, y1, color)

    def triangle(self, x0, y0, x1, y1, x2, y2, color):
        '''Draw a triangle with no fill color

        Args:
            x0: Vertex #0 x coordinate
            y0: Vertex #0 y coordinate
            x1: Vertex #1 x coordinate
            y1: Vertex #1 y coordinate
            x2: Vertex #2 x coordinate
            y2: Vertex #2 y coordinate
            color: The gray value of the line (0-255)
        '''
        self.line(x0, y0, x1, y1, color)
        self.line(x1, y1, x2, y2, color)
        self.line(x2, y2, x0, y0, color)

    def fill_triangle(self, x0, y0, x1, y1, x2,  y2, color):
        '''Draw a triangle with color-fill

        Args:
            x0: Vertex #0 x coordinate
            y0: Vertex #0 y coordinate
            x1: Vertex #1 x coordinate
            y1: Vertex #1 y coordinate
            x2: Vertex #2 x coordinate
            y2: Vertex #2 y coordinate
            color: The gray value of the line (0-255)
        '''
        a = 0
        b = 0
        y = 0
        last = 0

        if y0 > y1 :
            y0, y1 = self._swap_int(y0, y1)
            x0, x1 = self._swap_int(x0, x1)

        if y1 > y2 :
            y2, y1 = self._swap_int(y2, y1)
            x2, x1 = self._swap_int(x2, x1)

        if y0 > y1 :
            y0, y1 = self._swap_int(y0, y1)
            x0, x1 = self._swap_int(x0, x1)

        if y0 == y2 :
            a = b = x0
            if x1 < a :
                a = x1
            elif x1 > b :
                b = x1
            if x2 < a :
                a = x2
            elif x2 > b:
                b = x2
            self.hline(a, y0, b - a + 1, color)
            return

        dx01 = x1 - x0
        dy01 = y1 - y0
        dx02 = x2 - x0
        dy02 = y2 - y0
        dx12 = x2 - x1
        dy12 = y2 - y1
        sa = 0
        sb = 0

        if y1 == y2 :
            last = y1
        else:
            last = y1 - 1

        for y in range(y0, last + 1) :
            a = x0 + floor(sa / dy01)
            b = x0 + floor(sb / dy02)
            sa += dx01
            sb += dx02
            if a > b :
                a, b = self._swap_int(a, b)
            self.hline(a, y, b - a + 1, color)

        y = last + 1

        sa = dx12 * (y - y1)
        sb = dx02 * (y - y0)
        for i in range(y, y2 + 1):
            a = x1 + floor(sa / dy12)
            b = x0 + floor(sb / dy02)
            sa += dx12
            sb += dx02

            if (a > b):
                a, b = self._swap_int(a, b)
            self.hline(a, i, b - a + 1, color)

    def _fill_circle_helper(self, x0, y0, r, corners, delta, color):
        f = 1 - r
        ddF_x = 1
        ddF_y = -2 * r
        x = 0
        y = r
        px = x
        py = y

        delta += 1

        while x < y:
            if f >= 0:
                y -= 1
                ddF_y += 2
                f += ddF_y
            x += 1
            ddF_x += 2
            f += ddF_x
            if (x < (y + 1)):
                if (corners & 1):
                    self.vline(x0 + x, y0 - y, 2 * y + delta, color)
                if (corners & 2):
                    self.vline(x0 - x, y0 - y, 2 * y + delta, color)
            if y != py:
                if corners & 1:
                    self.vline(x0 + py, y0 - px, 2 * px + delta, color)
                if corners & 2:
                    self.vline(x0 - py, y0 - px, 2 * px + delta, color)
                py = y
            px = x

    def _write_line(self, x0, y0, x1, y1, color):
        '''Write a line. Bresenham's algorithm - thx wikpedia

        Args:
            x0  Start point x coordinate
            y0  Start point y coordinate
            x1  End point x coordinate
            y1  End point y coordinate
            color: The gray value of the line (0-255)
        '''
        steep = abs(y1 - y0) > abs(x1 - x0)
        if steep:
            x0, y0 = self._swap_int(x0, y0)
            x1, y1 = self._swap_int(x1, y1)

        if x0 > x1:
            x0, x1 = self._swap_int(x0, x1)
            x1, y1 = self._swap_int(y0, y1)

        dx = x1 - x0
        dy = abs(y1 - y0)

        err = floor(dx / 2)

        ystep = 1 if y0 < y1 else -1

        while x0 <= x1:
            if steep:
                self.pixel(y0, x0, color)
            else:
               self.pixel(x0, y0, color)
            err -= dy
            if err < 0:
                y0 += ystep
                err += dx
            x0 += 1

    @staticmethod
    def _swap_int(a, b):
        return b, a

if __name__ == "__main__":
    from epd import EPD47
    e = EPD47()
    e.power(True)
    e.clear()
    buffer = bytearray(480 * 540)
    fb = FrameBuffer(buffer, 960, 540)
    fb.fill(255)

    fb.hline(100, 100, 100, 256)

    fb.vline(100, 100, 100, 256)

    fb.circle(200, 200, 50, 256)
    fb.fill_circle(400, 200, 50, 256)

    fb.rect(600, 200, 100, 100, 256)
    fb.fill_rect(800, 200, 100, 100, 256)

    fb.line(100, 300, 400, 500, 256)
    fb.triangle(200, 400, 150, 500, 250, 400, 256)
    fb.fill_triangle(600, 400, 600, 500, 700, 500, 256)
    e.bitmap(list(buffer), 0, 0, 960, 540)
scumola commented 2 years ago

Oh, wow! Quite a bit of work. I'm impressed! I didn't know that you'd be re-writing all of the framebuffer functions yourself! There isn't a way to just re-use all of the existing framebuffer libraries out there already - the ones that support fonts and everything? Will I have to write my own font-rendering code in python in order to draw text to the framebuffer?

lbuque commented 2 years ago

support text https://github.com/Xinyuan-LilyGO/lilygo-micropython/tree/master/extmod/display/framebuf1

skorokithakis commented 1 year ago

I cannot get any of these examples to work, my screen keeps displaying white. I can only draw things if I change the offset on bitmap() to 20, 20. The C++ demos work fine, though. Does anyone know what might be wrong?

kubark42 commented 1 year ago

@skorokithakis I'm having a similar issue. Generally, the micropython EPD support seems to be troubled, and very little of the examples here work, and the few which do result in very poor quality images. When compared to the C++ examples, there's a stark difference. Perhaps something upstream changed and has caused regressions?