converting png file to bytearray

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
stijn
Posts: 735
Joined: Thu Apr 24, 2014 9:13 am

Re: converting png file to bytearray

Post by stijn » Thu May 17, 2018 4:58 pm

deshipu wrote:
Thu May 17, 2018 4:16 pm
Which of the 4 different formats described there?
I'm not sure what you mean? The whole page is about the BMP file format (which I assume is the one OutoftheBOTS_ means when saying 'save it as a BMP file') and only mentions some related formats exist without much further description. Or maybe I'm missing something.

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: converting png file to bytearray

Post by OutoftheBOTS_ » Thu May 17, 2018 8:14 pm

I have used this little bit of info here http://paulbourke.net/dataformats/bmp/ as a basis for my use of BMP images and so far it has got the job done that I needed without any hiccups.

In the end the original question was for someone to be able to load an image on a epaper screen. I have definitely found the simplest way to do this is from a BMP file again see my example of a 16 bit bmp file being loaded onto a SPI TFT here https://www.youtube.com/watch?v=4OeWaat3jZs and the waveshare wiki also suggest easiest way is using 1 bit BMP file for their epaper screens see https://www.waveshare.com/wiki/1.54inch_e-Paper_Module

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: converting png file to bytearray

Post by OutoftheBOTS_ » Thu May 17, 2018 8:21 pm

devnull wrote:
Thu May 17, 2018 10:02 am
OK, I am now using mono-bitmaps and are able to convert the image to a bytearray using PIL (pillow.image).

I can also write the image (icon) to the screen at whatever position I require, but when I use the writer class to add text next to it, it appears to overwrite the icon, and vice versa.

But if I repeatedly issue the display_frame() function, then the page toggles between just the icon, and the text frames, it almost appears that there are 2 separate pages.
I am still waiting for some hardware to arrive so I haven't yet played with a epaper but did do a quick read over the datasheet for the hardware that I plan to use and I remember the epaper do have 2 memory buffers (when reading it I though it was a bit strange). Off memory it had something to do with they way the epaper screens do their update of the image.

The epaper screens are definitely a bit more complex to use than the TFTs

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: converting png file to bytearray

Post by OutoftheBOTS_ » Thu May 17, 2018 8:22 pm

here from the waveshare wiki
Display a Frame (DisplayFrame)

DisplayFrame is used to display the data from the frame memory.

Note:

The module has two memory areas. Once DisplayFrame is invoked, the following function SetFrameMemory will set the other memory area, e.g. to set all the two memory areas, the process is: SetFrameMemory --> DisplayFrame --> SetFrameMemory --> DisplayFrame, i.e. set and update twice.
The data from SPI interface is first saved into the memory and then updated if the module received the update command.
The module will flicker during full update.
The module won't flicker during partial update, however, it may retain a "ghost image" of the last page.
So looking at it you have to write everything twice or you get the problem you have

the process is: SetFrameMemory --> DisplayFrame --> SetFrameMemory --> DisplayFrame, i.e. set and update twice.

adomanim
Posts: 3
Joined: Wed Dec 25, 2019 5:55 pm

Re: converting png file to bytearray

Post by adomanim » Wed Dec 25, 2019 5:56 pm

By jpeg byte array, you mean you literally have the image encoded as a jpeg? If so, just dump the bytes to a file and read it back in with

User avatar
MostlyHarmless
Posts: 166
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: converting png file to bytearray

Post by MostlyHarmless » Thu Dec 26, 2019 3:23 am

Correct me if I'm wrong, but all this sounds like you want to display some pixmap or bitmap via a framebuf interface.

If that assumption is wrong, the following might still help someone else.

But if that is what you are asking, then why not use a framebuf object in the source code right away? To demonstrate what I mean I created this little Smile in my favorite bitmap editor (pinta):
smile1_source.png
smile1_source.png (2.5 KiB) Viewed 8208 times
Saved that as a .BMP file. Now I convert that into a very old bitmap format (binary PBM file format 'P4' by Jef Poskanzer). That stuff dates back to the 1980's, but the fun here is that the data of that bitmap is arranged EXACTLY as it is in a MONO_HLSB type MicroPython FrameBuffer.

Code: Select all

convert smile1.bmp smile1.pbm
That convert(1) command comes from ImageMagick, a highly recommended toolbox on Unix if you do anything with images at all.

The smile1.pbm file will contain two '\n' terminated lines. The first one identifying the file format 'P4' (monochrome bitmap binary) and the second '<width> <height>' in decimal, space separated. The remaining bytes in that file are the binary bitmap data.

Code: Select all

$ head -2 smile1.pbm 
P4
24 23
Yeah, I couldn't even draw a perfect circle and note that one pixel on the left cheek is missing ... darn.

Anyhow, that file format is trivial to read, so a rather simple Python script

Code: Select all

#!/usr/bin/env python

import sys
import os

def main():
    if len(sys.argv) != 2:
        usage()
        return 2

    with open(sys.argv[1], 'rb') as fd:
        pbm_format = fd.readline().strip()
        if pbm_format != b'P4':
            print("ERROR: input file must be binary PBM (type P4)",
                  file = sys.stderr)
            return 1
        pbm_dims = [int(d) for d in fd.readline().strip().split()]
        pbm_data = fd.read()

    fbbase = "fb_{0}".format(os.path.basename(sys.argv[1]))
    fbname = os.path.splitext(fbbase)[0]
    with sys.stdout as fd:
        f = "{0} = framebuf.FrameBuffer(bytearray({1}), {2}, {3}, framebuf.MONO_HLSB)\n"
        fd.write(f.format(fbname, str(pbm_data), pbm_dims[0], pbm_dims[1]))


def usage():
    print("""usage: {0} PBM_FILE""".format(os.path.basename(sys.argv[0])),
          file = sys.stderr)

if __name__ == '__main__':
    main()
will convert that smile1.pbm file into the following output:

Code: Select all

fb_smile1 = framebuf.FrameBuffer(bytearray(b'\x00~\x00\x03\xff\xc0\x07\x81\xe0\x1e\x00x8\x00\x1c0\x00\x0c`\x00\x0ea\xc3\x86\xe0\x00\x07\xc0\x00\x03\xc0\x00\x03\xc0\x00\x02\xc0\x00\x03\xc0\x00\x03\xe0B\x07`<\x06`\x00\x060\x00\x0c8\x00\x1c\x1e\x00x\x07\x81\xe0\x03\xff\xc0\x00\xff\x00'), 24, 23, framebuf.MONO_HLSB)
Now fb_smile1 is a FrameBuffer object, 24x23 pixels in MONO_HLSB format. So the MicroPython script

Code: Select all

from machine import I2C, Pin
import ssd1306
import framebuf

i2c = I2C(-1, scl=Pin(22), sda=Pin(21))
oled = ssd1306.SSD1306_I2C(128, 64, i2c)

fb_smile1 = framebuf.FrameBuffer(bytearray(b'\x00~\x00\x03\xff\xc0\x07\x81\xe0\x1e\x00x8\x00\x1c0\x00\x0c`\x00\x0ea\xc3\x86\xe0\x00\x07\xc0\x00\x03\xc0\x00\x03\xc0\x00\x02\xc0\x00\x03\xc0\x00\x03\xe0B\x07`<\x06`\x00\x060\x00\x0c8\x00\x1c\x1e\x00x\x07\x81\xe0\x03\xff\xc0\x00\xff\x00'), 24, 23, framebuf.MONO_HLSB)

oled.fill(0)
oled.framebuf.blit(fb_smile1, 0, 20)
oled.text("Hello", 30, 20)
oled.text("MicroPython", 30, 28)
oled.show()
will produce this display (still missing one pixel on the left cheek):
IMG_20191225_211256.jpg
IMG_20191225_211256.jpg (228.54 KiB) Viewed 8208 times
Is that what you were looking for?

I am sure the same technique could be adopted for display types with more bits per pixel (grayscale or color). Although the conversion might get a bit more complicated than this monochrome example. It might also help to convert the FrameBuffer to the display's native format (the SSD1306 is MONO_VLSB, not MONO_HLSB), but I haven't studied the FrameBuffer C code enough to say for sure.


Regards, Jan

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: converting png file to bytearray

Post by pythoncoder » Thu Dec 26, 2019 7:39 am

Very useful.

If you wanted to take your idea further, your conversion utility could output Python source, like font_to_py and data_to_py. The idea behind these utilities is that, with suitable design of the output format, the Python source can be frozen as bytecode so that the data uses almost no RAM on the target. The key is to use memoryview objects.

There is a significant roadblock when it comes to colour displays.

I use the same technique as you for rendering fonts created using font_to_py: glyphs are rendered to a framebuf then copied to the display's framebuf using the blit method. It's fast and efficient provided your display is monochrome.

Alas there is a problem with colour. The blit method doesn't work properly when the source and destination buffers have different colour maps. This has been discussed several times - a full solution is nontrivial. A restricted solution where a monochrome source is mapped onto a colour destination would work fine for fonts and be easily implemented but my offer to do so was not taken up, presumably because the long term aim is to provide a full solution.

In consequence rendering a mono glyph onto a colour display has to be done pixel-by-pixel.
Peter Hinch
Index to my micropython libraries.

User avatar
MostlyHarmless
Posts: 166
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: converting png file to bytearray

Post by MostlyHarmless » Thu Dec 26, 2019 3:06 pm

pythoncoder wrote:
Thu Dec 26, 2019 7:39 am
Very useful.

If you wanted to take your idea further, your conversion utility could output Python source, like font_to_py and data_to_py. The idea behind these utilities is that, with suitable design of the output format, the Python source can be frozen as bytecode so that the data uses almost no RAM on the target. The key is to use memoryview objects.
I'd like to, but there are design questions to ponder first.

For number one I am still new to MicroPython. Let's assume freezing for all of the following (bitmaps that eventually make it onto a display once in a while should not be kept in RAM).

I took a look at the courier20.py file, included in your font_to_py. I presume that creating _font=b'...' on the module level creates one dict entry '_font' in RAM on import, but the bytes themselves stay in flash. The memoryview object '_mvfont' then creates another dict entry in RAM, but allows to copy a single glyph into RAM via get_ch() without first loading the entire _font object. Correct?

The next question is how to best make many bitmaps available with minimal resources. In a font the ord of a character translated to an index is a natural choice. However, that will not work for bitmaps as they are more user friendly if referred to by name. I would still like to copy your design in a way, but instead of using the _index data, generate individual functions per bitmap. So given multiple input bitmap files, the utility would create output like:

Code: Select all

_bmdata = b'...'
_mvdata = memoryview(_bmdata)

def get_<filename1>():
    return _mvdata[<start>, <end>], <width>, <height>
    
def get_<filename2>():
    return _mvdata[<start>, <end>], <width>, <height>
where <filenameN> is the basename of the original bitmap file minus extension for simplicity. This would still add a dict entry per bitmap into RAM, but even the <start>, <end>, &c information would be frozen byte code.

Another idea is to expand this with "indexed" bitmap versions. Think of a "battery" symbol that has 5 different states. 0%, 25%, 50%, 75% and 100%. It would be very convenient to get the right bitmap with

Code: Select all

mybitmaps.get_battery(round(bat_level / 25))
That would lean itself more to a configuration file driven design than a pure command line option driven tool.
pythoncoder wrote:
Thu Dec 26, 2019 7:39 am
In consequence rendering a mono glyph onto a colour display has to be done pixel-by-pixel.
Supporting color with all this is definitely going to be a challenge. At this point I presume that the Python code has to be generated with a specific target display type, and cannot be converted on the fly on board. Restricting text display to a single color per write is a compromise I can live with. Rendering an actual image that way is not and converting between color spaces per pixel is going to be too CPU expensive.


Thanks, Jan

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: converting png file to bytearray

Post by pythoncoder » Thu Dec 26, 2019 4:01 pm

MostlyHarmless wrote:
Thu Dec 26, 2019 3:06 pm
...I presume that creating _font=b'...' on the module level creates one dict entry '_font' in RAM on import, but the bytes themselves stay in flash. The memoryview object '_mvfont' then creates another dict entry in RAM...
OK so far, but
allows to copy a single glyph into RAM via get_ch() without first loading the entire _font object
The wonder of a memoryview is allocation-free slicing: the glyph remains in Flash with Python working on pointers into the ROM. RAM use is zero!

I'll think over how to index bitmaps. If you used numeric indices you could, of course, use a simple indexing scheme. Mine which is crafted for managing sparse number spaces; your indices would be contiguous. You could get round the unfriendliness of numbers with named constants. The utility would produce two files, one with Python source and the other with text like:

Code: Select all

_HAPPY = const(0)
_SAD = const(1)
...
Your user code would include the constants by cutting and pasting the text into your source. The point here is that constants with a leading underscore use no RAM whatsoever. This is because of the Python rule about names with leading underscores, which is the reason why you must cut and paste rather than importing the module.

You may think this is clunky. I'll think about this some more...
Peter Hinch
Index to my micropython libraries.

User avatar
MostlyHarmless
Posts: 166
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: converting png file to bytearray

Post by MostlyHarmless » Thu Dec 26, 2019 4:22 pm

pythoncoder wrote:
Thu Dec 26, 2019 4:01 pm
The wonder of a memoryview is allocation-free slicing: the glyph remains in Flash with Python working on pointers into the ROM. RAM use is zero!
Even better. I like that!
pythoncoder wrote:
Thu Dec 26, 2019 4:01 pm
I'll think over how to index bitmaps. If you used numeric indices you could, of course, use a simple indexing scheme. Mine which is crafted for managing sparse number spaces; your indices would be contiguous. You could get round the unfriendliness of numbers with named constants. The utility would produce two files, one with Python source and the other with text like:

Code: Select all

_HAPPY = const(0)
_SAD = const(1)
...
Your user code would include the constants by cutting and pasting the text into your source. The point here is that constants with a leading underscore use no RAM whatsoever. This is because of the Python rule about names with leading underscores, which is the reason why you must cut and paste rather than importing the module.

You may think this is clunky. I'll think about this some more...
The top goal about this design aspect is to conserve resources (RAM primarily). So "clunky" is OK with me.

The thing I don't like about this approach is that with generated code, the numbers associated with a given bitmap tend to change, which will completely mess up the source code that copy/pasted those const() declarations.

This makes two of us thinking more about it.


Thanks a lot, Jan

Post Reply