Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fill method to displayio.Bitmap #2756

Merged
merged 3 commits into from
Apr 14, 2020
Merged

Conversation

caternuson
Copy link

This is functional at least. I basically just stole from the pixel set code and loopified it.

Test program:

import time
import board
import displayio

WIDTH = board.DISPLAY.width
HEIGHT = board.DISPLAY.height

COLORS = (0xFF0000, 0x00FF00, 0x0000FF)

bitmap = displayio.Bitmap(WIDTH, HEIGHT, len(COLORS))

palette = displayio.Palette(len(COLORS))
for i, color in enumerate(COLORS):
    palette[i] = color

tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)

splash = displayio.Group()
splash.append(tile_grid)

board.DISPLAY.show(splash)

print("Fill 1")
board.DISPLAY.auto_refresh = False
start = time.monotonic()
for i in range(WIDTH*HEIGHT):
    bitmap[i] = 1
print(time.monotonic() - start)
board.DISPLAY.auto_refresh = True

time.sleep(1)

print("Fill 2")
start = time.monotonic()
bitmap.fill(2)
print(time.monotonic() - start)

Results:

Adafruit CircuitPython 5.1.0-91-g49fff2d9b-dirty on 2020-04-09; Adafruit CLUE nRF52840 Express with nRF52840
>>> import fill_test
Fill 1
1.32599
Fill 2
0.0269775
>>> 

@kevinjwalters
Copy link

kevinjwalters commented Apr 9, 2020

Looks like uint32_t bytes_per_value = self->bits_per_value / 8; is constant and can be placed outside both for loops to avoid repetition?

You could also swap the loop order and do y as the oustide loop to reduce execution of int32_t row_start = y * self->stride;

@kevinjwalters
Copy link

Are .x2 and .y2 exclusive values for the dirty region? I.e. if a 4x4 bitmap is being cleared should they be set to 3 or 4? The current proposed code sets them to 4.

@caternuson
Copy link
Author

For fill, I thought I did move bytes_per_value outside the loop?

Good question about .x2 and .y2. I based these on what is done in the constructor. The low ends are set to 0, so maybe these should be -1ed?

@kevinjwalters
Copy link

Apologies, I didn't check second diff as I thought it was just doc changes.

@caternuson
Copy link
Author

No worries. First commit was "copy, paste, does it work?". Second commit had some minor refactoring.

@jepler
Copy link
Member

jepler commented Apr 9, 2020

Normally in Python lower-bounds are inclusive and upper-bounds are exclusive, so the slice [1:4] is the elements 1, 2, and 3. I hope that the same is chosen for x2/y2 in this PR.

tannewt
tannewt previously approved these changes Apr 9, 2020
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is fine with me.

Do you want to speed it up further? You could pack the value into one uint32_t at the start and then fill the memory. That would reduce the number of memory operations.

@kevinjwalters
Copy link

kevinjwalters commented Apr 9, 2020

@tannewt That's funny, I was just went for a stroll and thought of the same thing for the special but common easy case of 0.

I've not looked at exactly how the packing works, will one 32bit chunk work for all cases? For 8 palette colours represented by 3bits does it spill between two 32bit chunks, i.e. would need three different 32 bit values to be written in?

@caternuson
Copy link
Author

@tannewt Sure, might as well make it as fast as possible while we're here. Guess I have the same general question as @kevinjwalters.

@tannewt
Copy link
Member

tannewt commented Apr 10, 2020

bits_per_value is always a power of 2 (so 1, 2 or 4 bits). Inherent in the computation here: https://github.com/adafruit/circuitpython/blob/master/shared-bindings/displayio/Bitmap.c#L66

You may accidentally set values past the end of the row but that is fine because the stride (aka how long a row is) is rounded up to the nearest word boundary.

@caternuson
Copy link
Author

caternuson commented Apr 10, 2020

Something like this? Tested this and seems to work.

    // build the packed word
    uint32_t word = 0;
    for (uint8_t i=0; i<32 / self->bits_per_value; i++) {
        word |= (value & self->bitmask) << (32 - ((i+1)*self->bits_per_value));
    }
    // copy it in
    for (uint32_t i=0; i<self->stride * self->height; i++) {
        self->data[i] = word;
    }

EDIT - oh, and is indeed faster :)

Adafruit CircuitPython 5.1.0-92-gdc7574684-dirty on 2020-04-10; Adafruit CLUE nRF52840 Express with nRF52840
>>> import fill_test
Fill 1
1.32199
Fill 2
0.0059967
>>> 

@tannewt
Copy link
Member

tannewt commented Apr 13, 2020

@caternuson yup! Looks right to me.

@caternuson
Copy link
Author

@tannewt OK, pushed those changes. I think the last remaining question is the treatment of the upper bounds for the dirty area.

This? (current code)

    self->dirty_area.x2 = self->width;
    self->dirty_area.y2 = self->height;

or this?

    self->dirty_area.x2 = self->width - 1;
    self->dirty_area.y2 = self->height - 1;

@kevinjwalters
Copy link

Looks like the original code was correct with x2 and y2 setting judging by comment describing x2 in https://github.com/adafruit/circuitpython/blob/master/shared-module/displayio/area.h#L33-L39 ...

Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current version looks correct. Thanks!

@tannewt tannewt merged commit e063b06 into adafruit:master Apr 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants