adafruit / Adafruit_CircuitPython_BitmapSaver

Save displayio bitmaps to BMP disk
MIT License
5 stars 9 forks source link

unperfect screenshots (do not match a photo of the screen). #8

Closed dglaude closed 3 years ago

dglaude commented 4 years ago

I followed the guide ( https://learn.adafruit.com/saving-bitmap-screenshots-in-circuitpython/overview ) to take a screencapture.

However the result does not match exactly what is in screen.

My code to demonstrate this problem is in this repo: https://github.com/dglaude/CircuitPython_MLX90640_PyGamer_Plus

I believe there are two problems with the above image:

Unfortunately this code require a MLX90640 thermal camera, but I am sure the camera output can be faked to trigger this problem without that hardware.

PS: Notice that 'picture.bmp' is an OK output, but that specify a bitmap and a palette (rather that full screen version without those parameter).

dglaude commented 4 years ago

While I did not narrow down what is wrong, I made a demo that does not require a MLX90640 and just require a PyGamer. This code is 'fake_mlx90640_pygamer+sd.py' and it produce a valid 'picture2.bmp' and a faulty 'screen2.bmp'. It might be possible to make an even smaller example of code that produce that leaking of pixel and unreadable text.

dglaude commented 4 years ago

Here is a small code sample exposing the first problem = the leaking the last column of pixels. There is another problem with the label.

### Adafruit_CircuitPython_BitmapSaver_minimal.py

import time
import board
import busio
import displayio
import adafruit_fancyled.adafruit_fancyled as fancy
import digitalio
import adafruit_sdcard
import storage
from adafruit_bitmapsaver import save_pixels

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
cs = digitalio.DigitalInOut(board.SD_CS)
sdcard = adafruit_sdcard.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")

number_of_colors = 64                          # Number of color in the gradian
palette = displayio.Palette(number_of_colors)  # Palette with all our colors

# gradian for fancyled palette generation
grad = [(0.00, fancy.CRGB(0, 0, 255)), (1.00, fancy.CRGB(255, 0, 0))]    # Blue - Red
fancy_palette = fancy.expand_gradient(grad, number_of_colors)
for c in range(number_of_colors):
    palette[c] = fancy_palette[c].pack()

image_bitmap = displayio.Bitmap(32, 24, number_of_colors)
image_tile= displayio.TileGrid(image_bitmap, pixel_shader=palette)
image_group = displayio.Group(scale=4)
image_group.append(image_tile)
group = displayio.Group(max_size = 8)
group.append(image_group)
board.DISPLAY.show(group)

for h in range(24):
    for w in range(32):
        image_bitmap[w, (23-h)] = h + w

save_pixels('/sd/picture5.bmp', image_bitmap, palette)
save_pixels('/sd/screen5.bmp')

while True:
    time.sleep(1)

screen5.bmp.pdf picture5.bmp.pdf

evaherrada commented 4 years ago

Hi @dglaude were you ever able to figure out why this was happening?

dglaude commented 4 years ago

I did not try to reproduce anymore, and I did not check in the code of the library either. I made code to reproduce the problem, it should be easy to reproduce.

Le jeu. 18 juin 2020 à 01:51, dherrada notifications@github.com a écrit :

Hi @dglaude https://github.com/dglaude were you ever able to figure out why this was happening?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver/issues/8#issuecomment-645685102, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEUJDHHM7I524VXPJSOASSDRXFJGJANCNFSM4KSFYM3A .

kmatch98 commented 3 years ago

I ran into something similar to what @dglaude mentions above. I found a way around it, and maybe that hints to the issue.

I am using the bitmap save directly from the display.

Here is what I got initially, that shows a "streaky" error above the graphics: initial_error_no_background

I added a black bitmap background to the display behind the image and got this: ok_with_black_background

To me, it looks like if the display doesn't have any content (transparent?), then it repeats the pixel color values from the previous row. That is a hint into what is going on.

Whenever the data is written into result_buffer from the display, if there is no Group, TileGrid or VectorIO object at that pixel location, then nothing gets overwritten, so the buffer is not updated in that location and the previous buffer value is retained at that pixel position.

The fundamental issue is in the CircuitPython displayio core functions for fill_area. If an area isn't "owned" by a Group, TileGrid or VectorIO object, then nothing is written into the buffer with fill_area.


A quick fix inside of this library is to reinitialize the result_buffer to all zeros before each call to fill_area. I added a single line:

result_buffer = bytearray(2048)

as the first line in the else statement.

Here's my updated code for _write_pixels in the file adafruit_bitmapsaver.py:

def _write_pixels(output_file, pixel_source, palette):
    saving_bitmap = isinstance(pixel_source, Bitmap)
    width, height = _rotated_height_and_width(pixel_source)
    row_buffer = bytearray(_bytes_per_row(width))
    result_buffer = bytearray(2048)
    for y in range(height, 0, -1):
        buffer_index = 0
        if saving_bitmap:
            for x in range(width):
                pixel = pixel_source[x, y - 1]
                color = palette[pixel]
                for _ in range(3):
                    row_buffer[buffer_index] = color & 0xFF
                    color >>= 8
                    buffer_index += 1
        else:
            result_buffer = bytearray(2048)
            data = pixel_source.fill_row(y - 1, result_buffer)
            for i in range(width):
                pixel565 = (data[i * 2] << 8) + data[i * 2 + 1]
                for b in _rgb565_to_bgr_tuple(pixel565):
                    row_buffer[buffer_index] = b & 0xFF
                    buffer_index += 1
        output_file.write(row_buffer)
        gc.collect()
dglaude commented 3 years ago

Great, this issue is more than one year old and you found an explanation.

kmatch98 commented 3 years ago

Solution was merged in: https://github.com/adafruit/Adafruit_CircuitPython_BitmapSaver/pull/14

hugodahl commented 3 years ago

Solution was merged in: #14

It appears that this issue should be closed as resolved from the previous comment.

jposada202020 commented 3 years ago

@hugodahl Thanks @kmatch98 closing