How can I store a JPG using micropython and LVGL snapshot?

The PP command is wrong. I just noticed that… It should be


The -E is what tells it to produce the preprocessor output.

also it is a lot easier if you don’t change the file at all. Just tell me whatever errors you get if any.

Better yet save the output to a text file and attach the file to a post so I can look at it.

Yea, but I had to change it, since the file had errors in terms of trying to build things in the wrong directory, the JPEG_HEADER part is all I changed.

I’m always trying it out as-is as well, by going back & forth.

The pycparsing thing worked now and jpeg is available as package, it just doesn’t have functionality or contents though :confused:

# libjpeg-turbo build rules

LIB_JPEG = $(MOD_DIR)/libjpeg-turbo
LIB_JPEG_BUILD = $(MOD_DIR)/libjpeg-turbo/build

LVGL_BINDING_DIR = $(TOP)/lib/lv_bindings

LIB_JPEG_PP = $(BUILD)/libjpeg-turbo/libjpeg_turbo.pp
LIB_JPEG_MPY = $(BUILD)/libjpeg-turbo/libjpeg_turbo_mpy.c
LIB_JPEG_MPY_METADATA = $(BUILD)/libjpeg-turbo/libjpeg_turbo.json

CFLAGS_USERMOD += -Wno-unused-function
CFLAGS_USERMOD += -Wno-missing-field-initializers


LDFLAGS_USERMOD += -l:libjpeg.a

# Use the full path for the JPEG header
JPEG_HEADER = $(LIB_JPEG)/jpeglib.h

	cd $(MOD_DIR)
	git clone
	mkdir -p $(LIB_JPEG_BUILD)


	mkdir -p $(dir $@) 




If you could paste that code into lv_micropython/custom_mod/libjpeg_turbo/

use this for the compile command.

make -j4 -C ports/unix USER_C_MODULES="../../../custom_mod" > build_output.txt

Attach the build_output.txt file to a post.

It is not going to output to the screen which is OK. I need to see the output.

That is the folder the header file should be located in. That is where the repository gets cloned to so that is where it should end up. If it doesn’t then we need to figure out why it’s not going where it is supposed to be going.

These things:

	cd $(MOD_DIR)
	git clone
	mkdir -p $(LIB_JPEG_BUILD)

will not work.

The cd command will just open a shell, change the directory in that shell and then exit. On the next line with git clone, it will open another shell, again in the starting directory.

I left the top part regarding JPEG_HEADER as I described earlier, otherwise directories are all mixed up.

build_output.txt (503 Bytes)

The exit errors aren’t getting reported via the pipe to the text file, so here:

