← index #2572Issue #10461
Off-topic · high · value 0.256
QUERY · ISSUE

extmod/modframebuf.c framebuf1_text() does not clear pixels

openby peterhinchopened 2016-10-29updated 2016-11-11

This section of the code looks wrong:

                    if (vline_data & 1) { // only draw if pixel set
                        if (0 <= y && y < self->height) { // clip y
                            uint byte_pos = x0 + self->stride * ((uint)y >> 3);
                            if (col == 0) {
                                // clear pixel
                                self->buf[byte_pos] &= ~(1 << (y & 7));
                            } else {
                                // set pixel
                                self->buf[byte_pos] |= 1 << (y & 7);
                            }
                        }

If the pixel is not set, it should surely clear down the pixel in the framebuf as we don't know its prior contents. Further, if we happen to be drawing with col==0 to a part of the framebuf which is already zero, then nothing will be drawn, because in that circumstance the code never sets a pixel.

I hope I'm right on this observation from reviewing the code: I haven't had the opportunity to try it since my SSD1306 was DOA.

CANDIDATE · ISSUE

extmod/framebuf: There will be obvious aliasing when drawing circles or triangles

closedby lbuqueopened 2023-01-10updated 2023-01-11
enhancement

I am increasing the color format of GS4_HLSB for framebuf.

I define GS4_HLSB as the color format opposite to GS4_HMSB.

And made the following changes to extmod/modframebuf.c:

diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c
index 87f7609dd..feb9c69d6 100644
--- a/extmod/modframebuf.c
+++ b/extmod/modframebuf.c
@@ -64,6 +64,7 @@ typedef struct _mp_framebuf_p_t {
 #define FRAMEBUF_GS8      (6)
 #define FRAMEBUF_MHLSB    (3)
 #define FRAMEBUF_MHMSB    (4)
+#define FRAMEBUF_GS4_HLSB (7)
 
 // Functions for MHLSB and MHMSB
 
@@ -231,6 +232,57 @@ STATIC void gs8_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned
     }
 }
 
+// Functions for GS4_HLSB format
+
+STATIC void gs4_hlsb_setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) {
+    uint8_t *pixel = &((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1];
+
+    if (x % 2) {
+        *pixel = ((uint8_t)col & 0x0f) | (*pixel & 0x0f);
+    } else {
+        *pixel = ((uint8_t)col << 4) | (*pixel & 0xf0);
+    }
+}
+
+STATIC uint32_t gs4_hlsb_getpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y) {
+    if (x % 2) {
+        return ((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1] >> 4;
+    }
+
+    return ((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1] & 0x0f;
+}
+
+STATIC void gs4_hlsb_fill_rect(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint32_t col) {
+    col &= 0x0f;
+    uint8_t *pixel_pair = &((uint8_t *)fb->buf)[(x + y * fb->stride) >> 1];
+    uint8_t col_shifted_left = col << 4;
+    uint8_t col_pixel_pair = col_shifted_left | col;
+    unsigned int pixel_count_till_next_line = (fb->stride - w) >> 1;
+    bool odd_x = (x % 2 == 1);
+
+    while (h--) {
+        unsigned int ww = w;
+
+        if (odd_x && ww > 0) {
+            *pixel_pair = (*pixel_pair & 0xf0) | col;
+            pixel_pair++;
+            ww--;
+        }
+
+        memset(pixel_pair, col_pixel_pair, ww >> 1);
+        pixel_pair += ww >> 1;
+
+        if (ww % 2) {
+            *pixel_pair = col_shifted_left | (*pixel_pair & 0x0f);
+            if (!odd_x) {
+                pixel_pair++;
+            }
+        }
+
+        pixel_pair += pixel_count_till_next_line;
+    }
+}
+
 STATIC mp_framebuf_p_t formats[] = {
     [FRAMEBUF_MVLSB] = {mvlsb_setpixel, mvlsb_getpixel, mvlsb_fill_rect},
     [FRAMEBUF_RGB565] = {rgb565_setpixel, rgb565_getpixel, rgb565_fill_rect},
@@ -239,6 +291,7 @@ STATIC mp_framebuf_p_t formats[] = {
     [FRAMEBUF_GS8] = {gs8_setpixel, gs8_getpixel, gs8_fill_rect},
     [FRAMEBUF_MHLSB] = {mono_horiz_setpixel, mono_horiz_getpixel, mono_horiz_fill_rect},
     [FRAMEBUF_MHMSB] = {mono_horiz_setpixel, mono_horiz_getpixel, mono_horiz_fill_rect},
+    [FRAMEBUF_GS4_HLSB] = {gs4_hlsb_setpixel, gs4_hlsb_getpixel, gs4_hlsb_fill_rect},
 };
 
 STATIC inline void setpixel(const mp_obj_framebuf_t *fb, unsigned int x, unsigned int y, uint32_t col) {
@@ -301,6 +354,7 @@ STATIC mp_obj_t framebuf_make_new(const mp_obj_type_t *type, size_t n_args, size
             o->stride = (o->stride + 3) & ~3;
             break;
         case FRAMEBUF_GS4_HMSB:
+        case FRAMEBUF_GS4_HLSB:
             o->stride = (o->stride + 1) & ~1;
             break;
         case FRAMEBUF_GS8:
@@ -885,6 +939,7 @@ STATIC const mp_rom_map_elem_t framebuf_module_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR_GS8), MP_ROM_INT(FRAMEBUF_GS8) },
     { MP_ROM_QSTR(MP_QSTR_MONO_HLSB), MP_ROM_INT(FRAMEBUF_MHLSB) },
     { MP_ROM_QSTR(MP_QSTR_MONO_HMSB), MP_ROM_INT(FRAMEBUF_MHMSB) },
+    { MP_ROM_QSTR(MP_QSTR_GS4_HLSB), MP_ROM_INT(FRAMEBUF_GS4_HLSB) },
 };
 
 STATIC MP_DEFINE_CONST_DICT(framebuf_module_globals, framebuf_module_globals_table);
@@ -897,4 +952,4 @@ const mp_obj_module_t mp_module_framebuf = {
 MP_REGISTER_MODULE(MP_QSTR_framebuf, mp_module_framebuf);
 #endif
 
-#endif // MICROPY_PY_FRAMEBUF
+#endif // MICROPY_PY_FRAMEBUF
\ No newline at end of file

I found that the image drawn using the framebuf module has obvious aliasing.

微信图片_20230110180900

However, I use the framebuf1 module below, and the drawn image seems to be smoother.

# from math import floor, ceil
# from collections import namedtuple
from zlib import decompress

'''
GFXfont = namedtuple("GFXfont", ["bitmap", "glyph", "intervals", \
                                 "interval_count", "compressed", "advance_y", \
                                 "ascender", "descender"])

UnicodeInterval = namedtuple("UnicodeInterval", ["first", "last", "offset"])

GFXglyph = namedtuple("GFXglyph", ["width", "height", "advance_x", "left", \
                                   "top", "compressed_size", "data_offset"])
'''


class FrameBuffer():
    '''This module provides a general frame buffer which can be used to create 
    bitmap images, which can then be sent to a display.
    '''

    def __init__(self, buffer: bytearray, width: int, height: int):
        self.fb = buffer
        self.width = width
        self.height = height

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

    def pixel(self, x: int, y: int, color: int):
        '''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
        color = 255 - color
        pos = y * int(self.width / 2) + int(x / 2)
        if int(x % 2):
            self.fb[pos] = (self.fb[pos] & 0x0F) | (color & 0xF0)
        else:
            self.fb[pos] = (self.fb[pos] & 0xF0) | (color >> 4 & 0x0F)

    def hline(self, x: int, y: int, length: int, color: int):
        '''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: int, y: int, length: int, color: int):
        '''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 line(self, x0: int, y0: int, x1: int, y1: int, color: int):
        '''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 rect(self, x: int, y: int, w: int, h: int, color: int):
        '''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: int, y: int, w: int, h: int, color: int):
        '''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 circle(self, x: int, y: int, r: int, color: int):
        '''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: int, y: int, r: int, color: int):
        '''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 triangle(self, x0: int, y0: int, x1: int, y1: int, x2: int, y2: int, color: int):
        '''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: int, y0: int, x1: int, y1: int, x2: int,  y2: int, color: int):
        '''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 = b = y = 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 = sb = 0

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

        for y in range(y0, last + 1):
            a = x0 + int(sa / dy01)
            b = x0 + int(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 + int(sa / dy12)
            b = x0 + int(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 text(self, gfx, s: str, x: int, y: int):
        '''Write a (multi-line) string to FrameBuffer.
        '''
        line_start = x
        a = s.splitlines()
        for i in a:
            x, y = self.writeln(gfx, i, x, y)
            y += gfx.advance_y
            x = line_start
        return x, y

    def writeln(self, gfx, s: str, x: int, y: int):
        if s is None or len(s) == 0:
            return

        # x1 = y1 = w = h = 0
        # tmp_cur_x = x
        # tmp_cur_y = y
        # tmp_cur_x, tmp_cur_y, x1, y1, w, h = self.get_text_bounds(gfx, s,
        #     tmp_cur_x, tmp_cur_y, x1, y1, w, h)

        local_cursor_x = x
        local_cursor_y = y

        cursor_x_init = local_cursor_x
        cursor_y_init = local_cursor_y

        for ch in s:
            local_cursor_x = self.draw_char(gfx, local_cursor_x, local_cursor_y,
                ord(ch))

        x += local_cursor_x - cursor_x_init
        y += local_cursor_y - cursor_y_init
        return x, y

    def get_text_bounds(self, gfx: GFXfont, s: str, x: int, y: int, x1: int, y1: int, w: int, h: int):
        if len(s) == 0:
            return x, y, x, y, 0, 0

        minx = 100000
        miny = 100000
        maxx = -1
        maxy = -1
        original_x = x

        for ch in s:
            x, y, minx, miny, maxx, maxy = self.get_char_bounds(gfx, ord(ch), x,
                y, minx, miny, maxx, maxy)
        x1 = min(original_x, minx)
        w = maxx - x1
        y1 = miny
        h = maxy - miny
        return x, y, x1, y1, w, h

    def get_char_bounds(self, gfx: GFXfont, codepoint: int, x: int, y: int, minx: int, miny: int, maxx: int, maxy: int):
        glyph = self.get_glyph(gfx, codepoint)

        if glyph is None:
            return

        x1 = x + glyph.left
        y1 = y + (glyph.top - glyph.height)
        x2 = x1 + glyph.width
        y2 = y1 + glyph.height

        if x1 < minx:
            minx = x1
        if y1 < miny:
            miny = y1
        if x2 > maxx:
            maxx = x2
        if y2 > maxy:
            maxy = y2

        x += glyph.advance_x
        return x, y, minx, miny, maxx, maxy

    def get_glyph(self, gfx: GFXfont, code_point: int):
        glyph = None
        for interval in gfx.intervals:
            if code_point >= interval.first and code_point <= interval.last:
                glyph = gfx.glyph[interval.offset + (code_point - interval.first)]
        return glyph

    def draw_char(self, gfx: GFXfont, cursor_x: int, cursor_y: int, cp: int):
        glyph = self.get_glyph(gfx, cp)

        if glyph is None:
            return

        offset = glyph.data_offset
        width = glyph.width
        height = glyph.height
        left = glyph.left

        byte_width = int(width / 2) + width % 2
        bitmap_size = byte_width * height

        if gfx.compressed:
            try:
                bitmap = decompress(
                    bytes(gfx.bitmap[offset: offset + glyph.compressed_size]),
                    bufsize = bitmap_size)
            except:
                bitmap = decompress(
                    bytes(gfx.bitmap[offset: offset + glyph.compressed_size]),
                    bitmap_size)
        else:
            bitmap = gfx.bitmap[offset: offset + bitmap_size]

        color_lut = [ max(0, min(15, 15 + c * int(-15 / 15))) for c in range(0, 16) ]

        for y in range(0, height):
            yy = cursor_y - glyph.top + y
            if yy < 0 or yy >= self.height:
                continue
            start_pos = cursor_x + left
            byte_complete = start_pos % 2
            x = max(0, -start_pos)
            max_x = min(start_pos + width, self.width)
            for xx in range(start_pos, max_x):
                buf_pos = yy * int(self.width/2) + int(xx / 2)
                old = self.fb[buf_pos]
                bm = bitmap[y * byte_width + int(x / 2)]
                bm = bm & 0xF if x & 1 == 0 else bm >> 4

                if xx & 1 == 0:
                    self.fb[buf_pos] = (old & 0xF0) | color_lut[bm]
                else:
                    self.fb[buf_pos] = (old & 0x0F) | (color_lut[bm] << 4)
                byte_complete = ~byte_complete
                x += 1

        cursor_x += glyph.advance_x
        return cursor_x

    def _fill_circle_helper(self, x0: int, y0: int, r: int, corners: int, delta: int, color: int):
        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: int, y0: int, x1: int, y1: int, color: int):
        '''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)
            y0, y1 = self._swap_int(y0, y1)

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

        err = int(dx / 2)
        ystep = 1 if y0 < y1 else -1

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

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

This is the effect of using framebuf1 module:

微信图片_20230110181134

Because I found that the current framebuf module is very powerful, I don't need to maintain a framebuf1 module.
So I want to merge the color format of GS4_HLSB into the framebuf module.

Any suggestions are welcome! ! !

Thanks! ! !

Keyboard

j / / n
next pair
k / / p
previous pair
1 / / h
show query pane
2 / / l
show candidate pane
c
copy suggested comment
r
toggle reasoning
g i
go to index
?
show this help
esc
close overlays

press ? or esc to close

copied