$ make -j4 -C ports/unix USER_C_MODULES="$(pwd)/custom_mod" V=1 > build_output.txt
Traceback (most recent call last):
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/ports/unix/../../lib/lv_bindings/gen/", line 294, in <module>
    ast = parser.parse(s, filename='<none>')
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/", line 147, in parse
    return self.cparser.parse(
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/ply/", line 331, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/ply/", line 1199, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/ply/", line 193, in call_errorfunc
    r = errorfunc(token)
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/", line 1931, in p_error
  File "/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/lib/lv_bindings/gen/../pycparser/pycparser/", line 67, in _parse_error
    raise ParseError("%s: %s" % (coord, msg))
pycparser.plyparser.ParseError: /home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/custom_mod/libjpeg_turbo/libjpeg-turbo/jpeglib.h:841:3: before: size_t
make: *** [/home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/custom_mod/libjpeg_turbo/ build-standard/libjpeg-turbo/libjpeg_turbo_mpy.c] Error 1
make: *** Deleting file 'build-standard/libjpeg-turbo/libjpeg_turbo_mpy.c'

And to prove it doesn’t work, here is with your new and make -j4 -C ports/unix USER_C_MODULES="../../../custom_mod" > build_output.txt
build_output.txt (40.8 KB)

It may compile, but it’s actually not including the libjpeg, since it doesn’t find the folder properly, even though it’s there.

To fix, I’ve adjusted the path, it had one too many of ..:
make -j4 -C ports/unix USER_C_MODULES="../../custom_mod" > build_output.txt

But that brings me back again to the infamous:
pycparser.plyparser.ParseError: /home/rini-debian/git-stash/lvgl-ui-detector/lvgl_ui_generator_v2/lv_micropython/custom_mod/libjpeg_turbo/libjpeg-turbo/jpeglib.h:841:3: before: size_t

Which for some fucking reason goes away after I do the gcc ... > $(LIB_JPEG_PP) manually in the CLI, with that command: (even though it fails, but a subsequent call to the ports/unix is then successful)

gcc -nostdinc -Wno-unused-function -Wno-missing-field-initializers -I$(pwd)/custom_mod/libjpeg_turbo/libjpeg-turbo/build -I$(pwd)/custom_mod/libjpeg_turbo/libjpeg-turbo -DPYCPARSER -x c -I$(pwd)lib/lv_bindings/pycparser/utils/fake_libc_include $(pwd)/custom_mod/libjpeg_turbo/libjpeg-turbo/jpeglib.h > $(pwd)/ports/unix/build-standard/libjpeg-turbo/libjpeg_turbo.pp

I am working on it. It’s a tad but more complicated due to things like size_t and needing to get some of the stdlib stiff included properly

ok so I wanted to let you know that the generator script is not liking the library all that much. It’s not a big deal as I did a quick bit of code that keeps libjpeg-turbo hidden from the generation script. You will have a single function in the module and that function you pass the buffer, filename, width, height and quality and it will encode the jpeg and save the file to disk.

I still have a few kinks to iron out in it but it will work for what you need it to.

I am almost finished. One of the thing you are going to want to install is nasm.

sudo apt-get install nasm

it is an assembly compiler and will result in better performance,

and here it is.

extract this into whatever empty folder you want to direct the user c module to. (2.1 KB)

Then compile and it should work.

I did check to make sure the function is available in the jpeg module and it is.

So to capture a screenshot call the function in the example code below supplying a filename and the “quality” You will have to look into what exactly the quality does. I know it is an integer value that gets passed.

This will capture the currently active screen on the currently active display. If you want to capture single objects you can do that but you will need to modify it so that will work.

This is about as easy as it gets.

The makefile collects libjpeg-turbo into the build folder that is used to compile micropython into. This keeps a single point where all of the working data is placed. Makes it easier when wanting to run clean to clean things up…

import jpeg
import lvgl as lv

def take_screenshot(filename, quality):
    disp = lv.display_get_default()

    width = disp.get_horizontal_resolution()
    height = disp.get_vertical_resolution()

    scrn = lv.screen_active()

    draw_buf = lv.snapshot_take(scrn, lv.COLOR_FORMAT_RGB888)
    # this turns the returned object into a memoryview which is a 
    # pointer to the actual data
    data =
    jpeg.encode(data, filename, width, height, quality)

did this work for you at all??

Hey, I am not able to test on this weekend since I am on holiday, will get to it on Tuesday, sorry for the inconvenience.

OK no worries m8, just wanted to know if you had tried it and if it worked for you. It will definitely be a lot faster than the python version.

It works! Thank you very much!

Will need to test it if the distortion error actually disappeared, but the jpeg module works now:

import jpeg
import lvgl as lv

def bgr_to_rgb(data):
    """Swap the BGR values to RGB in a flat bytearray."""
    # Assume data is a flat bytearray in BGR format
    for i in range(0, len(data), 3):
        data[i], data[i+2] = data[i+2], data[i]  # Swap the B and R values
    return data

def take_screenshot(container: lv.obj, output_file: str, quality:int = 100):
    """Take a screenshot of a container using the LVGL snapshot API and save it to a JPG file."""
    disp = lv.display_get_default()
    width = disp.get_horizontal_resolution()
    height = disp.get_vertical_resolution()
    scrn = lv.screen_active()
    snapshot = lv.snapshot_take(container, lv.COLOR_FORMAT.NATIVE)
    print(f"Snapshot: {snapshot} ({type(snapshot)}, {snapshot.data_size} bytes)")
    buffer =
    data = bgr_to_rgb(buffer)
        jpeg.encode(data, output_file, width, height, quality)
    except MemoryError as e:

It is also a HELL LOT faster!

Unfortunately the distortions still happen, but given how fast the encoding is now, I am quite unsure what the source of the problem is.

A failed example:

Source (using RGB888 instead of manual color swap):

import jpeg
import lvgl as lv

def take_screenshot(output_file: str, quality:int = 100):
    """Take a screenshot of a container using the LVGL snapshot API and save it to a JPG file."""
    disp = lv.display_get_default()
    width = disp.get_horizontal_resolution()
    height = disp.get_vertical_resolution()
    scrn = lv.screen_active()
    snapshot = lv.snapshot_take(scrn, lv.COLOR_FORMAT.RGB888)
    print(f"Snapshot: {snapshot} ({type(snapshot)}, {snapshot.data_size} bytes)")
    data =
        jpeg.encode(data, output_file, width, height, quality)
    except MemoryError as e:

OK so there is an error in LVGL causing this problem that needs to be escalated. We have now tried 2 different ways of handling encoding the JPEG with identical results. So we know 100% it’s not the JPEG encoder. That leaves only LVGL as the source of the problem.

What do you think could be causing this issue with lv_snapshot??

I am also seeing that you are catching memory errors, you must have ran out of heap.

You do know about doing this right?

micropython -X heapsize=1m

You can increase the amount of heap allocation when running micropython. I believe that 1m is the default and you can increase it to however much you want so long as the machine it is running on has the memory available.

I did not know that! Useful information!

I noticed, that the distortion error is not entirely random.

It can be reproduced given certain JSON design files, and it only occurs in design mode. I would not go directly to the conclusion that it happens entirely due to the snapshot API or LVGL internally. But I cannot verify it either, as from a theoretical standpoint, the snapshot should work regardless since the corresponding UI from the JSON can be displayed.

I need to dig into this a little more, but for now or at least the next few days I’m gonna take a bit of a well-deserved pause from the project, since I’ve finished writing the paper.

It’s been quite a few restless nights and days :wink: