├── tools ├── lib │ ├── __init__.py │ ├── codegen.py │ └── rle.py ├── make_images.py └── make_var_font.py ├── .gitignore ├── examples ├── lowlevel │ ├── CMakeLists.txt │ └── buffer_direct │ │ ├── CMakeLists.txt │ │ └── main.c ├── midlevel │ ├── mapscroll │ │ ├── Makefile │ │ ├── map.png │ │ ├── map.yaml │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── map.h │ ├── bitmapblit │ │ ├── Makefile │ │ ├── rose.png │ │ ├── rose.yaml │ │ ├── rose.h │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── bitmapimage │ │ ├── pnp.png │ │ ├── vase.png │ │ ├── diploma.png │ │ ├── Makefile │ │ ├── file_search.png │ │ ├── images.yaml │ │ ├── images.h │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── doublebuffer │ │ ├── common.h │ │ ├── ball.h │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ └── ball.c │ ├── CMakeLists.txt │ ├── scroll_text │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── hello_world │ │ ├── CMakeLists.txt │ │ └── main.c │ ├── metrics │ │ ├── CMakeLists.txt │ │ └── main.c │ └── bitmapshapes │ │ ├── CMakeLists.txt │ │ └── main.c ├── CMakeLists.txt └── highlevel │ ├── CMakeLists.txt │ ├── console_count │ ├── CMakeLists.txt │ └── main.c │ └── console_advanced_setup │ ├── CMakeLists.txt │ └── main.c ├── bootstrap.sh ├── images ├── rose.jpg ├── test.jpg ├── console.jpg ├── low_level.jpg ├── map_scroll.jpg ├── rose_blit.jpg ├── ball_bounce.jpg └── hello_world.jpg ├── test ├── medium.png ├── small.png ├── images.yaml ├── Makefile ├── images.h ├── common.h ├── CMakeLists.txt ├── common.c ├── consoletests.c ├── gen_testnames.py ├── images.c ├── imagetests.c ├── main.c ├── texttests.c └── tests.inc ├── fonts ├── Makefile ├── liberation_mono_10.h ├── liberation_mono_9.h ├── liberation_mono_12.h ├── liberation_mono_14.h ├── liberation_mono_24.h ├── liberation_mono_32.h ├── liberation_sans_12.h ├── liberation_sans_18.h ├── liberation_sans_24.h ├── liberation_sans_36.h ├── liberation_sans_48.h ├── liberation_sans_60.h ├── liberation_sans_80.h ├── liberation_sans_48.yaml ├── liberation_sans_60.yaml ├── liberation_sans_80.yaml ├── liberation_mono_12.yaml ├── liberation_mono_9.yaml ├── liberation_mono_10.yaml ├── liberation_mono_14.yaml ├── liberation_mono_24.yaml ├── liberation_mono_32.yaml ├── liberation_sans_12.yaml ├── liberation_sans_18.yaml ├── liberation_sans_24.yaml ├── liberation_sans_36.yaml └── CMakeLists.txt ├── src ├── rle.h ├── metrics.c ├── bitmapconsole.c ├── rle.c ├── sharpconsole.c ├── bitmap.c ├── CMakeLists.txt ├── sharpdisp.c ├── bitmaptext.c ├── doublebuffer.c └── bitmapimage.c ├── CMakeLists.txt └── include └── sharpdisp ├── bitmapconsole.h ├── metrics.h ├── sharpdisp.h ├── sharpconsole.h ├── bitmapshapes.h ├── bitmaptext.h ├── bitmapimage.h ├── doublebuffer.h └── bitmap.h /tools/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /examples/lowlevel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(buffer_direct) 2 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | rm -rf build 3 | mkdir build 4 | cd build 5 | cmake .. 6 | 7 | -------------------------------------------------------------------------------- /images/rose.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/rose.jpg -------------------------------------------------------------------------------- /images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/test.jpg -------------------------------------------------------------------------------- /test/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/test/medium.png -------------------------------------------------------------------------------- /test/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/test/small.png -------------------------------------------------------------------------------- /images/console.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/console.jpg -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/Makefile: -------------------------------------------------------------------------------- 1 | map.c: map.yaml map.png 2 | ../../../tools/make_images.py map.yaml 3 | -------------------------------------------------------------------------------- /images/low_level.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/low_level.jpg -------------------------------------------------------------------------------- /images/map_scroll.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/map_scroll.jpg -------------------------------------------------------------------------------- /images/rose_blit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/rose_blit.jpg -------------------------------------------------------------------------------- /examples/midlevel/bitmapblit/Makefile: -------------------------------------------------------------------------------- 1 | rose.c: rose.yaml rose.png 2 | ../../../tools/make_images.py rose.yaml 3 | -------------------------------------------------------------------------------- /images/ball_bounce.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/ball_bounce.jpg -------------------------------------------------------------------------------- /images/hello_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/images/hello_world.jpg -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(lowlevel) 2 | add_subdirectory(midlevel) 3 | add_subdirectory(highlevel) 4 | -------------------------------------------------------------------------------- /examples/highlevel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(console_count) 2 | add_subdirectory(console_advanced_setup) 3 | 4 | -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/mapscroll/map.png -------------------------------------------------------------------------------- /examples/midlevel/bitmapblit/rose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/bitmapblit/rose.png -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/pnp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/bitmapimage/pnp.png -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/vase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/bitmapimage/vase.png -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/diploma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/bitmapimage/diploma.png -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/Makefile: -------------------------------------------------------------------------------- 1 | IMAGES := $(wildcard *.png) 2 | images.c: images.yaml $(IMAGES) 3 | ../../../tools/make_images.py images.yaml 4 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/file_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattwach/pico_sharpmem_display/HEAD/examples/midlevel/bitmapimage/file_search.png -------------------------------------------------------------------------------- /fonts/Makefile: -------------------------------------------------------------------------------- 1 | FONTS := $(wildcard *.yaml) 2 | TARGETS := $(foreach F,$(FONTS),$(subst .yaml,.c,$F)) 3 | 4 | fonts: $(TARGETS) 5 | 6 | %.c: %.yaml 7 | ../tools/make_var_font.py $*.yaml 8 | -------------------------------------------------------------------------------- /test/images.yaml: -------------------------------------------------------------------------------- 1 | output_type: SharpMemoryImage 2 | 3 | images: 4 | - path: small.png 5 | - path: medium.png 6 | - path: medium.png 7 | tile_x: 8 8 | tile_y: 6 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/midlevel/doublebuffer/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_HEADER 2 | #define COMMON_HEADER 3 | 4 | #define WIDTH 400 // 400 pixels 5 | #define HEIGHT 240 6 | #define RADIUS 10 7 | 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /examples/midlevel/doublebuffer/ball.h: -------------------------------------------------------------------------------- 1 | #ifndef BALL_HEADER 2 | #define BALL_HEADER 3 | 4 | #include 5 | 6 | void init_balls(struct Bitmap* bitmap); 7 | 8 | void draw_balls(void); 9 | 10 | #endif 11 | 12 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapblit/rose.yaml: -------------------------------------------------------------------------------- 1 | output_type: SharpMemoryImage 2 | 3 | # The rest is a concatenated set of sections. If any section 4 | # overlaps a previous section, an error will be raised. 5 | images: 6 | - path: rose.png 7 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | FILES=$(wildcard *tests.c) 2 | IMAGES := $(wildcard *.png) 3 | 4 | all: tests.inc images.c 5 | 6 | tests.inc: $(FILES) gen_testnames.py 7 | ./gen_testnames.py 8 | 9 | images.c: images.yaml $(IMAGES) 10 | ../tools/make_images.py images.yaml 11 | -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/map.yaml: -------------------------------------------------------------------------------- 1 | output_type: SharpMemoryImage 2 | 3 | # The rest is a concatenated set of sections. If any section 4 | # overlaps a previous section, an error will be raised. 5 | images: 6 | - path: map.png 7 | tile_x: 64 8 | tile_y: 64 9 | 10 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapblit/rose.h: -------------------------------------------------------------------------------- 1 | #ifndef ROSE_H 2 | #define ROSE_H 3 | // Generated data for rose.yaml 4 | 5 | #define ROSE_IMG 0 6 | 7 | #include 8 | #include 9 | 10 | extern const uint8_t rose[] __in_flash(); 11 | 12 | #endif // ROSE_H 13 | -------------------------------------------------------------------------------- /examples/midlevel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(bitmapblit) 2 | add_subdirectory(bitmapimage) 3 | add_subdirectory(bitmapshapes) 4 | add_subdirectory(doublebuffer) 5 | add_subdirectory(hello_world) 6 | add_subdirectory(mapscroll) 7 | add_subdirectory(metrics) 8 | add_subdirectory(scroll_text) 9 | 10 | -------------------------------------------------------------------------------- /fonts/liberation_mono_10.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_10_H 2 | #define LIBERATION_MONO_10_H 3 | // Generated data for liberation_mono_10.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_10[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_10_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_mono_9.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_9_H 2 | #define LIBERATION_MONO_9_H 3 | // Generated font data for liberation_mono_9.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_9[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_9_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_mono_12.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_12_H 2 | #define LIBERATION_MONO_12_H 3 | // Generated font data for liberation_mono_12.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_12[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_12_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_mono_14.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_14_H 2 | #define LIBERATION_MONO_14_H 3 | // Generated font data for liberation_mono_14.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_14[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_14_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_mono_24.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_24_H 2 | #define LIBERATION_MONO_24_H 3 | // Generated font data for liberation_mono_24.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_24[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_24_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_mono_32.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_MONO_32_H 2 | #define LIBERATION_MONO_32_H 3 | // Generated font data for liberation_mono_32.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_mono_32[] __in_flash(); 9 | 10 | #endif // LIBERATION_MONO_32_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_12.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_12_H 2 | #define LIBERATION_SANS_12_H 3 | // Generated font data for liberation_sans_12.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_12[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_12_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_18.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_18_H 2 | #define LIBERATION_SANS_18_H 3 | // Generated font data for liberation_sans_18.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_18[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_18_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_24.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_24_H 2 | #define LIBERATION_SANS_24_H 3 | // Generated font data for liberation_sans_24.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_24[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_24_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_36.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_36_H 2 | #define LIBERATION_SANS_36_H 3 | // Generated font data for liberation_sans_36.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_36[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_36_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_48.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_48_H 2 | #define LIBERATION_SANS_48_H 3 | // Generated font data for liberation_sans_48.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_48[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_48_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_60.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_60_H 2 | #define LIBERATION_SANS_60_H 3 | // Generated font data for liberation_sans_60.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_60[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_60_H 11 | -------------------------------------------------------------------------------- /fonts/liberation_sans_80.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBERATION_SANS_80_H 2 | #define LIBERATION_SANS_80_H 3 | // Generated font data for liberation_sans_80.yaml 4 | 5 | #include 6 | #include 7 | 8 | extern const uint8_t liberation_sans_80[] __in_flash(); 9 | 10 | #endif // LIBERATION_SANS_80_H 11 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/images.yaml: -------------------------------------------------------------------------------- 1 | output_type: SharpMemoryImage 2 | 3 | # The rest is a concatenated set of sections. If any section 4 | # overlaps a previous section, an error will be raised. 5 | images: 6 | - path: diploma.png 7 | - path: file_search.png 8 | - path: pnp.png 9 | - path: vase.png 10 | -------------------------------------------------------------------------------- /src/rle.h: -------------------------------------------------------------------------------- 1 | #ifndef SHARPDISP_RLE_H 2 | #define SHARPDISP_RLE_H 3 | 4 | #include 5 | #include "sharpdisp/bitmap.h" 6 | 7 | #define RLE_BAD_DATA 0x02 8 | 9 | void map_rle_image( 10 | struct Bitmap* bitmap, 11 | const uint8_t* pgm_data, 12 | int16_t x, 13 | int16_t y, 14 | uint16_t width, 15 | uint16_t height, 16 | uint8_t* error 17 | ); 18 | 19 | #endif -------------------------------------------------------------------------------- /fonts/liberation_sans_48.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 48 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | y_offset: -6 13 | font_size: 48 14 | -------------------------------------------------------------------------------- /fonts/liberation_sans_60.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 60 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | y_offset: -8 13 | font_size: 60 14 | -------------------------------------------------------------------------------- /fonts/liberation_sans_80.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 80 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | y_offset: -10 13 | font_size: 80 14 | -------------------------------------------------------------------------------- /fonts/liberation_mono_12.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 12 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 8 13 | y_offset: 0 14 | font_size: 12 15 | -------------------------------------------------------------------------------- /fonts/liberation_mono_9.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 9 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 7 13 | y_offset: -1 14 | font_size: 9 15 | -------------------------------------------------------------------------------- /fonts/liberation_mono_10.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 10 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 7 13 | y_offset: -1 14 | font_size: 10 15 | -------------------------------------------------------------------------------- /fonts/liberation_mono_14.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 14 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 10 13 | y_offset: -1 14 | font_size: 14 15 | -------------------------------------------------------------------------------- /fonts/liberation_mono_24.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 24 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 16 13 | y_offset: 0 14 | font_size: 24 15 | -------------------------------------------------------------------------------- /fonts/liberation_mono_32.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 32 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationMono-Regular.ttf 12 | col_width: 20 13 | y_offset: 0 14 | font_size: 32 15 | -------------------------------------------------------------------------------- /fonts/liberation_sans_12.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 12 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | right_trim: 1 13 | y_offset: -1 14 | font_size: 12 15 | -------------------------------------------------------------------------------- /fonts/liberation_sans_18.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 18 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | right_trim: 1 13 | y_offset: -3 14 | font_size: 18 15 | -------------------------------------------------------------------------------- /fonts/liberation_sans_24.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 24 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | right_trim: 1 13 | y_offset: -3 14 | font_size: 24 15 | -------------------------------------------------------------------------------- /fonts/liberation_sans_36.yaml: -------------------------------------------------------------------------------- 1 | # Use make_var_font.py 2 | output_type: SharpMemoryFont 3 | 4 | # Height is in pixels and applies to every character 5 | height: 36 6 | 7 | # The rest is a concatenated set of sections. If any section 8 | # overlaps a previous section, an error will be raised. 9 | sections: 10 | - type: ttf 11 | path: LiberationSans-Regular.ttf 12 | right_trim: 2 13 | y_offset: -5 14 | font_size: 36 15 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/images.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGES_H 2 | #define IMAGES_H 3 | // Generated data for images.yaml 4 | 5 | #define DIPLOMA_IMG 0 6 | #define FILE_SEARCH_IMG 1 7 | #define PNP_IMG 2 8 | #define VASE_IMG 3 9 | 10 | #include 11 | #include 12 | 13 | extern const uint8_t images[] __in_flash(); 14 | 15 | #endif // IMAGES_H 16 | -------------------------------------------------------------------------------- /examples/lowlevel/buffer_direct/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_buffer_direct 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_buffer_direct 8 | pico_stdlib 9 | SHARPDISP 10 | ) 11 | 12 | # enable usb output, disable uart output 13 | #pico_enable_stdio_usb(sharpdisp_buffer_direct 1) 14 | #pico_enable_stdio_uart(sharpdisp_buffer_direct 0) 15 | 16 | pico_add_extra_outputs(sharpdisp_buffer_direct) 17 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_bitmapimage 2 | images.c 3 | main.c 4 | ) 5 | 6 | 7 | # pull in common dependencies 8 | target_link_libraries(sharpdisp_bitmapimage 9 | pico_stdlib 10 | SHARPDISP 11 | BITMAPIMAGE 12 | ) 13 | 14 | # enable usb output, disable uart output 15 | #pico_enable_stdio_usb(sharpdisp_bitmapimage 1) 16 | #pico_enable_stdio_uart(sharpdisp_bitmapimage 0) 17 | 18 | pico_add_extra_outputs(sharpdisp_bitmapimage) 19 | -------------------------------------------------------------------------------- /examples/highlevel/console_count/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_console_count 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_console_count 8 | pico_stdlib 9 | SHARPCONSOLE 10 | SHARPCONSOLE_DEFAULT_FONT 11 | ) 12 | 13 | # enable usb output, disable uart output 14 | #pico_enable_stdio_usb(sharpdisp_console_count 1) 15 | #pico_enable_stdio_uart(sharpdisp_console_count 0) 16 | 17 | pico_add_extra_outputs(sharpdisp_console_count) 18 | -------------------------------------------------------------------------------- /examples/midlevel/scroll_text/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_scroll_text 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_scroll_text 8 | pico_stdlib 9 | SHARPDISP 10 | BITMAPTEXT 11 | LIBERATION_SANS_80 12 | ) 13 | 14 | # enable usb output, disable uart output 15 | #pico_enable_stdio_usb(sharpdisp_scroll_text 1) 16 | #pico_enable_stdio_uart(sharpdisp_scroll_text 0) 17 | 18 | pico_add_extra_outputs(sharpdisp_scroll_text) 19 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapblit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_bitmapblit 2 | rose.c 3 | main.c 4 | ) 5 | 6 | 7 | # pull in common dependencies 8 | target_link_libraries(sharpdisp_bitmapblit 9 | pico_stdlib 10 | SHARPDISP 11 | BITMAPIMAGE 12 | BITMAPSHAPES 13 | ) 14 | 15 | # enable usb output, disable uart output 16 | #pico_enable_stdio_usb(sharpdisp_bitmapblit 1) 17 | #pico_enable_stdio_uart(sharpdisp_bitmapblit 0) 18 | 19 | pico_add_extra_outputs(sharpdisp_bitmapblit) 20 | -------------------------------------------------------------------------------- /examples/midlevel/hello_world/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_hello_world 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_hello_world 8 | pico_stdlib 9 | SHARPDISP 10 | BITMAPSHAPES 11 | BITMAPTEXT 12 | LIBERATION_SANS_36 13 | ) 14 | 15 | # enable usb output, disable uart output 16 | #pico_enable_stdio_usb(sharpdisp_hello_world 1) 17 | #pico_enable_stdio_uart(sharpdisp_hello_world 0) 18 | 19 | pico_add_extra_outputs(sharpdisp_hello_world) 20 | -------------------------------------------------------------------------------- /examples/midlevel/metrics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_metrics 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_metrics 8 | pico_stdlib 9 | SHARPDISP 10 | SHARPMETRICS 11 | BITMAPTEXT 12 | BITMAPSHAPES 13 | LIBERATION_SANS_18 14 | ) 15 | 16 | # enable usb output, disable uart output 17 | #pico_enable_stdio_usb(sharpdisp_metrics 1) 18 | #pico_enable_stdio_uart(sharpdisp_metrics 0) 19 | 20 | pico_add_extra_outputs(sharpdisp_metrics) 21 | -------------------------------------------------------------------------------- /examples/highlevel/console_count/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | 4 | #define WIDTH 400 5 | #define HEIGHT 240 6 | 7 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 8 | 9 | int main() { 10 | sleep_ms(100); // allow voltage to stabilize 11 | struct Console c; 12 | sharpconsole_init_default(&c, disp_buffer, WIDTH, HEIGHT); 13 | uint32_t i=0; 14 | while (1) { 15 | for (uint8_t j=0; j<6; ++j,++i) { 16 | sharpconsole_printf(&c, "%d ", i); 17 | } 18 | sharpconsole_char(&c, '\n'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/highlevel/console_advanced_setup/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_console_advanced_setup 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_console_advanced_setup 8 | pico_stdlib 9 | SHARPCONSOLE 10 | SHARPDISP 11 | LIBERATION_SANS_18 12 | ) 13 | 14 | # enable usb output, disable uart output 15 | #pico_enable_stdio_usb(sharpdisp_console_advanced_setup 1) 16 | #pico_enable_stdio_uart(sharpdisp_console_advanced_setup 0) 17 | 18 | pico_add_extra_outputs(sharpdisp_console_advanced_setup) 19 | -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_mapscroll 2 | map.c 3 | main.c 4 | ) 5 | 6 | 7 | # pull in common dependencies 8 | target_link_libraries(sharpdisp_mapscroll 9 | pico_stdlib 10 | SHARPDISP 11 | SHARPDOUBLEBUFFER 12 | BITMAPIMAGE 13 | SHARPMETRICS 14 | BITMAPTEXT 15 | LIBERATION_MONO_12 16 | ) 17 | 18 | # enable usb output, disable uart output 19 | #pico_enable_stdio_usb(sharpdisp_mapscroll 1) 20 | #pico_enable_stdio_uart(sharpdisp_mapscroll 0) 21 | 22 | pico_add_extra_outputs(sharpdisp_mapscroll) 23 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapshapes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_bitmapshapes 2 | main.c 3 | ) 4 | 5 | 6 | # pull in common dependencies 7 | target_link_libraries(sharpdisp_bitmapshapes 8 | pico_stdlib 9 | SHARPDISP 10 | SHARPDOUBLEBUFFER 11 | BITMAPSHAPES 12 | BITMAPTEXT 13 | LIBERATION_MONO_12 14 | LIBERATION_SANS_24 15 | ) 16 | 17 | # enable usb output, disable uart output 18 | #pico_enable_stdio_usb(sharpdisp_bitmapshapes 1) 19 | #pico_enable_stdio_uart(sharpdisp_bitmapshapes 0) 20 | 21 | pico_add_extra_outputs(sharpdisp_bitmapshapes) 22 | -------------------------------------------------------------------------------- /examples/midlevel/doublebuffer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_doublebuffer 2 | ball.c 3 | main.c 4 | ) 5 | 6 | 7 | # pull in common dependencies 8 | target_link_libraries(sharpdisp_doublebuffer 9 | pico_stdlib 10 | SHARPDISP 11 | SHARPDOUBLEBUFFER 12 | SHARPMETRICS 13 | BITMAPTEXT 14 | BITMAPSHAPES 15 | LIBERATION_SANS_18 16 | LIBERATION_MONO_14 17 | ) 18 | 19 | # enable usb output, disable uart output 20 | #pico_enable_stdio_usb(sharpdisp_doublebuffer 1) 21 | #pico_enable_stdio_uart(sharpdisp_doublebuffer 0) 22 | 23 | pico_add_extra_outputs(sharpdisp_doublebuffer) 24 | -------------------------------------------------------------------------------- /test/images.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGES_H 2 | #define IMAGES_H 3 | // Generated data for images.yaml 4 | 5 | #define MEDIUM_IMG_COLUMNS 2 6 | #define MEDIUM_IMG_ROWS 3 7 | #define SMALL_IMG 0 8 | #define MEDIUM_IMG 1 9 | #define MEDIUM_IMG_0_0 2 10 | #define MEDIUM_IMG_1_0 3 11 | #define MEDIUM_IMG_0_1 4 12 | #define MEDIUM_IMG_1_1 5 13 | #define MEDIUM_IMG_0_2 6 14 | #define MEDIUM_IMG_1_2 7 15 | 16 | #include 17 | #include 18 | 19 | extern const uint8_t images[] __in_flash(); 20 | 21 | #endif // IMAGES_H 22 | -------------------------------------------------------------------------------- /test/common.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTATNS_H 3 | 4 | #include 5 | #include 6 | 7 | #define DISPLAY_WIDTH 400 8 | #define DISPLAY_HEIGHT 240 9 | #define WIDTH 35 10 | #define HEIGHT 25 11 | 12 | struct SamplePoint { 13 | uint8_t x; 14 | uint8_t y; 15 | uint8_t value; // 0 or 1 16 | }; 17 | 18 | struct TestData { 19 | uint16_t on_count; // number of pixels that should be active 20 | uint16_t num_samples; 21 | const struct SamplePoint samples[]; 22 | }; 23 | 24 | // Utility functions 25 | int16_t rand16(int16_t min, int16_t max); 26 | // assert with a printf-syntax for failure 27 | void assert_true(struct TestData* t, uint8_t condition, const char* fmt, ...); 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(sharpdisp_test_alldraw 2 | main.c 3 | bitmaptests.c 4 | consoletests.c 5 | common.c 6 | images.c 7 | imagetests.c 8 | shapetests.c 9 | texttests.c 10 | ) 11 | 12 | 13 | # pull in common dependencies 14 | target_link_libraries(sharpdisp_test_alldraw 15 | pico_stdlib 16 | SHARPDISP 17 | SHARPDOUBLEBUFFER 18 | BITMAPCONSOLE 19 | BITMAPIMAGE 20 | BITMAPSHAPES 21 | BITMAPTEXT 22 | LIBERATION_MONO_10 23 | LIBERATION_SANS_18 24 | ) 25 | 26 | # enable usb output, disable uart output 27 | pico_enable_stdio_usb(sharpdisp_test_alldraw 1) 28 | #pico_enable_stdio_uart(sharpdisp_bitmapimage 0) 29 | 30 | pico_add_extra_outputs(sharpdisp_test_alldraw) 31 | -------------------------------------------------------------------------------- /test/common.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #include "hardware/structs/rosc.h" 5 | 6 | // some good-enough random generation 7 | int16_t rand16(int16_t min, int16_t max) { 8 | min *= 2; 9 | max *= 2; 10 | uint16_t v = 0x0000; 11 | for (int i=0; i<16; ++i, v<<=1) { 12 | if (rosc_hw->randombit) { 13 | v |= 0x0001; 14 | } 15 | } 16 | return (min + (v % (max - min))) / 2; 17 | } 18 | 19 | void assert_true(struct TestData* t, uint8_t condition, const char* fmt, ...) { 20 | if (!condition) { 21 | t->on_count = 10000; // the real bitmap can not get this high 22 | printf(" assert failure: "); 23 | va_list args; 24 | va_start(args, fmt); 25 | vprintf(fmt, args); 26 | va_end(args); 27 | printf("\n"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/metrics.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/metrics.h" 2 | #include "pico/stdlib.h" 3 | #include 4 | 5 | static inline uint32_t uptime_ms() { 6 | return to_ms_since_boot(get_absolute_time()); 7 | } 8 | 9 | void metrics_init(struct SharpMetrics* m) { 10 | memset(m, 0, sizeof(struct SharpMetrics)); 11 | } 12 | 13 | void metrics_start(struct SharpMetrics* m) { 14 | m->next_start_ms = uptime_ms(); 15 | } 16 | 17 | void metrics_prerefresh(struct SharpMetrics* m) { 18 | m->start_ms = m->next_start_ms; 19 | m->start_refresh_ms = uptime_ms(); 20 | } 21 | 22 | void metrics_postrefresh(struct SharpMetrics* m, uint32_t frame_ms) { 23 | ++m->frame_index; 24 | m->end_refresh_ms = uptime_ms(); 25 | 26 | const uint32_t total_ms = m->end_refresh_ms - m->start_ms; 27 | if (total_ms < frame_ms) { 28 | sleep_ms(frame_ms - total_ms); 29 | } 30 | m->finish_ms = uptime_ms(); 31 | } 32 | 33 | void metrics_refresh( 34 | struct SharpMetrics* m, struct SharpDisp* sd, uint32_t frame_ms) { 35 | metrics_prerefresh(m); 36 | sharpdisp_refresh(sd); 37 | metrics_postrefresh(m, frame_ms); 38 | } 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | if (NOT EXISTS $ENV{PICO_SDK_PATH}) 4 | message(FATAL_ERROR "Pico SDK Path not found, please set correct environment variable PICO_SDK_PATH") 5 | endif () 6 | 7 | include($ENV{PICO_SDK_PATH}/pico_sdk_init.cmake) 8 | 9 | project(pico_sharpmem_display C CXX ASM) 10 | set(CMAKE_C_STANDARD 11) 11 | set(CMAKE_CXX_STANDARD 17) 12 | 13 | set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR}) 14 | 15 | # Pull in SDK (must be before project) 16 | pico_sdk_init() 17 | 18 | 19 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") 20 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 21 | endif() 22 | 23 | 24 | 25 | add_compile_options(-Wall 26 | -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int 27 | -Wno-unused-function # we have some for the docs that aren't called 28 | -Wno-maybe-uninitialized 29 | ) 30 | 31 | add_subdirectory(examples) 32 | add_subdirectory(fonts) 33 | add_subdirectory(src) 34 | add_subdirectory(test) 35 | -------------------------------------------------------------------------------- /examples/midlevel/hello_world/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define WIDTH 400 8 | #define HEIGHT 240 9 | 10 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 11 | 12 | int main() { 13 | sleep_ms(100); // allow voltage to stabilize 14 | 15 | // Initailize 16 | struct SharpDisp sd; 17 | sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0x00); 18 | struct BitmapText text; 19 | text_init(&text, liberation_sans_36, &sd.bitmap); 20 | 21 | // Print Hello World! 22 | const char* hello = "Hello World!"; 23 | text.x = (WIDTH - text_str_width(&text, hello)) / 2; // center the string 24 | text.y = (HEIGHT - text_height(&text)) / 2; 25 | text_str(&text, hello); 26 | 27 | // Make a border 28 | const uint16_t border = 15; 29 | bitmap_rect( 30 | &sd.bitmap, border, border, WIDTH - border * 2, HEIGHT - border * 2); 31 | 32 | // Send to hardware 33 | sharpdisp_refresh(&sd); 34 | 35 | while (1) { 36 | sleep_ms(1000); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/highlevel/console_advanced_setup/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include 5 | #include "hardware/spi.h" 6 | 7 | // This shows how to setup a consle using a more advanced setup 8 | // You'll need to do this if you want specialized settings (for 9 | // example choosing different output pins). 10 | // 11 | // See highlevel/console_count for a more straight forward 12 | // example that uses default settings. 13 | 14 | #define WIDTH 400 15 | #define HEIGHT 240 16 | 17 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 18 | 19 | int main() { 20 | sleep_ms(100); // allow voltage to stabilize 21 | struct SharpDisp sd; 22 | sharpdisp_init( 23 | &sd, 24 | disp_buffer, 25 | WIDTH, 26 | HEIGHT, 27 | 0x00, 28 | 17, // GP17 for CS 29 | 18, // GP18 for SCK 30 | 19, // GP19 for MOSI 31 | spi0, 32 | 10000000 33 | ); 34 | struct Console c; 35 | sharpconsole_init(&c, &sd, liberation_sans_18, 32); 36 | for (uint32_t i=0;; ++i) { 37 | sharpconsole_printf(&c, "%d\n", i); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/midlevel/scroll_text/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define WIDTH 400 7 | #define HEIGHT 240 8 | 9 | #define MINXY -80 10 | #define MAXXY HEIGHT 11 | 12 | #define FRAME_MS 16 13 | 14 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 15 | 16 | static inline uint32_t uptime_ms() { 17 | return to_ms_since_boot(get_absolute_time()); 18 | } 19 | 20 | int main() { 21 | sleep_ms(100); // allow voltage to stabilize 22 | 23 | struct SharpDisp sd; 24 | sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0x00); 25 | struct BitmapText text; 26 | text_init(&text, liberation_sans_80, &sd.bitmap); 27 | 28 | int16_t xy = MINXY; 29 | int16_t delta = 1; 30 | 31 | while (1) { 32 | const uint32_t t1 = uptime_ms(); 33 | bitmap_clear(&sd.bitmap); 34 | text.x = xy; 35 | text.y = xy; 36 | text_str(&text, "Hello World!"); 37 | xy += delta; 38 | if ((xy < MINXY) || (xy >= MAXXY)) { 39 | delta = -delta; 40 | xy += delta; 41 | } 42 | sharpdisp_refresh(&sd); 43 | const uint32_t tdelta = uptime_ms() - t1; 44 | if (tdelta < FRAME_MS) { 45 | sleep_ms(FRAME_MS - tdelta); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /examples/lowlevel/buffer_direct/main.c: -------------------------------------------------------------------------------- 1 | // Example that is intended to be short and to-the-point 2 | #include "pico/stdlib.h" 3 | #include 4 | 5 | #define WIDTH 400 6 | #define HEIGHT 240 7 | 8 | // We manage the buffer. Changing the buffer directly 9 | // ourselves (i.e. not with sharpdisp functions) is permitted. 10 | // See bitmap.h for formulas that 11 | // translate between x,y and byte array offset. 12 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 13 | 14 | int main() { 15 | struct SharpDisp sd; 16 | // Default init picks "reasonable" values for pins, SPI rate etc. 17 | // see sharpdisp.h for more details and more initilization options. 18 | sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0xFF); 19 | 20 | // Draws a filled box using low-level direct access to the array. 21 | // This is mostly for educational purposes as you would normally 22 | // opt to use bitmapshapes.h or some other graphics library. 23 | for (uint16_t y=10; y < 110; ++y) { 24 | for (uint16_t x=1; x <= 10; ++x) { 25 | disp_buffer[y * (WIDTH / 8) + x] = 0x00; // Writes a 8x1 pixel slice 26 | // note, this would do the same thing 27 | // sd.bitmap.data[y * sd.bitmap.width_bytes + x] = 0xFF; 28 | } 29 | } 30 | // Send the buffer to the display hardware 31 | sharpdisp_refresh(&sd); 32 | while (1); // hang 33 | } 34 | -------------------------------------------------------------------------------- /tools/lib/codegen.py: -------------------------------------------------------------------------------- 1 | """Utilities for generating C code.""" 2 | 3 | from typing import Any, IO, List, Optional, Tuple 4 | import pathlib 5 | 6 | def dump_c_header(path: str, var_name: str, defines: Optional[List[Tuple[str, Any]]] = None) -> None: 7 | """Dumps the header of a .c file""" 8 | out_path = pathlib.Path(path).with_suffix('.h') 9 | header_guard = f'{var_name}_h'.upper() 10 | data = [ 11 | '#ifndef %s' % header_guard, 12 | '#define %s' % header_guard, 13 | '// Generated data for %s' % path, 14 | '', 15 | ] 16 | if defines: 17 | for name, value in defines: 18 | data.append('#define %-30s %s' % (name, value)) 19 | data.append('') 20 | data.extend([ 21 | '#include ', 22 | '#include ', 23 | '', 24 | 'extern const uint8_t %s[] __in_flash();' % var_name, 25 | '', 26 | '#endif // %s' % header_guard, 27 | '', 28 | ]) 29 | out_path.write_text('\n'.join(data), encoding='utf8') 30 | 31 | print('Wrote %s' % out_path) 32 | 33 | 34 | def chunks(lst, n): 35 | """Yield successive n-sized chunks from lst.""" 36 | for i in range(0, len(lst), n): 37 | yield lst[i:i + n] 38 | 39 | 40 | def generate_character_data(data: List[int], fout: IO) -> None: 41 | for chunk in chunks(data, 16): 42 | fout.write(' %s,\n' % ', '.join('0x%02X' % b for b in chunk)) 43 | 44 | -------------------------------------------------------------------------------- /examples/midlevel/metrics/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define WIDTH 400 // 400 pixels 9 | #define HEIGHT 240 10 | #define RADIUS 10 11 | #define SPI_FREQ_HZ 10000000 12 | #define SLEEP_MS 16 13 | 14 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 15 | char printf_buffer[256]; 16 | 17 | int main() { 18 | sleep_ms(100); // allow voltage to stabilize 19 | struct SharpDisp sd; 20 | sharpdisp_init_freq_hz( 21 | &sd, 22 | disp_buffer, 23 | WIDTH, 24 | HEIGHT, 25 | 0xFF, 26 | SPI_FREQ_HZ); 27 | struct SharpMetrics m; 28 | metrics_init(&m); 29 | struct BitmapText text; 30 | text_init(&text, liberation_sans_18, &sd.bitmap); 31 | text.printf_buffer = printf_buffer; 32 | 33 | int16_t x = 50; 34 | int16_t y = 50; 35 | int16_t x_vel = 2; 36 | int16_t y_vel = 1; 37 | const int16_t max_x = WIDTH - RADIUS; 38 | const int16_t max_y = HEIGHT - RADIUS; 39 | 40 | while (1) { 41 | metrics_start(&m); 42 | 43 | bitmap_clear(&sd.bitmap); 44 | bitmap_filled_circle(&sd.bitmap, x, y, RADIUS); 45 | text.x = 8; 46 | text.y = HEIGHT - 32; 47 | if (m.frame_index > 0) { 48 | text_printf( 49 | &text, 50 | "Draw: %u ms Refresh: %u ms FPS: %u", 51 | metrics_draw_ms(&m), 52 | metrics_refresh_ms(&m), 53 | 1000 / metrics_total_ms(&m)); 54 | } 55 | 56 | x += x_vel; 57 | if ((x < RADIUS) || (x > max_x)) { 58 | x_vel = -x_vel; 59 | x += x_vel; 60 | } 61 | y += y_vel; 62 | if ((y < RADIUS) || (y > max_y)) { 63 | y_vel = -y_vel; 64 | y += y_vel; 65 | } 66 | 67 | metrics_refresh(&m, &sd, SLEEP_MS); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/consoletests.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "sharpdisp/bitmapconsole.h" 3 | #include "sharpdisp/bitmaptext.h" 4 | #include "fonts/liberation_mono_10.h" 5 | 6 | static struct TestData shapes_cons1_data = {36, 0, {}}; 7 | struct TestData* test_shapes_cons1(struct Bitmap* bitmap) { 8 | struct BitmapConsole bc; 9 | struct BitmapText text; 10 | text_init(&text, liberation_mono_10, bitmap); 11 | bitmap_console_init(&bc, &text); 12 | bitmap_console_char(&bc, 'A'); 13 | bitmap_console_char(&bc, '\n'); 14 | bitmap_console_char(&bc, 'B'); 15 | return &shapes_cons1_data; 16 | } 17 | 18 | static struct TestData shapes_cons2_data = {94, 0, {}}; 19 | struct TestData* test_shapes_cons2(struct Bitmap* bitmap) { 20 | struct BitmapConsole bc; 21 | struct BitmapText text; 22 | text_init(&text, liberation_mono_10, bitmap); 23 | bitmap_console_init(&bc, &text); 24 | bitmap_console_strlen(&bc, "Hello\n", 6); 25 | bitmap_console_strlen(&bc, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n", 6); 26 | return &shapes_cons2_data; 27 | } 28 | 29 | static struct TestData shapes_cons3_data = {15, 0, {}}; 30 | struct TestData* test_shapes_cons3(struct Bitmap* bitmap) { 31 | struct BitmapConsole bc; 32 | struct BitmapText text; 33 | text_init(&text, liberation_mono_10, bitmap); 34 | bitmap_console_init(&bc, &text); 35 | bitmap_console_str(&bc, "Hello\n"); 36 | bitmap_console_str(&bc, "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n"); 37 | return &shapes_cons3_data; 38 | } 39 | 40 | static struct TestData shapes_cons4_data = {37, 0, {}}; 41 | struct TestData* test_shapes_cons4(struct Bitmap* bitmap) { 42 | char printf_buffer[80]; 43 | struct BitmapConsole bc; 44 | struct BitmapText text; 45 | text_init(&text, liberation_mono_10, bitmap); 46 | text.printf_buffer = printf_buffer; 47 | bitmap_console_init(&bc, &text); 48 | for (uint8_t i=0; i<10; ++i) { 49 | bitmap_console_printf(&bc, "#%d\n", i); 50 | } 51 | return &shapes_cons4_data; 52 | } 53 | -------------------------------------------------------------------------------- /src/bitmapconsole.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/bitmapconsole.h" 2 | #include 3 | #include 4 | 5 | void bitmap_console_init(struct BitmapConsole* c, struct BitmapText* text) { 6 | c->text = text; 7 | c->vscroll = 0; 8 | c->is_scrolling = 0; 9 | bitmap_console_clear(c); 10 | } 11 | 12 | void bitmap_console_clear(struct BitmapConsole* c) { 13 | bitmap_clear(c->text->bitmap); 14 | c->is_scrolling = 0; 15 | c->text->x = 0; 16 | c->text->y = 0; 17 | } 18 | 19 | static void bitmap_console_scroll(struct BitmapConsole* c) { 20 | struct BitmapText* t = c->text; 21 | const uint8_t height = text_height(t); 22 | const uint16_t disp_height = t->bitmap->height - height + 1; 23 | 24 | // increment y and reset x 25 | t->x = 0; 26 | t->y += height; 27 | if (t->y >= disp_height) { 28 | c->is_scrolling = 1; 29 | t->y = 0; 30 | } 31 | 32 | if (c->is_scrolling) { 33 | c->vscroll += height; 34 | if (c->vscroll >= disp_height) { 35 | c->vscroll = 0; 36 | } 37 | } 38 | 39 | // clear the current line 40 | memset( 41 | t->bitmap->data + (t->y * t->bitmap->width_bytes), 42 | t->bitmap->clear_byte, 43 | height * t->bitmap->width_bytes); 44 | } 45 | 46 | void bitmap_console_char(struct BitmapConsole* c, char ch) { 47 | struct BitmapText* t = c->text; 48 | if (ch == '\n') { 49 | bitmap_console_scroll(c); 50 | return; 51 | } 52 | 53 | uint16_t width = text_char_width(t, ch); 54 | if ((t->x + width) > t->bitmap->width) { 55 | bitmap_console_scroll(c); 56 | } 57 | 58 | text_char(t, ch); 59 | } 60 | 61 | void bitmap_console_printf(struct BitmapConsole* c, const char* fmt, ...) { 62 | if (!c->text->printf_buffer) { 63 | // User never set this up 64 | return; 65 | } 66 | va_list args; 67 | va_start(args, fmt); 68 | vsprintf(c->text->printf_buffer, fmt, args); 69 | va_end(args); 70 | bitmap_console_str(c, c->text->printf_buffer); 71 | } 72 | 73 | -------------------------------------------------------------------------------- /src/rle.c: -------------------------------------------------------------------------------- 1 | #include "rle.h" 2 | 3 | void map_rle_image( 4 | struct Bitmap* bitmap, 5 | const uint8_t* pgm_data, 6 | int16_t x, 7 | int16_t y, 8 | uint16_t width, 9 | uint16_t height, 10 | uint8_t* error 11 | ) { 12 | if (*error) { 13 | return; 14 | } 15 | 16 | if (!pgm_data) { 17 | return; 18 | } 19 | 20 | uint8_t bytes_remaining = 0; // number of bytes remaining on the current run 21 | uint8_t repeat_mode = 0; 22 | const uint16_t num_cols = (width + 7) >> 3; 23 | uint8_t rle_byte = 0; 24 | 25 | // vertical stripes 26 | for (uint16_t col = 0; col < num_cols; ++col) { 27 | // each strip is height in length 28 | for (uint16_t row = 0; row < height; ++row) { 29 | if (bytes_remaining == 0) { 30 | // grab the next control byte 31 | bytes_remaining = *(pgm_data++); 32 | if (bytes_remaining & 0x80) { 33 | // a non-repeating sequence 34 | bytes_remaining &= 0x7F; 35 | repeat_mode = 0; 36 | } else { 37 | // a repeating sequence 38 | rle_byte = *pgm_data; 39 | repeat_mode = 1; 40 | if (rle_byte == 0x00) { 41 | // Optimization: When the byte is zero, the destination bitmap 42 | // is not changed at all. Thus we can skip over the process of 43 | // even trying. 44 | row += bytes_remaining - 1; // account for the increment inthe for loop above 45 | for (; row >= height; row -= height, ++col); 46 | // and skip to the next byte 47 | ++pgm_data; 48 | bytes_remaining = 0; 49 | continue; 50 | } 51 | } 52 | 53 | if (bytes_remaining == 0) { 54 | // data is invalid 55 | *error = RLE_BAD_DATA; 56 | return; 57 | } 58 | } 59 | 60 | --bytes_remaining; 61 | if (!repeat_mode || (bytes_remaining == 0)) { 62 | rle_byte = *(pgm_data++); 63 | } 64 | 65 | bitmap_apply_stripe( 66 | bitmap, 67 | x + (col * 8), 68 | y + row, 69 | rle_byte); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapimage/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include "hardware/structs/rosc.h" 5 | 6 | #include "images.h" 7 | 8 | #define WIDTH 400 9 | #define HEIGHT 240 10 | 11 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 12 | struct SharpDisp sd; 13 | struct BitmapImages bi; 14 | 15 | // some good-enough random generation 16 | static int16_t rand16(int16_t min, int16_t max) { 17 | uint16_t v = 0x0000; 18 | for (int i=0; i<16; ++i, v<<=1) { 19 | if (rosc_hw->randombit) { 20 | v |= 0x0001; 21 | } 22 | } 23 | return min + (v % (max - min)); 24 | } 25 | 26 | static void show_an_image(uint32_t idx) { 27 | int16_t width = image_width(&bi, idx); 28 | int16_t height = image_height(&bi, idx); 29 | int16_t x = (WIDTH - width) / 2; 30 | int16_t y = (HEIGHT - height) / 2; 31 | if (rand16(0,100) > 50) { 32 | // randomize the location 33 | x = rand16(-width / 2, WIDTH - width / 2); 34 | y = rand16(-height / 2, HEIGHT - height / 2); 35 | } 36 | if (rand16(0,100) > 50) { 37 | sd.bitmap.clear_byte = 0xFF; 38 | } else { 39 | sd.bitmap.clear_byte = 0x00; 40 | } 41 | bitmap_clear(&sd.bitmap); 42 | image_draw(&bi, idx, x, y); 43 | } 44 | 45 | // need to keep refreshing the screen or it 46 | // will sometimes blank out. 47 | static void sleep_for(uint32_t ms) { 48 | const uint32_t steps = ms / 50; 49 | for (uint32_t i=0; i List[str]: 9 | fn_list = [] 10 | with open(path) as fin: 11 | for line in fin: 12 | if '(' not in line: 13 | continue 14 | line, _ = line.split('(', 1) 15 | tokens = line.split() 16 | if len(tokens) < 3: 17 | continue 18 | if tokens[0] != 'struct': 19 | continue 20 | if tokens[1] != 'TestData*': 21 | continue 22 | fn_name = tokens[2].strip() 23 | if fn_name.startswith('test_'): 24 | fn_list.append(fn_name) 25 | return fn_list 26 | 27 | def create_signatures(fn_list: List[str]) -> List[str]: 28 | def make_sig(name): 29 | return f'struct TestData* {name}(struct Bitmap* bitmap);' 30 | return [make_sig(name) for name in fn_list] 31 | 32 | def create_fn_array(fn_list: List[str]) -> List[str]: 33 | lines = [ 34 | 'struct TestData* (*tests[])(struct Bitmap*) = {', 35 | ] 36 | lines.extend(f' {name},' for name in fn_list) 37 | lines.append('};') 38 | return lines 39 | 40 | def create_name_array(fn_list: List[str]) -> List[str]: 41 | lines = [ 42 | 'const char* test_names[] = {', 43 | ] 44 | for fn_name in fn_list: 45 | name = fn_name.split('_')[-1] 46 | if len(name) > 5: 47 | sys.exit(f'{fn_name} name is > 5 chars: {name}') 48 | lines.append(f' "{name}",') 49 | lines.append('};') 50 | return lines 51 | 52 | def generate_output(fn_list: List[str]) -> None: 53 | lines = [ 54 | '// THIS FILE IS AUTOGENERATED', 55 | '// Run make or ./gen_testnames.py to create it', 56 | '', 57 | ] 58 | lines.extend(create_signatures(fn_list)) 59 | lines.append('') 60 | lines.extend(create_fn_array(fn_list)) 61 | lines.append('') 62 | lines.extend(create_name_array(fn_list)) 63 | lines.append('') 64 | with open('tests.inc', 'w') as fout: 65 | fout.write('\n'.join(lines)) 66 | print('Wrote tests.inc') 67 | 68 | def main(): 69 | fn_list = [] 70 | for path in glob.glob('*tests.c'): 71 | fn_list.extend(get_functions(path)) 72 | generate_output(sorted(fn_list)) 73 | 74 | if __name__ == '__main__': 75 | main() 76 | -------------------------------------------------------------------------------- /src/sharpconsole.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/sharpconsole.h" 2 | #include "fonts/liberation_mono_12.h" 3 | #include "pico/stdlib.h" 4 | #include 5 | #include 6 | 7 | static inline uint32_t uptime_ms() { 8 | return to_ms_since_boot(get_absolute_time()); 9 | } 10 | 11 | void sharpconsole_init( 12 | struct Console* c, 13 | struct SharpDisp* display, 14 | const void* font, 15 | uint32_t refresh_period_ms) { 16 | c->display = display; 17 | text_init(&(c->text), font, &(c->display->bitmap)); 18 | bitmap_console_init(&(c->console), &(c->text)); 19 | c->text.printf_buffer = c->printf_buffer; 20 | c->refresh_period_ms = refresh_period_ms; 21 | c->next_refresh_ms = 0; 22 | sharpconsole_flush(c); 23 | } 24 | 25 | void sharpconsole_init_default( 26 | struct Console* c, 27 | uint8_t* buff, 28 | uint16_t width, 29 | uint16_t height) { 30 | static struct SharpDisp display; 31 | sharpdisp_init_default(&display, buff, width, height, 0xFF); 32 | sharpconsole_init( 33 | c, 34 | &display, 35 | liberation_mono_12, 36 | 32); 37 | } 38 | 39 | void sharpconsole_clear(struct Console* c) { 40 | bitmap_console_clear(&(c->console)); 41 | sharpconsole_flush(c); 42 | } 43 | 44 | static void sharpconsole_refresh(struct Console* c) { 45 | const uint32_t timestamp_ms = uptime_ms(); 46 | if (timestamp_ms >= c->next_refresh_ms) { 47 | sharpdisp_refresh_vscroll(c->display, c->console.vscroll); 48 | c->next_refresh_ms = timestamp_ms + c->refresh_period_ms; 49 | } 50 | } 51 | 52 | void sharpconsole_flush(struct Console* c) { 53 | c->next_refresh_ms = 0; 54 | sharpconsole_refresh(c); 55 | } 56 | 57 | void sharpconsole_sleep_ms(struct Console* c, uint32_t ms) { 58 | const uint32_t t1 = uptime_ms(); 59 | sharpconsole_flush(c); 60 | const uint32_t delta = uptime_ms() - t1; 61 | if (delta < ms) { 62 | sleep_ms(ms - delta); 63 | } 64 | } 65 | 66 | void sharpconsole_char(struct Console* c, char ch) { 67 | bitmap_console_char(&(c->console), ch); 68 | sharpconsole_refresh(c); 69 | } 70 | 71 | void sharpconsole_printf(struct Console* c, const char* fmt, ...) { 72 | va_list args; 73 | va_start(args, fmt); 74 | vsnprintf(c->printf_buffer, PRINTF_BUFFER_SIZE - 1, fmt, args); 75 | va_end(args); 76 | sharpconsole_str(c, c->printf_buffer); 77 | } 78 | -------------------------------------------------------------------------------- /src/bitmap.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/bitmap.h" 2 | #include "string.h" 3 | 4 | void bitmap_init( 5 | struct Bitmap* b, 6 | uint8_t* buff, 7 | uint16_t width, 8 | uint16_t height, 9 | uint8_t mode, 10 | uint8_t clear_byte) { 11 | b->data = buff; 12 | b->width = width; 13 | b->height = height; 14 | b->mode = mode; 15 | b->clear_byte = clear_byte; 16 | b->width_bytes = (width + 7) >> 3; 17 | } 18 | 19 | void bitmap_copy(struct Bitmap* dest, const struct Bitmap* src) { 20 | if (dest->width_bytes != src->width_bytes) { 21 | return; 22 | } 23 | if (dest->data == src->data) { 24 | return; 25 | } 26 | const uint16_t height = dest->height <= src->height ? dest->height : src->height; 27 | memcpy(dest->data, src->data, height * dest->width_bytes); 28 | } 29 | 30 | void bitmap_copy_rect( 31 | struct Bitmap* dest, 32 | const struct Bitmap* src, 33 | int16_t src_x, 34 | int16_t src_y) { 35 | const uint16_t num_columns = dest->width_bytes; 36 | const uint16_t num_rows = dest->height; 37 | uint8_t* dest_data = dest->data; 38 | for (uint16_t row = 0; row < num_rows; ++row) { 39 | for (uint16_t column = 0; column < num_columns; ++column, ++dest_data) { 40 | *dest_data = bitmap_get_stripe( 41 | src, 42 | src_x + (column << 3), 43 | src_y + row 44 | ); 45 | } 46 | } 47 | // some extra pixels beyond the actual width may have been grabbed 48 | const uint8_t clear_width = 8 - (dest->width & 0x07); 49 | if (clear_width >= 8) { 50 | // guess not 51 | return; 52 | } 53 | const uint8_t clear_mask = 0xFF << clear_width; 54 | // select the last column 55 | dest_data = dest->data + num_columns - 1; 56 | for (uint16_t row = 0; row < num_rows; ++row,dest_data+=num_columns) { 57 | (*dest_data) &= clear_mask; 58 | } 59 | } 60 | 61 | void bitmap_blit( 62 | struct Bitmap* dest, 63 | int16_t dest_x, 64 | int16_t dest_y, 65 | const struct Bitmap* src) { 66 | const uint16_t num_columns = src->width_bytes; 67 | const uint16_t num_rows = src->height; 68 | uint8_t* src_data = src->data; 69 | for (uint16_t row = 0; row < num_rows; ++row) { 70 | for (uint16_t column = 0; column < num_columns; ++column, ++src_data) { 71 | bitmap_apply_stripe( 72 | dest, 73 | dest_x + (column << 3), 74 | dest_y + row, 75 | *src_data); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /include/sharpdisp/bitmapconsole.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_BITMAPCONSOLE 2 | #define LIB_SHARPDISP_BITMAPCONSOLE 3 | 4 | #include "sharpdisp/bitmaptext.h" 5 | 6 | // This library provides logic for supporting scrollable console displays 7 | // Also see console.h which is requires less code to use in trade for 8 | // being more limited (focusing on the "common" usecase of a full-screen 9 | // console). 10 | // 11 | // Example: 12 | // 13 | // #define WIDTH 400 14 | // #define HEIGHT 240 15 | // uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 16 | // 17 | // int main() { 18 | // struct SharpDisp sd; 19 | // sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT); 20 | // struct BitmapText text; 21 | // text_init(&text, terminal_font, &sd.bitmap); 22 | // struct BitmapConsole console; 23 | // bitmap_console_init(&console, &text, 0xFF); 24 | // while (1) { 25 | // bitmap_console_str(&console, "Hello World!\n"); 26 | // sharpdisp_refresh_vscroll(&sd, console.vscroll); 27 | // sleep_ms(1000); 28 | // } 29 | // } 30 | // 31 | 32 | struct BitmapConsole { 33 | struct BitmapText* text; 34 | // for the sake of efficiency, the bitmap is wrapped and the vscroll 35 | // parameter is used with the sharpdisp_refresh_vscroll() function for 36 | // better scrolling efficiency (less memory movement needed). 37 | uint16_t vscroll; 38 | // a flag used by the logic to tell if the console is in a scrolling state 39 | uint8_t is_scrolling; 40 | }; 41 | 42 | void bitmap_console_init( 43 | struct BitmapConsole* c, 44 | struct BitmapText* text); 45 | 46 | // clear console and reset text position 47 | void bitmap_console_clear(struct BitmapConsole* c); 48 | 49 | // output a single character to the console, wrapping and scrolling 50 | // as-needed. 51 | void bitmap_console_char(struct BitmapConsole* c, char ch); 52 | 53 | static inline void bitmap_console_strlen( 54 | struct BitmapConsole* c, 55 | const char* str, 56 | uint16_t length) { 57 | for (uint16_t i=0; i 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // Bouncing the balls around looks cool but adds a lot of complicated 9 | // logic. Thus all of that code/complexity is moved to balls.c, so this 10 | // file can focus on showcasing the double buffering logic. 11 | #include "common.h" 12 | #include "ball.h" 13 | 14 | // you can experiment with changing the SPI speed to see how 15 | // the double buffering responds. At 10 Mhz, we are around 14ms 16 | // per refresh so making it much slower will fairly quickly exceed the 17 | // 16.7 ms needed for 60 FPS. 18 | #define SPI_FREQ_HZ 10000000 19 | #define SLEEP_MS 16 20 | 21 | struct SharpDisp display; 22 | struct DoubleBuffer dub_buff; 23 | struct SharpMetrics metrics; 24 | struct BitmapText text; 25 | 26 | // need to provide buffers for the display (two because it's double buffering) 27 | // and another so that text_printf will work. 28 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 29 | uint8_t disp_buffer2[BITMAP_SIZE(WIDTH, HEIGHT)]; 30 | char printf_buffer[256]; 31 | 32 | static void init_metrics() { 33 | // Metric makes this example a bit more complex than needed, but 34 | // it is useful/interesting to show FPS and draw time as 35 | // a demonstration of the effecitveness of the method. 36 | metrics_init(&metrics); 37 | text_init(&text, liberation_sans_18, &display.bitmap); 38 | text.printf_buffer = printf_buffer; 39 | } 40 | 41 | static void draw_metrics(void) { 42 | text.x = 8; 43 | text.y = HEIGHT - 18; 44 | if (metrics.frame_index > 0) { 45 | text_printf( 46 | &text, 47 | "Draw: %u ms FPS: %u", 48 | metrics_draw_ms(&metrics), 49 | 1000 / metrics_total_ms(&metrics)); 50 | } 51 | } 52 | 53 | int main() { 54 | sleep_ms(100); // allow voltage to stabilize 55 | sharpdisp_init_freq_hz( 56 | &display, 57 | disp_buffer, 58 | WIDTH, 59 | HEIGHT, 60 | 0xFF, 61 | SPI_FREQ_HZ); 62 | doublebuffer_init(&dub_buff, &display, disp_buffer2, SLEEP_MS); 63 | init_metrics(); 64 | init_balls(&dub_buff.bitmap); 65 | 66 | while (1) { 67 | metrics_start(&metrics); 68 | bitmap_clear(&dub_buff.bitmap); 69 | draw_balls(); 70 | draw_metrics(); 71 | metrics_prerefresh(&metrics); 72 | doublebuffer_swap(&dub_buff); // This instead of sharpdisp_refresh() 73 | metrics_postrefresh(&metrics, 0); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /include/sharpdisp/metrics.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_METRICS 2 | #define LIB_SHARPDISP_METRICS 3 | // Simple performance stats wrapper. 4 | // This wrapper makes it convienient to add simple performance measurements 5 | // 6 | // Example Usage: 7 | // 8 | // int main() { 9 | // struct SharpDisp sd; 10 | // sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0x00); 11 | // struct SharpMetrics m; 12 | // metrics_init(&m); 13 | // struct BitmapText text; 14 | // text_init(&text, liberation_sans_14, &sd.bitmap); 15 | // 16 | // while (1) { 17 | // metrics_start(&m); 18 | // bitmap_clear(&sd); 19 | // text.x = 8; 20 | // text.y = 50; 21 | // if (m.frame_index > 0) { 22 | // text_printf( 23 | // "Draw: %u ms Refresh: %u ms FPS: %u", 24 | // metrics_draw_ms(&m), 25 | // metrics_refresh_ms(&m), 26 | // 1000 / metrics_total_ms(&m)); 27 | // } 28 | // metrics_refresh(&m, &sd, 17); 29 | // } 30 | // } 31 | 32 | #include 33 | #include "sharpdisp/sharpdisp.h" 34 | 35 | struct SharpMetrics { 36 | uint32_t frame_index; 37 | uint32_t start_ms; 38 | uint32_t start_refresh_ms; 39 | uint32_t end_refresh_ms; 40 | uint32_t finish_ms; 41 | uint32_t next_start_ms; 42 | }; 43 | 44 | void metrics_init(struct SharpMetrics* m); 45 | // call before drawing anything 46 | void metrics_start(struct SharpMetrics* m); 47 | // call this to render the frame 48 | // If frame_ms is > 0, the code will sleep frame_ms - draw_ms - render_ms which 49 | // should result in a consistent frame rate, asuming that frame_ms is not too 50 | // agressive. 51 | void metrics_refresh( 52 | struct SharpMetrics* m, struct SharpDisp* sd, uint32_t frame_ms); 53 | 54 | // If you are using doublebuffer.h or some other special refresh approach, 55 | // metrics_refresh() will be too high level to be usable. In that case, 56 | // you can use the two functions below in the following way: 57 | // 58 | // metrics_prerefresh(&m); 59 | // doublebuffer_swap(&db); 60 | // metrics_postrefresh(&m, 0); // zero becuase doublebuffer_swap handles the delay. 61 | void metrics_prerefresh(struct SharpMetrics* m); 62 | void metrics_postrefresh(struct SharpMetrics* m, uint32_t frame_ms); 63 | 64 | // All of these are for the previous frame 65 | static inline uint32_t metrics_draw_ms(struct SharpMetrics* m) { 66 | return m->start_refresh_ms - m->start_ms; 67 | } 68 | 69 | static inline uint32_t metrics_refresh_ms(struct SharpMetrics* m) { 70 | return m->end_refresh_ms - m->start_refresh_ms; 71 | } 72 | 73 | static inline uint32_t metrics_total_ms(struct SharpMetrics* m) { 74 | return m->finish_ms - m->start_ms; 75 | } 76 | 77 | #endif -------------------------------------------------------------------------------- /tools/lib/rle.py: -------------------------------------------------------------------------------- 1 | 2 | """Contains logic that translates a list of integers to a run-length-encoded format.""" 3 | 4 | from typing import List 5 | from PIL import Image 6 | 7 | def create_rle_data(height: int, img: Image.Image, invert: bool = True) -> List[int]: 8 | data = [] 9 | cols = (img.width + 7) // 8 10 | for col in range(cols): 11 | for y in range(height): 12 | startx = col * 8 13 | endx = min(startx + 8, img.width) 14 | byte = 0x00 15 | bit = 7 16 | for x in range(startx, endx): 17 | if bool(img.getpixel((x, y))) == invert: 18 | byte = byte | 1 << bit 19 | bit -= 1 20 | data.append(byte) 21 | 22 | return run_length_encode(data) 23 | 24 | 25 | #pylint: disable=too-many-branches 26 | def run_length_encode(data: List[int]) -> List[int]: 27 | """Convert a sequence of data into an RLE form.""" 28 | current_run = [] 29 | current_run_set = set() 30 | output = [] 31 | for b in data: 32 | if len(current_run) < 2: 33 | # Always take the first two 34 | current_run.append(b) 35 | current_run_set.add(b) 36 | elif b == current_run[-1]: 37 | # There is a sequence 38 | if len(current_run_set) > 1: 39 | # Represent the last run as a changing sequence 40 | # not including it's last character 41 | output.append(0x80 | (len(current_run) - 1)) 42 | output.extend(current_run[:-1]) 43 | current_run = [b, b] 44 | current_run_set = set([b]) 45 | else: 46 | # another entry for the run 47 | current_run.append(b) 48 | else: 49 | if len(current_run_set) == 1: 50 | # Output the repeating sequence and start a new sequence 51 | output.append(len(current_run)) 52 | output.append(current_run[0]) 53 | current_run = [b] 54 | current_run_set = set(current_run) 55 | else: 56 | # keep adding to this one 57 | current_run.append(b) 58 | current_run_set.add(b) 59 | 60 | if len(current_run) == 127: 61 | # at this point, we have to output the data because the run length 62 | # byte is saturated 63 | if len(current_run_set) == 1: 64 | output.append(len(current_run)) 65 | output.append(current_run[0]) 66 | else: 67 | output.append(0x80 | len(current_run)) 68 | output.extend(current_run) 69 | 70 | current_run = [] 71 | current_run_set = set() 72 | 73 | if current_run: 74 | # still a bit more to do 75 | if len(current_run_set) == 1: 76 | output.append(len(current_run)) 77 | output.append(current_run[0]) 78 | else: 79 | output.append(0x80 | len(current_run)) 80 | output.extend(current_run) 81 | 82 | return output 83 | #pylint: enable=too-many-branches 84 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | #project(SHARPDISP C CXX ASM) 4 | set(CMAKE_C_STANDARD 11) 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | add_library(BITMAP INTERFACE) 8 | target_include_directories(BITMAP INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 9 | target_sources(BITMAP INTERFACE 10 | ${CMAKE_CURRENT_LIST_DIR}/bitmap.c 11 | ) 12 | 13 | add_library(BITMAPTEXT INTERFACE) 14 | target_include_directories(BITMAPTEXT INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 15 | target_link_libraries(BITMAPTEXT INTERFACE BITMAP) 16 | target_sources(BITMAPTEXT INTERFACE 17 | ${CMAKE_CURRENT_LIST_DIR}/bitmaptext.c 18 | ${CMAKE_CURRENT_LIST_DIR}/rle.c 19 | ) 20 | 21 | add_library(BITMAPIMAGE INTERFACE) 22 | target_include_directories(BITMAPIMAGE INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 23 | target_link_libraries(BITMAPIMAGE INTERFACE BITMAP) 24 | target_sources(BITMAPIMAGE INTERFACE 25 | ${CMAKE_CURRENT_LIST_DIR}/bitmapimage.c 26 | ${CMAKE_CURRENT_LIST_DIR}/rle.c 27 | ) 28 | 29 | add_library(BITMAPSHAPES INTERFACE) 30 | target_include_directories(BITMAPSHAPES INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 31 | target_link_libraries(BITMAPSHAPES INTERFACE BITMAP) 32 | target_sources(BITMAPSHAPES INTERFACE 33 | ${CMAKE_CURRENT_LIST_DIR}/bitmapshapes.c 34 | ) 35 | 36 | add_library(BITMAPCONSOLE INTERFACE) 37 | target_include_directories(BITMAPCONSOLE INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 38 | target_link_libraries(BITMAPCONSOLE INTERFACE BITMAPTEXT) 39 | target_sources(BITMAPCONSOLE INTERFACE 40 | ${CMAKE_CURRENT_LIST_DIR}/bitmapconsole.c 41 | ) 42 | 43 | add_library(SHARPDISP INTERFACE) 44 | target_include_directories(SHARPDISP INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 45 | target_link_libraries(SHARPDISP INTERFACE pico_stdlib hardware_spi BITMAP) 46 | target_sources(SHARPDISP INTERFACE 47 | ${CMAKE_CURRENT_LIST_DIR}/sharpdisp.c 48 | ) 49 | 50 | add_library(SHARPCONSOLE INTERFACE) 51 | target_include_directories(SHARPCONSOLE INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 52 | target_link_libraries(SHARPCONSOLE INTERFACE BITMAPCONSOLE SHARPDISP) 53 | target_sources(SHARPCONSOLE INTERFACE 54 | ${CMAKE_CURRENT_LIST_DIR}/sharpconsole.c 55 | ) 56 | 57 | add_library(SHARPDOUBLEBUFFER INTERFACE) 58 | target_include_directories(SHARPDOUBLEBUFFER INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 59 | target_link_libraries(SHARPDOUBLEBUFFER INTERFACE SHARPDISP pico_multicore) 60 | target_sources(SHARPDOUBLEBUFFER INTERFACE 61 | ${CMAKE_CURRENT_LIST_DIR}/doublebuffer.c 62 | ) 63 | 64 | add_library(SHARPMETRICS INTERFACE) 65 | target_include_directories(SHARPMETRICS INTERFACE ${CMAKE_CURRENT_LIST_DIR}/../include) 66 | target_link_libraries(SHARPMETRICS INTERFACE SHARPDISP) 67 | target_sources(SHARPMETRICS INTERFACE 68 | ${CMAKE_CURRENT_LIST_DIR}/metrics.c 69 | ) 70 | -------------------------------------------------------------------------------- /test/images.c: -------------------------------------------------------------------------------- 1 | // Generated image data for images 2 | 3 | #include 4 | #include 5 | 6 | // Note: all values are little endian per r2040 spec 7 | 8 | const uint8_t images[] __in_flash() = { 9 | 0x53, 0x48, 0x49, 0x31, // id: SHI1 10 | 0x08, 0x00, 0x00, 0x00, // image count (8) 11 | // Image Offsets 12 | 0x07, 0x00, 0x05, 0x00, 0x40, 0x00, 0x00, 0x00, // 0: SMALL_IMG w=7, h=5, off=64 13 | 0x0A, 0x00, 0x0F, 0x00, 0x46, 0x00, 0x00, 0x00, // 1: MEDIUM_IMG w=10, h=15, off=70 14 | 0x08, 0x00, 0x06, 0x00, 0x63, 0x00, 0x00, 0x00, // 2: MEDIUM_IMG_0_0 w=8, h=6, off=99 15 | 0x02, 0x00, 0x06, 0x00, 0x6A, 0x00, 0x00, 0x00, // 3: MEDIUM_IMG_1_0 w=2, h=6, off=106 16 | 0x08, 0x00, 0x06, 0x00, 0x6F, 0x00, 0x00, 0x00, // 4: MEDIUM_IMG_0_1 w=8, h=6, off=111 17 | 0x02, 0x00, 0x06, 0x00, 0x76, 0x00, 0x00, 0x00, // 5: MEDIUM_IMG_1_1 w=2, h=6, off=118 18 | 0x08, 0x00, 0x03, 0x00, 0x7D, 0x00, 0x00, 0x00, // 6: MEDIUM_IMG_0_2 w=8, h=3, off=125 19 | 0x02, 0x00, 0x03, 0x00, 0x81, 0x00, 0x00, 0x00, // 7: MEDIUM_IMG_1_2 w=2, h=3, off=129 20 | // Image data 21 | // Image 0: SMALL_IMG w=7 h=5 22 | // #-----# 23 | // -#-#-#- 24 | // --#-#-- 25 | // -#-#-#- 26 | // #-----# 27 | 0x85, 0x82, 0x54, 0x28, 0x54, 0x82, 28 | // Image 0: MEDIUM_IMG w=10 h=15 29 | // #--------# 30 | // -#------#- 31 | // --#----#-- 32 | // ---#--#--- 33 | // ----##---- 34 | // ----##---- 35 | // ---#--#--- 36 | // --#----#-- 37 | // -#------#- 38 | // #--------# 39 | // ----##---- 40 | // ---#--#--- 41 | // --#----#-- 42 | // -#------#- 43 | // #--------# 44 | 0x84, 0x80, 0x40, 0x21, 0x12, 0x02, 0x0C, 0x8B, 0x12, 0x21, 0x40, 0x80, 0x0C, 0x12, 0x21, 0x40, 45 | 0x80, 0x40, 0x80, 0x06, 0x00, 0x82, 0x80, 0x40, 0x03, 0x00, 0x82, 0x80, 0x40, 46 | // Image 0: MEDIUM_IMG_0_0 w=8 h=6 47 | // #------- 48 | // -#------ 49 | // --#----# 50 | // ---#--#- 51 | // ----##-- 52 | // ----##-- 53 | 0x84, 0x80, 0x40, 0x21, 0x12, 0x02, 0x0C, 54 | // Image 0: MEDIUM_IMG_1_0 w=2 h=6 55 | // -# 56 | // #- 57 | // -- 58 | // -- 59 | // -- 60 | // -- 61 | 0x82, 0x40, 0x80, 0x04, 0x00, 62 | // Image 0: MEDIUM_IMG_0_1 w=8 h=6 63 | // ---#--#- 64 | // --#----# 65 | // -#------ 66 | // #------- 67 | // ----##-- 68 | // ---#--#- 69 | 0x86, 0x12, 0x21, 0x40, 0x80, 0x0C, 0x12, 70 | // Image 0: MEDIUM_IMG_1_1 w=2 h=6 71 | // -- 72 | // -- 73 | // #- 74 | // -# 75 | // -- 76 | // -- 77 | 0x02, 0x00, 0x82, 0x80, 0x40, 0x02, 0x00, 78 | // Image 0: MEDIUM_IMG_0_2 w=8 h=3 79 | // --#----# 80 | // -#------ 81 | // #------- 82 | 0x83, 0x21, 0x40, 0x80, 83 | // Image 0: MEDIUM_IMG_1_2 w=2 h=3 84 | // -- 85 | // #- 86 | // -# 87 | 0x83, 0x00, 0x80, 0x40, 88 | }; 89 | -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "map.h" 11 | 12 | #define WIDTH 400 13 | #define HEIGHT 240 14 | #define SLEEP_MS 16 15 | 16 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 17 | uint8_t disp_buffer2[BITMAP_SIZE(WIDTH, HEIGHT)]; 18 | char printf_buffer[256]; 19 | struct SharpDisp sd; 20 | struct DoubleBuffer dub_buff; 21 | struct SharpMetrics metrics; 22 | struct BitmapText text; 23 | struct BitmapImages bi; 24 | 25 | static void init_metrics() { 26 | // Metric makes this example a bit more complex than needed, but 27 | // it is useful/interesting to show FPS and draw time as 28 | // a demonstration of the effecitveness of the method. 29 | metrics_init(&metrics); 30 | text_init(&text, liberation_mono_12, &dub_buff.bitmap); 31 | text.printf_buffer = printf_buffer; 32 | } 33 | 34 | static void draw_metrics(void) { 35 | text.x = 8; 36 | text.y = HEIGHT - 18; 37 | if (metrics.frame_index > 0) { 38 | const uint8_t old_mode = dub_buff.bitmap.mode; 39 | dub_buff.bitmap.mode = BITMAP_INVERSE; 40 | text_printf( 41 | &text, 42 | "Draw: %2u ms FPS: %u", 43 | metrics_draw_ms(&metrics), 44 | 1000 / metrics_total_ms(&metrics)); 45 | dub_buff.bitmap.mode = old_mode; 46 | } 47 | } 48 | 49 | int main() { 50 | sleep_ms(100); // allow voltage to stabilize 51 | 52 | // Initailize 53 | sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0xFF); 54 | doublebuffer_init(&dub_buff, &sd, disp_buffer2, SLEEP_MS); 55 | image_init(&bi, map, &dub_buff.bitmap); 56 | init_metrics(); 57 | 58 | const int16_t xmin = WIDTH - image_width_tiled( 59 | &bi, MAP_IMG_0_0, MAP_IMG_COLUMNS); 60 | const int16_t xmax = 0; 61 | const int16_t ymin = HEIGHT - image_height_tiled( 62 | &bi, MAP_IMG_0_0, MAP_IMG_COLUMNS, MAP_IMG_ROWS); 63 | const int16_t ymax = 0; 64 | 65 | int16_t x = 0; 66 | int16_t y = 0; 67 | int16_t xv = -1; 68 | int16_t yv = -1; 69 | 70 | while (!bi.error) { 71 | metrics_start(&metrics); 72 | bitmap_clear(&dub_buff.bitmap); 73 | image_draw_tiled(&bi, MAP_IMG_0_0, MAP_IMG_COLUMNS, MAP_IMG_ROWS, x, y); 74 | draw_metrics(); 75 | metrics_prerefresh(&metrics); 76 | doublebuffer_swap(&dub_buff); 77 | metrics_postrefresh(&metrics, 0); 78 | 79 | x += xv; 80 | if ((x < xmin) || (x > xmax)) { 81 | xv = -xv; 82 | x += xv; 83 | } 84 | y += yv; 85 | if ((y < ymin) || (y > ymax)) { 86 | yv = -yv; 87 | y += yv; 88 | } 89 | } 90 | 91 | // an error occurred. If you don't see images, add some debug messages here 92 | while (1) { 93 | sleep_ms(1000); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/sharpdisp.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/sharpdisp.h" 2 | #include "pico/stdlib.h" 3 | 4 | #define SHARPMEM_BIT_WRITECMD 0x80 5 | #define SHARPMEM_BIT_VCOM 0x40 6 | #define SHARPMEM_BIT_CLEAR 0x20 7 | 8 | static void sharpdisp_init_spi( 9 | spi_inst_t* spi, 10 | uint8_t cs_pin, 11 | uint8_t sck_pin, 12 | uint8_t mosi_pin, 13 | uint32_t freq_hz) { 14 | gpio_init(cs_pin); 15 | gpio_set_dir(cs_pin, GPIO_OUT); 16 | gpio_put(cs_pin, 0); // this display is low on inactive 17 | 18 | spi_init(spi, freq_hz); 19 | // just a placeholder as the spi_init defaults already set everything 20 | // the same way. 21 | //spi_set_format( 22 | // spi, 23 | // 8, // bits per transfer 24 | // 0, // polarity 25 | // 0, // phase 26 | // SPI_MSB_FIRST); 27 | gpio_set_function(mosi_pin, GPIO_FUNC_SPI); 28 | gpio_set_function(sck_pin, GPIO_FUNC_SPI); 29 | } 30 | 31 | void sharpdisp_init( 32 | struct SharpDisp* s, 33 | uint8_t* buff, 34 | uint16_t width, 35 | uint16_t height, 36 | uint8_t clear_byte, 37 | uint8_t cs_pin, 38 | uint8_t sck_pin, 39 | uint8_t mosi_pin, 40 | spi_inst_t* spi, 41 | uint32_t freq_hz) { 42 | bitmap_init( 43 | &(s->bitmap), 44 | buff, 45 | width, 46 | height, 47 | clear_byte ? BITMAP_BLACK : BITMAP_WHITE, 48 | clear_byte); 49 | bitmap_clear(&(s->bitmap)); 50 | s->cs_pin = cs_pin; 51 | s->spi = spi; 52 | s->vcom = SHARPMEM_BIT_VCOM; 53 | sharpdisp_init_spi(spi, cs_pin, sck_pin, mosi_pin, freq_hz); 54 | } 55 | 56 | uint8_t reverse_bits(uint8_t b) { 57 | uint8_t c = 0; 58 | for (uint8_t x=0; x < 8; ++x) { 59 | c = (c << 1) | (b & 1); 60 | b >>= 1; 61 | } 62 | return c; 63 | } 64 | 65 | void sharpdisp_refresh_vscroll(struct SharpDisp* s, uint16_t vscroll) { 66 | gpio_put(s->cs_pin, 1); 67 | busy_wait_us_32(6); // Datasheet tsSCS value 68 | 69 | uint8_t spi_byte = s->vcom | SHARPMEM_BIT_WRITECMD; 70 | spi_write_blocking(s->spi, &spi_byte, 1); 71 | // Not sure why this needs to be inverted each time but Adafruit_SharpMem.cpp 72 | // is doing it that way. The specification is not making the reason clear 73 | // to me. 74 | s->vcom ^= SHARPMEM_BIT_VCOM; 75 | 76 | const uint16_t height = s->bitmap.height; 77 | uint16_t memy = vscroll; 78 | for (uint16_t y = 0; y < height; ++y, ++memy) { 79 | if (memy >= height) { 80 | memy = 0; // wrap around 81 | } 82 | // send the line address, which is apparently 1-based 83 | spi_byte = reverse_bits(y + 1); 84 | spi_write_blocking(s->spi, &spi_byte, 1); 85 | // send the data 86 | spi_write_blocking(s->spi, s->bitmap.data + s->bitmap.width_bytes * memy, s->bitmap.width_bytes); 87 | // end with a zero for some reason 88 | spi_byte = 0; 89 | spi_write_blocking(s->spi, &spi_byte, 1); 90 | } 91 | 92 | // for some reason, end all data with another zero 93 | spi_byte = 0; 94 | spi_write_blocking(s->spi, &spi_byte, 1); 95 | 96 | gpio_put(s->cs_pin, 0); 97 | busy_wait_us_32(2); // Datasheet thSCS 98 | } 99 | -------------------------------------------------------------------------------- /include/sharpdisp/sharpdisp.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP 2 | #define LIB_SHARPDISP 3 | 4 | #include "hardware/spi.h" 5 | #include "sharpdisp/bitmap.h" 6 | 7 | // Sharp memory display controller API 8 | // 9 | // Here is an example that draws a 40x50 filled rectangle starting at 10,10 10 | // 11 | // #define WIDTH 400 12 | // #define HEIGHT 240 13 | // 14 | // uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)] 15 | // 16 | // void main() { 17 | // struct SharpDisp sd; 18 | // sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0xFF); 19 | // for (uint16_t x=0; x < 40; ++x) { 20 | // for (uint16_t y=0; y < 50; ++y) { 21 | // // bitmap_set_byte would be quicker but more complicated 22 | // bitmap_point(&sd.bitmap, 10 + x, 10 + y); 23 | // } 24 | // } 25 | // 26 | // sharpdisp_refresh(&sd); 27 | // while (1); 28 | // } 29 | 30 | struct SharpDisp { 31 | spi_inst_t *spi; // choice of SPI hardware 32 | struct Bitmap bitmap; 33 | uint8_t cs_pin; // pin choice for CS 34 | uint8_t vcom; // internal state 35 | }; 36 | 37 | void sharpdisp_init( 38 | struct SharpDisp* s, // object to initialize 39 | uint8_t* buff, // client provides the memory buffer 40 | uint16_t width, // display width, in pixels 41 | uint16_t height, // display height, in pixels 42 | uint8_t clear_byte, // 0x00 for black, 0xFF for white 43 | uint8_t cs_pin, // choice for CS pin, 44 | uint8_t sck_pin, // choice for SCK pin, constrained by spi choice - see datasheet 45 | uint8_t mosi_pin, // choice for mosi pin, constrained by spi choice - see datasheet 46 | spi_inst_t *spi, // choice of SPI hardware 47 | uint32_t freq_hz // spi clock freequency 48 | ); 49 | 50 | // sharpdisp_init_default provides some reasonable default for cases 51 | // where you are not particular 52 | static inline void sharpdisp_init_freq_hz( 53 | struct SharpDisp* s, 54 | uint8_t* buff, 55 | uint16_t width, 56 | uint16_t height, 57 | uint8_t clear_byte, 58 | uint32_t baud) { 59 | sharpdisp_init( 60 | s, 61 | buff, 62 | width, 63 | height, 64 | clear_byte, 65 | 17, // cs_pin will be GP17 (pin 22) 66 | 18, // sck_pin will be GP18 (pin 24) 67 | 19, // mosi_pin will be GP19 (pin 25) 68 | spi0, 69 | baud); 70 | } 71 | 72 | static inline void sharpdisp_init_default( 73 | struct SharpDisp* s, 74 | uint8_t* buff, 75 | uint16_t width, 76 | uint16_t height, 77 | uint8_t clear_byte) { 78 | sharpdisp_init_freq_hz(s, buff, width, height, clear_byte, 10000000); 79 | } 80 | 81 | // vscroll determines the vertical location of y=0 on the display. 82 | // for eaxample, if the value is 10 then y=0 would start on physical 83 | // line 10, lines off the bottom wrap back to the top. The advantage 84 | // of using this variable is that you can implement console scrolling 85 | // without needing to use memmove() like operations to scroll the 86 | // display up. 87 | void sharpdisp_refresh_vscroll(struct SharpDisp* s, uint16_t vscroll); 88 | 89 | static inline void sharpdisp_refresh(struct SharpDisp* s) { 90 | sharpdisp_refresh_vscroll(s, 0); 91 | } 92 | 93 | #endif 94 | 95 | -------------------------------------------------------------------------------- /src/bitmaptext.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/bitmaptext.h" 2 | #include 3 | #include 4 | #include "rle.h" 5 | 6 | void text_verify_font(struct BitmapText* text) { 7 | if (text->error) { 8 | return; 9 | } 10 | const struct SharpMemoryFont* font = (struct SharpMemoryFont*)text->font; 11 | const uint8_t* id = font->id; 12 | if ((id[0] != 'S') || 13 | (id[1] != 'H') || 14 | (id[2] != 'F') || 15 | (id[3] != '1')) { 16 | text->error = TEXT_BAD_FONT_ID_ERROR; 17 | } 18 | } 19 | 20 | static const uint8_t* find_character_data( 21 | const struct SharpMemoryFont* font, 22 | const char c, 23 | uint8_t* width) { 24 | const uint8_t num_chars = font->num_chars; 25 | 26 | // binary search 27 | uint8_t mask = 0x80; 28 | while (mask > num_chars) { 29 | mask >>= 1; 30 | } 31 | 32 | uint8_t search_idx = 0; 33 | char slot_c = 0; 34 | for (; mask; mask >>= 1) { 35 | search_idx |= mask; 36 | if (search_idx >= num_chars) { 37 | // too high 38 | search_idx &= ~mask; 39 | } else { 40 | slot_c = font->data[search_idx << 2]; 41 | if (slot_c > c) { 42 | // overshot the mark 43 | search_idx &= ~mask; 44 | } 45 | } 46 | } 47 | 48 | const uint8_t* slot_addr = font->data + (search_idx << 2); 49 | if (c == slot_addr[0]) { 50 | // found it 51 | *width = slot_addr[1]; 52 | const uint16_t offset = (uint16_t)((slot_addr[2] << 8) | slot_addr[3]); 53 | return font->data + offset; 54 | } 55 | 56 | // did not find anything 57 | return 0; 58 | } 59 | 60 | void text_char(struct BitmapText* text, char c) { 61 | if (text->error) { 62 | return; 63 | } 64 | 65 | const struct SharpMemoryFont* font = (struct SharpMemoryFont*)text->font; 66 | const uint8_t height = font->height; 67 | uint8_t width = 0; 68 | const uint8_t* pgm_data = find_character_data(font, c, &width); 69 | uint8_t* error = &(text->error); 70 | 71 | map_rle_image( 72 | text->bitmap, 73 | pgm_data, 74 | text->x, 75 | text->y, 76 | width, 77 | height, 78 | error); 79 | 80 | if (text->x < text->bitmap->width) { 81 | text->x += width; 82 | } 83 | } 84 | 85 | 86 | uint8_t text_char_width(struct BitmapText* text, char c) { 87 | const struct SharpMemoryFont* font = (struct SharpMemoryFont*)text->font; 88 | uint8_t width = 0; 89 | find_character_data(font, c, &width); 90 | return width; 91 | } 92 | 93 | 94 | void text_init(struct BitmapText* text, const void* font, struct Bitmap* bitmap) { 95 | text->font = font; 96 | text->bitmap = bitmap; 97 | text->x = 0; 98 | text->y = 0; 99 | text->error = 0; 100 | text->printf_buffer = NULL; 101 | text_verify_font(text); 102 | } 103 | 104 | void text_printf(struct BitmapText* text, const char* fmt, ...) { 105 | if (!text->printf_buffer) { 106 | // User never set this up 107 | return; 108 | } 109 | va_list args; 110 | va_start(args, fmt); 111 | vsprintf(text->printf_buffer, fmt, args); 112 | va_end(args); 113 | text_str(text, text->printf_buffer); 114 | } 115 | -------------------------------------------------------------------------------- /src/doublebuffer.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/doublebuffer.h" 2 | #include 3 | #include "pico/stdlib.h" 4 | #include "pico/util/queue.h" 5 | #include "pico/multicore.h" 6 | 7 | // Global data used by core 1 8 | static struct CoreData { 9 | struct DoubleBuffer* db; 10 | uint32_t frame_period_ms; 11 | uint32_t last_render_time_ms; 12 | } core1; 13 | 14 | static inline uint32_t uptime_ms() { 15 | return to_ms_since_boot(get_absolute_time()); 16 | } 17 | 18 | void doublebuffer_init_nostart( 19 | struct DoubleBuffer* db, 20 | struct SharpDisp* disp, 21 | uint8_t* buff2, uint32_t 22 | frame_period_ms) { 23 | db->disp = disp; 24 | bitmap_init( 25 | &(db->bitmap), 26 | buff2, 27 | disp->bitmap.width, 28 | disp->bitmap.height, 29 | disp->bitmap.mode, 30 | disp->bitmap.clear_byte); 31 | uint32_t unused_entry = 0; 32 | queue_init(&(db->frame_is_rendered), sizeof(unused_entry), 1); 33 | queue_init(&(db->frame_is_ready), sizeof(unused_entry), 1); 34 | queue_add_blocking(&(db->frame_is_rendered), &unused_entry); 35 | core1.db = db; 36 | core1.frame_period_ms = frame_period_ms; 37 | core1.last_render_time_ms = 0; 38 | } 39 | 40 | static void doublebuffer_start_core1(void) { 41 | while (1) { 42 | doublebuffer_core1_render(); 43 | } 44 | } 45 | 46 | void doublebuffer_init( 47 | struct DoubleBuffer* db, 48 | struct SharpDisp* disp, 49 | uint8_t* buff2, 50 | uint32_t frame_period_ms) { 51 | doublebuffer_init_nostart(db, disp, buff2, frame_period_ms); 52 | multicore_launch_core1(doublebuffer_start_core1); 53 | } 54 | 55 | void doublebuffer_core1_render(void) { 56 | uint32_t unused_entry = 0; 57 | queue_remove_blocking(&(core1.db->frame_is_ready), &unused_entry); 58 | const uint32_t current_time = uptime_ms(); 59 | if ((current_time - core1.last_render_time_ms) < core1.frame_period_ms) { 60 | sleep_ms(core1.frame_period_ms - (current_time - core1.last_render_time_ms)); 61 | } 62 | core1.last_render_time_ms = uptime_ms(); 63 | sharpdisp_refresh(core1.db->disp); 64 | queue_add_blocking(&(core1.db->frame_is_rendered), &unused_entry); 65 | } 66 | 67 | void doublebuffer_swap(struct DoubleBuffer* db) { 68 | uint32_t unused_entry = 0; 69 | // wait for any rendering to finish 70 | queue_remove_blocking(&(db->frame_is_rendered), &unused_entry); 71 | // swap the frames 72 | uint8_t* tmp = db->disp->bitmap.data; 73 | db->disp->bitmap.data = db->bitmap.data; 74 | db->bitmap.data = tmp; 75 | // allow core1 to start rendering this frame 76 | queue_add_blocking(&(db->frame_is_ready), &unused_entry); 77 | } 78 | 79 | void doublebuffer_refresh(struct DoubleBuffer* db) { 80 | uint32_t unused_entry = 0; 81 | // wait for any rendering to finish 82 | queue_remove_blocking(&(db->frame_is_rendered), &unused_entry); 83 | // allow core1 to start rendering this frame 84 | queue_add_blocking(&(db->frame_is_ready), &unused_entry); 85 | } 86 | 87 | void doublebuffer_sleep_ms(struct DoubleBuffer* db, uint8_t swap, uint32_t ms) { 88 | uint32_t done_time_ms = uptime_ms() + ms; 89 | if (swap) { 90 | doublebuffer_swap(db); 91 | } 92 | while (uptime_ms() < done_time_ms) { 93 | doublebuffer_refresh(db); 94 | } 95 | } -------------------------------------------------------------------------------- /include/sharpdisp/sharpconsole.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_CONSOLE 2 | #define LIB_SHARPDISP_CONSOLE 3 | 4 | // This library provides logic for supporting scrollable 5 | // console displays with added SharpDisp integration 6 | // 7 | // This library is build for convienence in common cases. 8 | // If this library is too rigid for your needs, you might 9 | // look to bitmapconsole.h instead which works directly with the 10 | // bitmap and has no references to the sharp display or text 11 | // objects. That will allow you do do such things as write 12 | // to off-screen consoles. 13 | // 14 | // Example: 15 | // 16 | // #define WIDTH 400 17 | // #define HEIGHT 240 18 | // uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 19 | // 20 | // int main() { 21 | // struct Console console; 22 | // sharpconsole_init(&console, disp_buffer, WIDTH, HEIGHT, terminal_font, 50); 23 | // while (1) { 24 | // sharpconsole_str(&console, "Hello World!\n"); 25 | // sharpconsole_sleep_ms(1000); // ensures that the console is updated 26 | // } 27 | // } 28 | 29 | #include "sharpdisp/sharpdisp.h" 30 | #include "sharpdisp/bitmaptext.h" 31 | #include "sharpdisp/bitmapconsole.h" 32 | 33 | // Add a definition to resize this as-needed 34 | #ifndef PRINTF_BUFFER_SIZE 35 | #define PRINTF_BUFFER_SIZE 256 36 | #endif 37 | 38 | struct Console { 39 | struct SharpDisp* display; 40 | struct BitmapText text; 41 | struct BitmapConsole console; 42 | // How often to refresh the display. Refreshing the display takes 43 | // time and will slow down console performance 44 | uint32_t refresh_period_ms; 45 | // The next timestamp to refresh 46 | uint32_t next_refresh_ms; 47 | char printf_buffer[PRINTF_BUFFER_SIZE]; 48 | }; 49 | 50 | void sharpconsole_init( 51 | struct Console* c, 52 | struct SharpDisp* display, 53 | const void* font, 54 | uint32_t refresh_period_ms); 55 | 56 | void sharpconsole_init_default( 57 | struct Console* c, 58 | uint8_t* buff, 59 | uint16_t width, 60 | uint16_t height); 61 | 62 | void sharpconsole_clear(struct Console* c); 63 | 64 | // If your app has long pauses, you should call sharpconsole_flush() 65 | // ahead of the pause unless you are fine with characters not being 66 | // displayed until after the pause is finished and something else 67 | // is printed. 68 | void sharpconsole_flush(struct Console* c); 69 | 70 | // convienence method that combines sleep_ms with sharpconsole_flush 71 | // also accounts for the flush time in the sleeping 72 | void sharpconsole_sleep_ms(struct Console* c, uint32_t ms); 73 | 74 | // Outputs a single character to the console 75 | void sharpconsole_char(struct Console* c, char ch); 76 | 77 | static inline void sharpconsole_strlen(struct Console* c, const char* str, uint16_t length) { 78 | for (uint16_t i=0; i 3 | #include 4 | #include 5 | #include "hardware/structs/rosc.h" 6 | 7 | #include "rose.h" 8 | 9 | #define WIDTH 400 10 | #define HEIGHT 240 11 | #define MIN_COPY_SIZE 100 12 | #define MAX_COPY_SIZE 200 13 | #define BORDER 16 14 | #define EDGE_WIDTH 4 15 | #define SLEEP_MIN 25 16 | #define SLEEP_MAX 2000 17 | #define SLEEP_STEP 100 18 | 19 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 20 | uint8_t copy_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 21 | struct SharpDisp sd; 22 | struct BitmapImages bi; 23 | 24 | // some good-enough random generation 25 | static int16_t rand16(int16_t min, int16_t max) { 26 | uint16_t v = 0x0000; 27 | for (int i=0; i<16; ++i, v<<=1) { 28 | if (rosc_hw->randombit) { 29 | v |= 0x0001; 30 | } 31 | } 32 | return min + (v % (max - min)); 33 | } 34 | 35 | // need to keep refreshing the screen or it 36 | // will sometimes blank out. 37 | static void sleep_for(uint32_t ms) { 38 | const uint32_t steps = ms / 50; 39 | for (uint32_t i=0; i 50 ? BITMAP_WHITE : BITMAP_BLACK; 79 | bitmap_filled_rect( 80 | &sd.bitmap, 81 | destx - EDGE_WIDTH, 82 | desty - EDGE_WIDTH, 83 | bcopy.width + EDGE_WIDTH * 2, 84 | bcopy.height + EDGE_WIDTH * 2 85 | ); 86 | sd.bitmap.mode = BITMAP_BLACK; 87 | bitmap_filled_rect( 88 | &sd.bitmap, 89 | destx, 90 | desty, 91 | bcopy.width, 92 | bcopy.height 93 | ); 94 | 95 | // paste in the bitmap 96 | sd.bitmap.mode = BITMAP_WHITE; 97 | bitmap_blit( 98 | &sd.bitmap, 99 | destx, 100 | desty, 101 | &bcopy 102 | ); 103 | } 104 | 105 | int main() { 106 | sleep_ms(100); // allow voltage to stabilize 107 | sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0x00); 108 | image_init(&bi, rose, &sd.bitmap); 109 | draw_background(); 110 | 111 | int32_t ms = SLEEP_MAX; 112 | int32_t step = -SLEEP_STEP; 113 | while (1) { 114 | sleep_for(ms); 115 | copy_region(); 116 | ms += step; 117 | if (ms > SLEEP_MAX || ms < SLEEP_MIN) { 118 | if (ms > SLEEP_MAX) { 119 | draw_background(); 120 | } 121 | step = -step; 122 | ms += step; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /include/sharpdisp/bitmapshapes.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_BITMAPSHAPES 2 | #define LIB_SHARPDISP_BITMAPSHAPES 3 | 4 | #include "sharpdisp/bitmap.h" 5 | 6 | // Draws a horizontal line 7 | // bitmap: The bitmap to draw to 8 | // x: starting x 9 | // y: y coordicate 10 | // w: Width (line draws to the right) 11 | // bit_op: Bit operator. e.g. bitmap_OR 12 | void bitmap_hline(struct Bitmap* bitmap, int16_t x, int16_t y, uint16_t w); 13 | 14 | // Draws a vertical line 15 | // bitmap: The bitmap to draw to 16 | // x: x coordinate 17 | // y: starting y coordinate 18 | // h: height 19 | // bit_op: Bit operator. e.g. bitmap_OR 20 | void bitmap_vline(struct Bitmap* bitmap, int16_t x, int16_t y, uint16_t h); 21 | 22 | // Draws a line. Prefer bitmap_hline and bitmap_vline 23 | // for best performance. 24 | // bitmap: The bitmap to draw to 25 | // x0, y0: starting coordinate 26 | // x1, y1: ending coordinate 27 | // bit_op: Bit operator. e.g. bitmap_OR 28 | void bitmap_line( 29 | struct Bitmap* bitmap, 30 | int16_t x0, 31 | int16_t y0, 32 | int16_t x1, 33 | int16_t y1); 34 | 35 | // Draws a rectangle 36 | // bitmap: The bitmap to draw to 37 | // x: top x coordinate 38 | // y: left y coordinate 39 | // w: width 40 | // h: height 41 | // bit_op: Bit operator. e.g. bitmap_OR 42 | void bitmap_rect( 43 | struct Bitmap* bitmap, 44 | int16_t x, 45 | int16_t y, 46 | uint16_t w, 47 | uint16_t h); 48 | void bitmap_filled_rect( 49 | struct Bitmap* bitmap, 50 | int16_t x, 51 | int16_t y, 52 | uint16_t w, 53 | uint16_t h); 54 | 55 | // Draws an oval. Preper bitmap_circle 56 | // when drawing circles for a small code footprint. 57 | // bitmap: The bitmap to draw to 58 | // cx: Center x 59 | // cy: Center y 60 | // r: radius (circle only) 61 | // rx: 0 degree radius 62 | // ry: 90 degtee radius 63 | // bit_op: Bit operator. e.g. bitmap_OR 64 | void bitmap_oval( 65 | struct Bitmap* bitmap, 66 | int16_t cx, 67 | int16_t cy, 68 | uint16_t rx, 69 | uint16_t ry); 70 | void bitmap_filled_oval( 71 | struct Bitmap* bitmap, 72 | int16_t cx, 73 | int16_t cy, 74 | uint16_t rx, 75 | uint16_t ry); 76 | void bitmap_circle( 77 | struct Bitmap* bitmap, 78 | int16_t cx, 79 | int16_t cy, 80 | uint16_t r); 81 | void bitmap_filled_circle( 82 | struct Bitmap* bitmap, 83 | int16_t cx, 84 | int16_t cy, 85 | uint16_t r); 86 | 87 | // Sets or clears a given region using a "flood fill" algorithm. 88 | // 89 | // Note: bitmap->mode must be BITMAP_BLACK or BITMAP_WHITE or this 90 | // function will return without doing anything. This is to avoid a 91 | // potential infinite loop of setting/clearing pixels (as would happen 92 | // with BITMAP_INVERSE). 93 | // 94 | // bitmap: the bitmap 95 | // x: Starting x coordinate 96 | // y: Starting y coordinate 97 | // set: If != 0 then sert pixels, oterwise clear them 98 | // queue, queue_length: IMPORTANT: queue_length must be a 99 | // power of and at least 8 (e.g. 8, 16, 32, 64, 128, ...) 100 | // Flood fill needs some working memory to keep track 101 | // of regions-to-explore. How much depends on the complexity of the 102 | // task. Usually a queue_length of around 150 will work for most cases, 103 | // but much less for simple cases. The amount of memory needed is 104 | // deterministic based on the bitmap contents and starting point. 105 | // Providing insufficient memory will result in the function returning 106 | // with some areas unfilled. 107 | void bitmap_flood_fill( 108 | struct Bitmap* bitmap, 109 | uint16_t x, 110 | uint16_t y, 111 | uint32_t* queue, 112 | uint16_t queue_length); 113 | 114 | #endif 115 | 116 | -------------------------------------------------------------------------------- /include/sharpdisp/bitmaptext.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_BITMAPTEXT 2 | #define LIB_SHARPDISP_BITMAPTEXT 3 | 4 | #include 5 | #include "sharpdisp/bitmap.h" 6 | 7 | // errors 8 | #define TEXT_BAD_FONT_ID_ERROR 0x01 9 | #define TEXT_INVALID_RLE_DATA 0x02 10 | 11 | struct BitmapText { 12 | const uint8_t* font; // Pointer to some font data 13 | struct Bitmap* bitmap; // Pointer to the bitmap to update 14 | // Coordinates of next character to print (upper left corner). X will automatically 15 | // advance after printing, unless if is already past the end of the buffer 16 | int16_t x; 17 | int16_t y; 18 | // Set to non-zero if any error occurs. Most text functions 19 | // will do nothing if this is non-zero. 20 | uint8_t error; 21 | // an optional user-provided bufer that you need to fill in if 22 | // you call text_printf 23 | char* printf_buffer; 24 | }; 25 | 26 | struct SharpMemoryFont { 27 | uint8_t id[4]; // Should be set to 'SHF1' 28 | uint8_t num_chars; // number of characters 29 | uint8_t height; // the height of each character in pixels 30 | 31 | // Now for a lookup table. Format is 32 | // uint8_t char_idx 33 | // uint8_t char_width 34 | // uint16_t offset 35 | // ... 36 | // 37 | // Where offset is the number of bytes from the start of data[] 38 | // char_idx must be in order to support binary search 39 | 40 | // then comes all of the data bytes. These are stored in a simple 41 | // but effective RLE format of the following pattern: 42 | // l1, b Length of sequence, sequence byte (l1 must be <128) 43 | // l2 | 0x80, b1, b2, b3 ... Length of non repeat followed by bytes 44 | // 45 | // example: 46 | // 0x05 0xFF 0x83 0x01 0x02 0x03 47 | // 48 | // Would result in 49 | // 0xFF 0xFF 0xFF 0xFF 0xFF 0x01 0x02 0x03 50 | // 51 | // The pattern is effective due to all of the whitespace and repeating 52 | // patterns in a typical font. Think about 'H', '-', '.' etc... 53 | // 54 | // The data itself is composed of vertical 8x1 strips. This format 55 | // can make it efficient to transfer data to the bitmap while still 56 | // taking advantage of RLE patterns. 57 | uint8_t data[]; 58 | }; 59 | 60 | // Use this to initialize a text object or change fonts in an existing one 61 | void text_init(struct BitmapText* text, const void* font, struct Bitmap* bitmap); 62 | 63 | // Output A Single character 64 | void text_char(struct BitmapText* text, char c); 65 | 66 | // output a sequence of characters 67 | static inline void text_strlen(struct BitmapText* text, const char* str, uint16_t length) { 68 | for (uint16_t i=0; ifont); 87 | return font->height; 88 | } 89 | 90 | // returns the pixel width of a character in the current font 91 | uint8_t text_char_width(struct BitmapText* text, char c); 92 | 93 | // returns the pixel width of a length-provided string 94 | static inline uint16_t text_strlen_width(struct BitmapText* text, const char* str, uint16_t length) { 95 | uint16_t len = 0; 96 | for (uint16_t i=0; i 5 | #include "sharpdisp/bitmap.h" 6 | 7 | // errors 8 | #define IMAGE_BAD_ID_ERROR 0x01 9 | #define IMAGE_BAD_RLE_DATA 0x02 10 | #define IMAGE_INVALID_ID 0x03 11 | #define IMAGE_UNEXPECTED_HEADER_SIZE 0x04 12 | 13 | struct BitmapImages { 14 | const uint8_t* images; // Pointer to some font data 15 | struct Bitmap* bitmap; // Pointer to the bitmap to update 16 | // Set to non-zero if any error occurs. Most text functions 17 | // will do nothing if this is non-zero. 18 | uint8_t error; 19 | }; 20 | 21 | struct SharpMemoryImage { 22 | uint8_t id[4]; // Should be set to 'SHI1' 23 | uint32_t num_images; // number of images 24 | 25 | // Now for a lookup table. Format is 26 | // uint16_t image_width 27 | // uint16_t image_height 28 | // uint32_t offset 29 | // ... 30 | // 31 | // Where offset is the number of bytes from the start of data[] 32 | // char_idx must be in order to support binary search 33 | 34 | // then comes all of the data bytes. These are stored in a simple 35 | // but effective RLE format of the following pattern: 36 | // l1, b Length of sequence, sequence byte (l1 must be <128) 37 | // l2 | 0x80, b1, b2, b3 ... Length of non repeat followed by bytes 38 | // 39 | // example: 40 | // 0x05 0xFF 0x83 0x01 0x02 0x03 41 | // 42 | // Would result in 43 | // 0xFF 0xFF 0xFF 0xFF 0xFF 0x01 0x02 0x03 44 | // 45 | // The data itself is composed of vertical 8x1 strips. This format 46 | // can make it efficient to transfer data to the bitmap while still 47 | // taking advantage of RLE patterns. 48 | uint8_t data[]; 49 | }; 50 | 51 | // Use this to initialize a text object or change fonts in an existing one 52 | void image_init(struct BitmapImages* bi, const void* image_data, struct Bitmap* bitmap); 53 | 54 | // Draw an image. Zero-bits are transparent. One bits will use the Bitmap.mode mode to 55 | // transfer. Optionally use bitmapshapes.h bitmap_filled_rect() to clear out background 56 | // pixels for simple cases. If you really need pixel-perfect masking, then you will probably 57 | // want to define a second image which will be the background mask. For example a 58 | // baseball would first fill in a white circle, then draw a black imamge on top of it 59 | // for stitching/etc. 60 | void image_draw(struct BitmapImages* bi, uint32_t id, int16_t x, int16_t y); 61 | 62 | // image_draw_tiled helps make drawing very large images more efficient. 63 | // The strategy is that you break an image into rectangular tiles of 64 | // equal size (except for the far left and bottom) in the following 65 | // pattern 66 | // 67 | // 01234 68 | // 56789 69 | // 70 | // The example above would show the layout of a image broken into 5x2 tiles 71 | // 72 | // The way this is optimized is that the function will skip drawing tiles 73 | // that are completly off the bitmap. For images that are significantly 74 | // larger than the bitmap, significant amounts of drawing will be skipped. 75 | // 76 | // A good general tile size is 64x64 pixels. This gives 512 byte uncompressed 77 | // tiles which gives the RLE engine something to compress. To get actual ideal 78 | // numbers, benchmark different sizes using sharpmetrics.h or similar. 79 | // 80 | // The make_images.py tool supports splitting a large image into tiles for you 81 | // Provide it with tile_x and tile_y parameters and the image will be divided up. 82 | void image_draw_tiled( 83 | struct BitmapImages* bi, 84 | uint32_t first_id, 85 | uint16_t columns, // number of tiled columns 86 | uint16_t rows, // number of tiled rows 87 | int16_t x, 88 | int16_t y); 89 | 90 | uint16_t image_width(struct BitmapImages* bi, uint32_t id); 91 | uint16_t image_height(struct BitmapImages* bi, uint32_t id); 92 | uint32_t image_count(struct BitmapImages* bi); 93 | 94 | // Determines the overall width and height of a tiled image 95 | uint16_t image_width_tiled( 96 | struct BitmapImages* bi, uint32_t first_id, uint16_t columns); 97 | uint16_t image_height_tiled( 98 | struct BitmapImages* bi, uint32_t first_id, uint16_t columns, uint16_t rows); 99 | 100 | #endif 101 | 102 | -------------------------------------------------------------------------------- /fonts/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is only used with PI PICO and NOT AVR. 2 | cmake_minimum_required(VERSION 3.12) 3 | 4 | #project(SHARPDISP C CXX ASM) 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD 17) 7 | 8 | add_library(LIBERATION_MONO_9 INTERFACE) 9 | target_include_directories(LIBERATION_MONO_9 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 10 | target_link_libraries(LIBERATION_MONO_9 INTERFACE) 11 | target_sources(LIBERATION_MONO_9 INTERFACE 12 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_9.c 13 | ) 14 | 15 | add_library(LIBERATION_MONO_10 INTERFACE) 16 | target_include_directories(LIBERATION_MONO_10 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 17 | target_link_libraries(LIBERATION_MONO_10 INTERFACE) 18 | target_sources(LIBERATION_MONO_10 INTERFACE 19 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_10.c 20 | ) 21 | 22 | 23 | add_library(LIBERATION_MONO_12 INTERFACE) 24 | target_include_directories(LIBERATION_MONO_12 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 25 | target_link_libraries(LIBERATION_MONO_12 INTERFACE) 26 | target_sources(LIBERATION_MONO_12 INTERFACE 27 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_12.c 28 | ) 29 | 30 | add_library(LIBERATION_MONO_14 INTERFACE) 31 | target_include_directories(LIBERATION_MONO_14 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 32 | target_link_libraries(LIBERATION_MONO_14 INTERFACE) 33 | target_sources(LIBERATION_MONO_14 INTERFACE 34 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_14.c 35 | ) 36 | 37 | add_library(LIBERATION_MONO_24 INTERFACE) 38 | target_include_directories(LIBERATION_MONO_24 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 39 | target_link_libraries(LIBERATION_MONO_24 INTERFACE) 40 | target_sources(LIBERATION_MONO_24 INTERFACE 41 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_24.c 42 | ) 43 | 44 | add_library(LIBERATION_MONO_32 INTERFACE) 45 | target_include_directories(LIBERATION_MONO_32 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 46 | target_link_libraries(LIBERATION_MONO_32 INTERFACE) 47 | target_sources(LIBERATION_MONO_32 INTERFACE 48 | ${CMAKE_CURRENT_LIST_DIR}/liberation_mono_32.c 49 | ) 50 | 51 | add_library(SHARPCONSOLE_DEFAULT_FONT INTERFACE) 52 | target_link_libraries(SHARPCONSOLE_DEFAULT_FONT INTERFACE LIBERATION_MONO_12) 53 | 54 | add_library(LIBERATION_SANS_12 INTERFACE) 55 | target_include_directories(LIBERATION_SANS_12 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 56 | target_link_libraries(LIBERATION_SANS_12 INTERFACE) 57 | target_sources(LIBERATION_SANS_12 INTERFACE 58 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_12.c 59 | ) 60 | 61 | add_library(LIBERATION_SANS_18 INTERFACE) 62 | target_include_directories(LIBERATION_SANS_18 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 63 | target_link_libraries(LIBERATION_SANS_18 INTERFACE) 64 | target_sources(LIBERATION_SANS_18 INTERFACE 65 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_18.c 66 | ) 67 | 68 | add_library(LIBERATION_SANS_24 INTERFACE) 69 | target_include_directories(LIBERATION_SANS_24 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 70 | target_link_libraries(LIBERATION_SANS_24 INTERFACE) 71 | target_sources(LIBERATION_SANS_24 INTERFACE 72 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_24.c 73 | ) 74 | 75 | add_library(LIBERATION_SANS_36 INTERFACE) 76 | target_include_directories(LIBERATION_SANS_36 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 77 | target_link_libraries(LIBERATION_SANS_36 INTERFACE) 78 | target_sources(LIBERATION_SANS_36 INTERFACE 79 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_36.c 80 | ) 81 | 82 | add_library(LIBERATION_SANS_48 INTERFACE) 83 | target_include_directories(LIBERATION_SANS_48 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 84 | target_link_libraries(LIBERATION_SANS_48 INTERFACE) 85 | target_sources(LIBERATION_SANS_48 INTERFACE 86 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_48.c 87 | ) 88 | 89 | add_library(LIBERATION_SANS_60 INTERFACE) 90 | target_include_directories(LIBERATION_SANS_60 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 91 | target_link_libraries(LIBERATION_SANS_60 INTERFACE) 92 | target_sources(LIBERATION_SANS_60 INTERFACE 93 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_60.c 94 | ) 95 | 96 | add_library(LIBERATION_SANS_80 INTERFACE) 97 | target_include_directories(LIBERATION_SANS_80 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/..) 98 | target_link_libraries(LIBERATION_SANS_80 INTERFACE) 99 | target_sources(LIBERATION_SANS_80 INTERFACE 100 | ${CMAKE_CURRENT_LIST_DIR}/liberation_sans_80.c 101 | ) 102 | -------------------------------------------------------------------------------- /src/bitmapimage.c: -------------------------------------------------------------------------------- 1 | #include "sharpdisp/bitmapimage.h" 2 | #include "rle.h" 3 | 4 | struct ImageInfo { 5 | uint16_t width; 6 | uint16_t height; 7 | uint32_t offset; 8 | }; 9 | 10 | static void verify_image_data(struct BitmapImages* bi) { 11 | if (bi->error) { 12 | return; 13 | } 14 | const struct SharpMemoryImage* img_data = (struct SharpMemoryImage*)bi->images; 15 | const uint8_t* id = img_data->id; 16 | if ((id[0] != 'S') || 17 | (id[1] != 'H') || 18 | (id[2] != 'I') || 19 | (id[3] != '1')) { 20 | bi->error = IMAGE_BAD_ID_ERROR; 21 | } 22 | } 23 | 24 | static struct ImageInfo* image_info(struct BitmapImages* bi, uint32_t id) { 25 | if (bi->error) { 26 | return NULL; 27 | } 28 | const struct SharpMemoryImage* img_data = (struct SharpMemoryImage*)bi->images; 29 | if (id >= img_data->num_images) { 30 | bi->error = IMAGE_INVALID_ID; 31 | return NULL; 32 | } 33 | 34 | return (struct ImageInfo*)(img_data->data + (id * sizeof(struct ImageInfo))); 35 | } 36 | 37 | void image_init(struct BitmapImages* bi, const void* image_data, struct Bitmap* bitmap) { 38 | bi->images = image_data; 39 | bi->bitmap = bitmap; 40 | bi->error = 0; 41 | if (sizeof(struct ImageInfo) != 8) { 42 | bi->error = IMAGE_UNEXPECTED_HEADER_SIZE; 43 | return; 44 | } 45 | verify_image_data(bi); 46 | } 47 | 48 | uint32_t image_count(struct BitmapImages* bi) { 49 | const struct SharpMemoryImage* img_data = (struct SharpMemoryImage*)bi->images; 50 | return img_data->num_images; 51 | } 52 | 53 | uint16_t image_width(struct BitmapImages* images, uint32_t id) { 54 | struct ImageInfo* ii = image_info(images, id); 55 | if (!ii) { 56 | return 0; 57 | } 58 | return ii->width; 59 | } 60 | 61 | uint16_t image_height(struct BitmapImages* images, uint32_t id) { 62 | struct ImageInfo* ii = image_info(images, id); 63 | if (!ii) { 64 | return 0; 65 | } 66 | return ii->height; 67 | } 68 | 69 | void image_draw(struct BitmapImages* bi, uint32_t id, int16_t x, int16_t y) { 70 | struct ImageInfo* ii = image_info(bi, id); 71 | if (!ii) { 72 | return; 73 | } 74 | 75 | const struct SharpMemoryImage* img_data = (struct SharpMemoryImage*)bi->images; 76 | const uint16_t width = ii->width; 77 | const uint16_t height = ii->height; 78 | uint8_t* error = &(bi->error); 79 | 80 | map_rle_image( 81 | bi->bitmap, 82 | img_data->data + ii->offset, 83 | x, 84 | y, 85 | width, 86 | height, 87 | error); 88 | } 89 | 90 | void image_draw_tiled( 91 | struct BitmapImages* bi, 92 | uint32_t first_id, 93 | uint16_t columns, 94 | uint16_t rows, 95 | int16_t x, 96 | int16_t y) { 97 | // The first task is to figure out the starting and 98 | // ending columns 99 | const uint16_t tile_width = image_width(bi, first_id); 100 | const uint16_t min_column = (-x) >= tile_width ? 101 | (-x) / tile_width : 102 | 0; 103 | int16_t last_x = x + (tile_width * (columns - 1)); 104 | if (last_x > bi->bitmap->width) { 105 | last_x = bi->bitmap->width; 106 | } 107 | const uint16_t max_column = (last_x - x) / tile_width; 108 | 109 | // same calculations, but for rows 110 | const uint16_t tile_height = image_height(bi, first_id); 111 | const uint16_t min_row = (-y) >= tile_height ? 112 | (-y) / tile_height : 113 | 0; 114 | int16_t last_y = y + (tile_height * (rows - 1)); 115 | if (last_y > bi->bitmap->height) { 116 | last_y = bi->bitmap->height; 117 | } 118 | const uint16_t max_row = (last_y - y) / tile_height; 119 | 120 | // draw the tiles 121 | for (uint16_t row = min_row; row <= max_row; ++row) { 122 | const uint32_t row_id = first_id + (row * columns); 123 | for (uint16_t column = min_column; column <= max_column; ++column) { 124 | image_draw( 125 | bi, 126 | row_id + column, 127 | x + (column * tile_width), 128 | y + (row * tile_height)); 129 | } 130 | } 131 | } 132 | 133 | uint16_t image_width_tiled( 134 | struct BitmapImages* bi, uint32_t first_id, uint16_t columns) { 135 | const uint16_t tile_width = image_width(bi, first_id); 136 | if (columns <= 1) { 137 | return tile_width; 138 | } 139 | return (tile_width * (columns - 1)) + image_width(bi, first_id + columns - 1); 140 | } 141 | 142 | uint16_t image_height_tiled( 143 | struct BitmapImages* bi, uint32_t first_id, uint16_t columns, uint16_t rows) { 144 | const uint16_t tile_height = image_height(bi, first_id); 145 | if (rows <= 1) { 146 | return tile_height; 147 | } 148 | return (tile_height * (rows - 1)) + image_height(bi, first_id + (rows * columns) - 1); 149 | } 150 | -------------------------------------------------------------------------------- /include/sharpdisp/doublebuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_SHARPDISP_DOUBLEBUFFER 2 | #define LIB_SHARPDISP_DOUBLEBUFFER 3 | 4 | // Provides a mechanism for multi-core double-buffering. 5 | // 6 | // sharpdisp_refresh is blocking and can take a significant amount of time 7 | // The measured refresh time at an SPI freqiency of 10Mhz is 14 ms. If you want 8 | // a 60 FPS refresh, you will need to trun frames every 16ms so not a lot of 9 | // time for the drawing code to operate in this case. 10 | // 11 | // A solution is to use double buffering. 12 | // The implementation uses both of the pi picos cores. Core 0 is the general use 13 | // core and core 1 will be used to send the SPI signals (Core 1 can also be used 14 | // for other things if you need that). 15 | // 16 | // There are 2 buffers. Core 0 writes to one while Core 1 sends output to SPI 17 | // from the others. The buffers swap on every frame rendered. 18 | // 19 | // It is possible for core 0 to complete much faster than core 1 or visa versa, 20 | // therefore a locking system is needed to appropriately coordinate the usage 21 | // of the two buffers. 22 | // 23 | // Example: 24 | // 25 | // #define WIDTH 400 26 | // #define HEIGHT 240 27 | // 28 | // uint8_t disp_buffer1[BITMAP_SIZE(WIDTH, HEIGHT)] 29 | // uint8_t disp_buffer2[BITMAP_SIZE(WIDTH, HEIGHT)] 30 | // 31 | // void main() { 32 | // struct SharpDisp sd; 33 | // sharpdisp_init_default(&sd, disp_buffer, WIDTH, HEIGHT, 0x00); 34 | // struct DoubleBuffer db; 35 | // doublebuffer_init(&db, &sd, disp_buffer2); 36 | // while (1) { 37 | // // db.bitmap will point to disp_buffer1 or disp_buffer2 38 | // draw_something(&db.bitmap); 39 | // // Waits for the refresh 40 | // doublebuffer_swap(&db); 41 | // } 42 | // } 43 | 44 | #include "sharpdisp/bitmap.h" 45 | #include "sharpdisp/sharpdisp.h" 46 | #include "pico/util/queue.h" 47 | 48 | struct DoubleBuffer { 49 | // currently rendering 50 | struct SharpDisp* disp; 51 | // for writing 52 | struct Bitmap bitmap; 53 | // A single slot queue. When it has an entry, the CPU1 is free to render disp 54 | queue_t frame_is_ready; 55 | // A single slot queue. When it has an entry, CPU0 is free to swap the buffers 56 | queue_t frame_is_rendered; 57 | }; 58 | 59 | // This is the easy version of init that takes care starting the rendering 60 | // thread on CPU1 for you. The downside is that you can't really use CPU1 61 | // for anything else when going this path but in many cases it's not a problem. 62 | // Look to doublebuffer_init_nostart() if you need to share CPU1 between 63 | // rendering and other tasks. 64 | void doublebuffer_init( 65 | struct DoubleBuffer* db, 66 | struct SharpDisp* disp, 67 | uint8_t* buff2, 68 | uint32_t frame_period_ms); 69 | 70 | // The _nostart version is used when you want to start up and control cpu1 71 | // yourself so that you can use it for more things than just pushing frames. 72 | // When using this method, your CPU1 thread will need to call 73 | // doublebuffer_core1_render() often enough to achieve the desired FPS. 74 | // Also note that doublebuffer_core1_render() can block up to frame_period_ms 75 | // or even longer (if cpu0 is not clearing the freame for rendering). Thus 76 | // you will probably want to do all of your other CPU1 work first, then 77 | // call dobulebuffer_core1_render() at the end to wrap up the frame. 78 | // Alternatively, set frame_period_ms to 0 and handle the FPS management your 79 | // own way. 80 | void doublebuffer_init_nostart( 81 | struct DoubleBuffer* db, 82 | struct SharpDisp* disp, 83 | uint8_t* buff2, 84 | uint32_t frame_period_ms); 85 | 86 | // Your code will call this instead of sharpdisp_refresh(). The function 87 | // waits-as-needed for the outgoing buffer to be sent, swaps the buffer 88 | // pointers and finally signals the frame renderer (running on CPU 1) 89 | // that there is another frame to send to the hardware. This scheme frees 90 | // CPU0 to work on whatever it wants while CPU1 is busy sending the frame 91 | // data to the hardware. 92 | void doublebuffer_swap(struct DoubleBuffer* db); 93 | 94 | // The sharp display hardware needs to be refreshed with a cadence 95 | // to avoid display issues. If you want to sent a refresh but have 96 | // no new data to send, call doublebuffer_refresh() instead 97 | // of doublebuffer_swap(). doublebuffer_refresh() will update 98 | // the sharp display hardware without swapping buffers, effectively 99 | // repeating whatever was sent by the last-issued doublebuffer_swap(). 100 | void doublebuffer_refresh(struct DoubleBuffer* db); 101 | 102 | // doublebuffer_sleep_ms() is a convienence method that will block 103 | // for the given requested ms value, refreshing the display while it 104 | // blocks. Note that the ms value is coarse (tolerance around the 105 | // frame_period_ms value given to the init function) thus this 106 | // function should not be used where tight timing tolerances are 107 | // required. Set swap to 1 if you want the function to start with 108 | // a doublebuffer_swap and zero otherwise. 109 | void doublebuffer_sleep_ms(struct DoubleBuffer* db, uint8_t swap, uint32_t ms); 110 | 111 | // Only call this function if you used doublebuffer_init_nostart(). It is 112 | // called in the case where you are setting up CPU1 yourself. See the init function 113 | // above for more details on what to do. 114 | void doublebuffer_core1_render(void); 115 | 116 | #endif -------------------------------------------------------------------------------- /test/imagetests.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "sharpdisp/bitmap.h" 3 | #include "sharpdisp/bitmapimage.h" 4 | #include "images.h" 5 | 6 | static struct TestData image_imgba_data = { 7 | 12, 8 | 12, 9 | { 10 | {0, 0, 1}, 11 | {6, 0, 1}, 12 | {1, 1, 1}, 13 | {3, 1, 1}, 14 | {5, 1, 1}, 15 | {2, 2, 1}, 16 | {4, 2, 1}, 17 | {1, 3, 1}, 18 | {3, 3, 1}, 19 | {5, 3, 1}, 20 | {0, 4, 1}, 21 | {6, 4, 1}, 22 | } 23 | }; 24 | struct TestData* test_image_imgba(struct Bitmap* bitmap) { 25 | struct BitmapImages bi; 26 | image_init(&bi, images, bitmap); 27 | image_draw(&bi, SMALL_IMG, 0, 0); 28 | return &image_imgba_data; 29 | } 30 | 31 | static struct TestData image_imgx1_data = {0, 0, {}}; 32 | struct TestData* test_image_imgx1(struct Bitmap* bitmap) { 33 | struct BitmapImages bi; 34 | image_init(&bi, images, bitmap); 35 | image_draw(&bi, SMALL_IMG, -7, 0); 36 | return &image_imgx1_data; 37 | } 38 | 39 | static struct TestData image_imgx2_data = { 40 | 2, 41 | 2, 42 | { 43 | {0, 0, 1}, 44 | {0, 4, 1}, 45 | } 46 | }; 47 | struct TestData* test_image_imgx2(struct Bitmap* bitmap) { 48 | struct BitmapImages bi; 49 | image_init(&bi, images, bitmap); 50 | image_draw(&bi, SMALL_IMG, -6, 0); 51 | return &image_imgx2_data; 52 | } 53 | 54 | static struct TestData image_imgx3_data = { 55 | 10, 56 | 10, 57 | { 58 | {5, 0, 1}, 59 | {0, 1, 1}, 60 | {2, 1, 1}, 61 | {4, 1, 1}, 62 | {1, 2, 1}, 63 | {3, 2, 1}, 64 | {0, 3, 1}, 65 | {2, 3, 1}, 66 | {4, 3, 1}, 67 | {5, 4, 1}, 68 | } 69 | }; 70 | struct TestData* test_image_imgx3(struct Bitmap* bitmap) { 71 | struct BitmapImages bi; 72 | image_init(&bi, images, bitmap); 73 | image_draw(&bi, SMALL_IMG, -1, 0); 74 | return &image_imgx3_data; 75 | } 76 | 77 | static struct TestData image_imgx4_data = {12, 0, {}}; 78 | struct TestData* test_image_imgx4(struct Bitmap* bitmap) { 79 | struct BitmapImages bi; 80 | image_init(&bi, images, bitmap); 81 | image_draw(&bi, SMALL_IMG, bitmap->width - 7, 0); 82 | return &image_imgx4_data; 83 | } 84 | 85 | static struct TestData image_imgx5_data = {10, 0, {}}; 86 | struct TestData* test_image_imgx5(struct Bitmap* bitmap) { 87 | struct BitmapImages bi; 88 | image_init(&bi, images, bitmap); 89 | image_draw(&bi, SMALL_IMG, bitmap->width - 6, 0); 90 | return &image_imgx5_data; 91 | } 92 | 93 | static struct TestData image_imgx6_data = {2, 0, {}}; 94 | struct TestData* test_image_imgx6(struct Bitmap* bitmap) { 95 | struct BitmapImages bi; 96 | image_init(&bi, images, bitmap); 97 | image_draw(&bi, SMALL_IMG, bitmap->width - 1, 0); 98 | return &image_imgx6_data; 99 | } 100 | 101 | static struct TestData image_imgx7_data = {0, 0, {}}; 102 | struct TestData* test_image_imgx7(struct Bitmap* bitmap) { 103 | struct BitmapImages bi; 104 | image_init(&bi, images, bitmap); 105 | image_draw(&bi, SMALL_IMG, bitmap->width, 0); 106 | return &image_imgx7_data; 107 | } 108 | 109 | static struct TestData image_imgy1_data = {0, 0, {}}; 110 | struct TestData* test_image_imgy1(struct Bitmap* bitmap) { 111 | struct BitmapImages bi; 112 | image_init(&bi, images, bitmap); 113 | image_draw(&bi, SMALL_IMG, 0, -5); 114 | return &image_imgy1_data; 115 | } 116 | 117 | static struct TestData image_imgy2_data = { 118 | 2, 119 | 2, 120 | { 121 | {0, 0, 1}, 122 | {6, 0, 1}, 123 | } 124 | }; 125 | struct TestData* test_image_imgy2(struct Bitmap* bitmap) { 126 | struct BitmapImages bi; 127 | image_init(&bi, images, bitmap); 128 | image_draw(&bi, SMALL_IMG, 0, -4); 129 | return &image_imgy2_data; 130 | } 131 | 132 | static struct TestData image_imgy3_data = {10, 0, {}}; 133 | struct TestData* test_image_imgy3(struct Bitmap* bitmap) { 134 | struct BitmapImages bi; 135 | image_init(&bi, images, bitmap); 136 | image_draw(&bi, SMALL_IMG, 0, -1); 137 | return &image_imgy3_data; 138 | } 139 | 140 | static struct TestData image_imgy4_data = {12, 0, {}}; 141 | struct TestData* test_image_imgy4(struct Bitmap* bitmap) { 142 | struct BitmapImages bi; 143 | image_init(&bi, images, bitmap); 144 | image_draw(&bi, SMALL_IMG, 0, bitmap->height - 5); 145 | return &image_imgy4_data; 146 | } 147 | 148 | static struct TestData image_imgy5_data = {10, 0, {}}; 149 | struct TestData* test_image_imgy5(struct Bitmap* bitmap) { 150 | struct BitmapImages bi; 151 | image_init(&bi, images, bitmap); 152 | image_draw(&bi, SMALL_IMG, 0, bitmap->height - 4); 153 | return &image_imgy5_data; 154 | } 155 | 156 | static struct TestData image_imgy6_data = {2, 0, {}}; 157 | struct TestData* test_image_imgy6(struct Bitmap* bitmap) { 158 | struct BitmapImages bi; 159 | image_init(&bi, images, bitmap); 160 | image_draw(&bi, SMALL_IMG, 0, bitmap->height - 1); 161 | return &image_imgy6_data; 162 | } 163 | 164 | static struct TestData image_imgy7_data = {0, 0, {}}; 165 | struct TestData* test_image_imgy7(struct Bitmap* bitmap) { 166 | struct BitmapImages bi; 167 | image_init(&bi, images, bitmap); 168 | image_draw(&bi, SMALL_IMG, 0, bitmap->height); 169 | return &image_imgy7_data; 170 | } 171 | 172 | static struct TestData image_tile0_data = {30, 0, {}}; 173 | struct TestData* test_image_tile0(struct Bitmap* bitmap) { 174 | struct BitmapImages bi; 175 | image_init(&bi, images, bitmap); 176 | image_draw_tiled(&bi, MEDIUM_IMG_0_0, MEDIUM_IMG_COLUMNS, MEDIUM_IMG_ROWS, 0, 0); 177 | return &image_tile0_data; 178 | } 179 | 180 | static struct TestData image_tile1_data = {0, 0, {}}; 181 | struct TestData* test_image_tile1(struct Bitmap* bitmap) { 182 | struct BitmapImages bi; 183 | image_init(&bi, images, bitmap); 184 | bitmap->mode = BITMAP_INVERSE; 185 | const uint16_t rounds = 100; 186 | 187 | for (uint16_t i=0; iwidth + 1); 189 | const uint16_t y = rand16(-10, bitmap->height + 1); 190 | 191 | // draw it 192 | image_draw(&bi, MEDIUM_IMG, x, y); 193 | // erase it 194 | image_draw_tiled(&bi, MEDIUM_IMG_0_0, MEDIUM_IMG_COLUMNS, MEDIUM_IMG_ROWS, x, y); 195 | } 196 | 197 | return &image_tile1_data; 198 | } -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "pico/stdlib.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "common.h" 11 | 12 | #define USB_WAIT_MS 2000 13 | #define WAIT_MS 1000 14 | #define REFRESH_MS 32 15 | #define TEXT_HEIGHT 10 16 | #define XPAD 4 17 | 18 | // Call all APIs that result in something being drawn to the display 19 | // Then checks pixel counts and certain called-out pixel values 20 | 21 | const uint8_t eyecatcher_bytes[] = { 0xAB, 0xCD, 0x12, 0x34 }; 22 | uint8_t disp_buffer[BITMAP_SIZE(DISPLAY_WIDTH, DISPLAY_HEIGHT)]; 23 | uint8_t disp_buffer2[BITMAP_SIZE(DISPLAY_WIDTH, DISPLAY_HEIGHT)]; 24 | uint8_t bitmap_buffer[BITMAP_SIZE(WIDTH, HEIGHT) + sizeof(eyecatcher_bytes) * 2]; 25 | char printf_buffer[128]; 26 | struct SharpDisp sd; 27 | struct DoubleBuffer db; 28 | struct BitmapText text; 29 | 30 | // Test list is autogenerated via gen_testnames.py 31 | #include "tests.inc" 32 | 33 | struct DrawState { 34 | uint16_t x; 35 | uint16_t y; 36 | } ds; 37 | 38 | static inline uint32_t uptime_ms() { 39 | return to_ms_since_boot(get_absolute_time()); 40 | } 41 | 42 | static void usb_wait(void) { 43 | const uint32_t tstart = uptime_ms(); 44 | uint32_t tend = tstart + USB_WAIT_MS; 45 | for (uint32_t t=tstart; t < tend; t=uptime_ms()) { 46 | bitmap_clear(&db.bitmap); 47 | text.x = 50; 48 | text.y = DISPLAY_HEIGHT / 2; 49 | text_printf(&text, "WAIT FOR USB: %d/%d ms", t-tstart, USB_WAIT_MS); 50 | doublebuffer_swap(&db); 51 | } 52 | bitmap_clear(&db.bitmap); 53 | } 54 | 55 | static void init(void) { 56 | stdio_init_all(); 57 | sleep_ms(100); // wait for voltages to stabilize 58 | sharpdisp_init_default(&sd, disp_buffer, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0x00); 59 | doublebuffer_init(&db, &sd, disp_buffer2, REFRESH_MS); 60 | text_init(&text, liberation_mono_10, &db.bitmap); 61 | text.printf_buffer = printf_buffer; 62 | usb_wait(); 63 | printf("Test Framework Initialized\n"); 64 | } 65 | 66 | static void set_eyecatcher(uint8_t addr[]) { 67 | for (int idx=0; idx < sizeof(eyecatcher_bytes); ++idx) { 68 | addr[idx] = eyecatcher_bytes[idx]; 69 | } 70 | } 71 | 72 | static void prepare_bitmap(struct Bitmap* bitmap) { 73 | bitmap_init( 74 | bitmap, 75 | bitmap_buffer + sizeof(eyecatcher_bytes), 76 | WIDTH, 77 | HEIGHT, 78 | BITMAP_WHITE, 79 | 0x00); 80 | memset(bitmap_buffer, 0, sizeof(bitmap_buffer)); 81 | // Eye catchers are to help detect buffer overruns/underruns 82 | set_eyecatcher(bitmap_buffer); 83 | set_eyecatcher(bitmap_buffer + sizeof(bitmap_buffer) - sizeof(eyecatcher_bytes)); 84 | } 85 | 86 | static void increment_display_slot(void) { 87 | ds.x += WIDTH + XPAD; 88 | if ((ds.x + WIDTH) > DISPLAY_WIDTH) { 89 | ds.x = 0; 90 | ds.y += HEIGHT + TEXT_HEIGHT; 91 | } 92 | // the 10 is for text height 93 | if ((ds.y + HEIGHT + TEXT_HEIGHT) > DISPLAY_HEIGHT) { 94 | ds.y = 0; 95 | doublebuffer_sleep_ms(&db, 0, WAIT_MS); 96 | bitmap_clear(&db.bitmap); 97 | } else { 98 | sleep_ms(REFRESH_MS); 99 | } 100 | } 101 | 102 | static void draw_result(struct Bitmap* bitmap, const char* name, uint8_t errors) { 103 | db.bitmap.mode = BITMAP_WHITE; 104 | bitmap_blit(&db.bitmap, ds.x, ds.y, bitmap); 105 | if (errors) { 106 | bitmap_filled_rect( 107 | &db.bitmap, ds.x, ds.y + bitmap->height, bitmap->width, TEXT_HEIGHT); 108 | } 109 | db.bitmap.mode = BITMAP_INVERSE; 110 | text.x = ds.x; 111 | text.y = ds.y + bitmap->height; 112 | text_str(&text, name); 113 | doublebuffer_swap(&db); 114 | bitmap_copy(&db.bitmap, &sd.bitmap); 115 | increment_display_slot(); 116 | } 117 | 118 | static uint8_t check_eyecatchers(const uint8_t* data, const char* context) { 119 | uint8_t errors = 0; 120 | for (int idx = 0; idx < sizeof(eyecatcher_bytes); ++idx) { 121 | if (data[idx] != eyecatcher_bytes[idx]) { 122 | printf(" Eyecatcher %s, byte %d FAIL (want=0x%02X, got=0x%02X)\n", 123 | context, 124 | idx, 125 | eyecatcher_bytes[idx], 126 | data[idx]); 127 | ++errors; 128 | } 129 | } 130 | return (errors > 0) ? 1 : 0; 131 | } 132 | 133 | static uint8_t check_pixel_count(struct Bitmap* bitmap, uint16_t want) { 134 | uint16_t got = 0; 135 | const uint16_t width = bitmap->width; 136 | const uint16_t height = bitmap->height; 137 | // This code could be faster if it considered entire bytes. 138 | // But the gial here is not speed as much as simplicity as 139 | // it's a testing framework. 140 | for (uint16_t y=0; yvalue != 0; 157 | uint8_t got = bitmap_get_point(bitmap, p->x, p->y) != 0; 158 | if (want != got) { 159 | printf(" Sample: [%d,%d] = want=%d, got=%d FAIL\n", p->x, p->y, want, got); 160 | } 161 | return want != got; 162 | } 163 | 164 | static uint8_t check_test_data(struct Bitmap* bitmap, struct TestData* test_data) { 165 | uint8_t errors = 0; 166 | errors += check_eyecatchers(bitmap_buffer, "start"); 167 | errors += check_eyecatchers( 168 | bitmap_buffer + sizeof(bitmap_buffer) - sizeof(eyecatcher_bytes), "end"); 169 | errors += check_pixel_count(bitmap, test_data->on_count); 170 | for (uint16_t sample = 0; sample < test_data->num_samples; ++sample) { 171 | errors += test_a_sample(bitmap, test_data->samples + sample); 172 | } 173 | return (errors > 0) ? 1 : 0; 174 | } 175 | 176 | static uint8_t run_test(int index) { 177 | struct Bitmap bitmap; 178 | prepare_bitmap(&bitmap); 179 | struct TestData* test_data = tests[index](&bitmap); 180 | printf("%s:\n", test_names[index]); 181 | uint8_t errors = check_test_data(&bitmap, test_data); 182 | draw_result(&bitmap, test_names[index], errors); 183 | return errors; 184 | } 185 | 186 | static void final_status(uint32_t failures, uint32_t num_ran, uint32_t num_tests) { 187 | printf("Tests Completed: %d failures. %d/%d passed, %d skipped\n", 188 | failures, 189 | (num_ran - failures), 190 | num_ran, 191 | num_tests - num_ran); 192 | 193 | text.x = 10; 194 | text.y = DISPLAY_HEIGHT - TEXT_HEIGHT * 2; 195 | bitmap_copy(&db.bitmap, &sd.bitmap); 196 | if (failures > 0) { 197 | text_printf(&text, "Completed: %d/%d FAILED, %d SKIP", failures, num_ran, num_tests - num_ran); 198 | } else { 199 | text_printf(&text, "ALL %d TESTS PASSED.", num_tests); 200 | } 201 | text.x = 10; 202 | text.y += TEXT_HEIGHT; 203 | text_printf(&text, "Connect a USB console for more details.", failures); 204 | doublebuffer_swap(&db); 205 | } 206 | 207 | int main(void) { 208 | init(); 209 | const int num_tests = sizeof(tests) / sizeof(tests[0]); 210 | uint32_t failures = 0; 211 | ds.x = 0; 212 | ds.y = 0; 213 | int i = 0; 214 | for (; i < num_tests; ++i) { 215 | failures += run_test(i); 216 | if ((failures > 0) && (ds.x == 0) && (ds.y == 0)) { 217 | // stop here for analysis 218 | break; 219 | } 220 | } 221 | final_status(failures, i, num_tests); 222 | while (1) { 223 | doublebuffer_refresh(&db); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /test/texttests.c: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "sharpdisp/bitmap.h" 3 | #include "sharpdisp/bitmaptext.h" 4 | #include "fonts/liberation_sans_18.h" 5 | 6 | static struct TestData bitmap_char1_data = {41, 0, {}}; 7 | struct TestData* test_text_char1(struct Bitmap* bitmap) { 8 | struct BitmapText text; 9 | text_init(&text, liberation_sans_18, bitmap); 10 | text_char(&text, 'A'); 11 | assert_true(&bitmap_char1_data, text.error == 0, "text.error want=0, got=%d", text.error); 12 | assert_true(&bitmap_char1_data, text.y == 0, "text.y want=0, got=%d", text.y); 13 | assert_true(&bitmap_char1_data, text.x == 14, "text.x want=14, got=%d", text.x); 14 | return &bitmap_char1_data; 15 | } 16 | 17 | static struct TestData bitmap_char2_data = {0, 0, {}}; 18 | struct TestData* test_text_char2(struct Bitmap* bitmap) { 19 | struct BitmapText text; 20 | text_init(&text, liberation_sans_18, bitmap); 21 | text.x = -14; 22 | text_char(&text, 'A'); 23 | assert_true(&bitmap_char2_data, text.error == 0, "text.error want=0, got=%d", text.error); 24 | assert_true(&bitmap_char2_data, text.x == 0, "text.x want=0, got=%d", text.x); 25 | return &bitmap_char2_data; 26 | } 27 | 28 | static struct TestData bitmap_char3_data = {20, 0, {}}; 29 | struct TestData* test_text_char3(struct Bitmap* bitmap) { 30 | struct BitmapText text; 31 | text_init(&text, liberation_sans_18, bitmap); 32 | text.x = -7; 33 | text_char(&text, 'A'); 34 | assert_true(&bitmap_char3_data, text.error == 0, "text.error want=0, got=%d", text.error); 35 | assert_true(&bitmap_char3_data, text.x == 7, "text.x want=7, got=%d", text.x); 36 | return &bitmap_char3_data; 37 | } 38 | 39 | static struct TestData bitmap_char4_data = {41, 0, {}}; 40 | struct TestData* test_text_char4(struct Bitmap* bitmap) { 41 | struct BitmapText text; 42 | text_init(&text, liberation_sans_18, bitmap); 43 | text.x = bitmap->width - 14; 44 | text_char(&text, 'A'); 45 | assert_true(&bitmap_char4_data, text.error == 0, "text.error want=0, got=%d", text.error); 46 | assert_true(&bitmap_char4_data, text.x == bitmap->width, "text.x want=%d, got=%d", bitmap->width, text.x); 47 | return &bitmap_char4_data; 48 | } 49 | 50 | static struct TestData bitmap_char5_data = {21, 0, {}}; 51 | struct TestData* test_text_char5(struct Bitmap* bitmap) { 52 | struct BitmapText text; 53 | text_init(&text, liberation_sans_18, bitmap); 54 | text.x = bitmap->width - 7; 55 | text_char(&text, 'A'); 56 | assert_true(&bitmap_char5_data, text.error == 0, "text.error want=0, got=%d", text.error); 57 | assert_true(&bitmap_char5_data, text.x == bitmap->width + 7, "text.x want=%d, got=%d", bitmap->width + 7, text.x); 58 | return &bitmap_char5_data; 59 | } 60 | 61 | static struct TestData bitmap_char6_data = {0, 0, {}}; 62 | struct TestData* test_text_char6(struct Bitmap* bitmap) { 63 | struct BitmapText text; 64 | text_init(&text, liberation_sans_18, bitmap); 65 | text.x = bitmap->width - 1; 66 | text_char(&text, 'A'); 67 | assert_true(&bitmap_char6_data, text.error == 0, "text.error want=0, got=%d", text.error); 68 | assert_true(&bitmap_char6_data, text.x == bitmap->width + 13, "text.x want=%d, got=%d", bitmap->width + 13, text.x); 69 | return &bitmap_char6_data; 70 | } 71 | 72 | static struct TestData bitmap_char7_data = {0, 0, {}}; 73 | struct TestData* test_text_char7(struct Bitmap* bitmap) { 74 | struct BitmapText text; 75 | text_init(&text, liberation_sans_18, bitmap); 76 | text.x = bitmap->width; 77 | text_char(&text, 'A'); 78 | assert_true(&bitmap_char7_data, text.error == 0, "text.error want=0, got=%d", text.error); 79 | assert_true(&bitmap_char7_data, text.x == bitmap->width, "text.x want=%d, got=%d", bitmap->width + 14, text.x); 80 | return &bitmap_char7_data; 81 | } 82 | 83 | static struct TestData bitmap_char8_data = {28, 0, {}}; 84 | struct TestData* test_text_char8(struct Bitmap* bitmap) { 85 | struct BitmapText text; 86 | text_init(&text, liberation_sans_18, bitmap); 87 | text.y = -7; 88 | text_char(&text, 'A'); 89 | assert_true(&bitmap_char8_data, text.error == 0, "text.error want=0, got=%d", text.error); 90 | assert_true(&bitmap_char8_data, text.y == -7, "text.y want=7, got=%d", text.y); 91 | return &bitmap_char8_data; 92 | } 93 | 94 | static struct TestData bitmap_char9_data = {13, 0, {}}; 95 | struct TestData* test_text_char9(struct Bitmap* bitmap) { 96 | struct BitmapText text; 97 | text_init(&text, liberation_sans_18, bitmap); 98 | text.y = bitmap->height - 7; 99 | text_char(&text, 'A'); 100 | assert_true(&bitmap_char9_data, text.error == 0, "text.error want=0, got=%d", text.error); 101 | assert_true(&bitmap_char9_data, text.y == bitmap->height - 7, "text.y want=%d, got=%d", bitmap->height + 7, text.y); 102 | return &bitmap_char9_data; 103 | } 104 | 105 | // Unknown character 106 | static struct TestData bitmap_chr10_data = {0, 0, {}}; 107 | struct TestData* test_text_chr10(struct Bitmap* bitmap) { 108 | struct BitmapText text; 109 | text_init(&text, liberation_sans_18, bitmap); 110 | text_char(&text, 200); 111 | assert_true(&bitmap_chr10_data, text.error == 0, "text.error want=0, got=%d", text.error); 112 | assert_true(&bitmap_chr10_data, text.y == 0, "text.y want=0, got=%d", text.y); 113 | assert_true(&bitmap_chr10_data, text.x == 0, "text.x want=0, got=%d", text.x); 114 | return &bitmap_chr10_data; 115 | } 116 | 117 | // Bad font data 118 | static struct TestData bitmap_chr11_data = {0, 0, {}}; 119 | struct TestData* test_text_chr11(struct Bitmap* bitmap) { 120 | struct BitmapText text; 121 | text_init(&text, liberation_sans_18 + 1, bitmap); 122 | text_char(&text, 'A'); 123 | assert_true(&bitmap_chr11_data, text.error != 0, "text.error want!=0, got=0"); 124 | assert_true(&bitmap_chr11_data, text.y == 0, "text.y want=0, got=%d", text.y); 125 | assert_true(&bitmap_chr11_data, text.x == 0, "text.x want=0, got=%d", text.x); 126 | return &bitmap_chr11_data; 127 | } 128 | 129 | static struct TestData bitmap_slen1_data = {79, 0, {}}; 130 | struct TestData* test_text_slen1(struct Bitmap* bitmap) { 131 | struct BitmapText text; 132 | text_init(&text, liberation_sans_18, bitmap); 133 | text_strlen(&text, "Hi", 2); 134 | assert_true(&bitmap_slen1_data, text.error == 0, "text.error want=0, got=%d", text.error); 135 | assert_true(&bitmap_slen1_data, text.x == 21, "text.x want=21, got=%d", text.x); 136 | return &bitmap_slen1_data; 137 | } 138 | 139 | static struct TestData bitmap_str1_data = {79, 0, {}}; 140 | struct TestData* test_text_str1(struct Bitmap* bitmap) { 141 | struct BitmapText text; 142 | text_init(&text, liberation_sans_18, bitmap); 143 | text_str(&text, "Hi"); 144 | assert_true(&bitmap_str1_data, text.error == 0, "text.error want=0, got=%d", text.error); 145 | assert_true(&bitmap_str1_data, text.x == 21, "text.x want=21, got=%d", text.x); 146 | return &bitmap_str1_data; 147 | } 148 | 149 | static struct TestData bitmap_prtf1_data = {61, 0, {}}; 150 | struct TestData* test_text_prtf1(struct Bitmap* bitmap) { 151 | struct BitmapText text; 152 | char str[16]; 153 | text_init(&text, liberation_sans_18, bitmap); 154 | text.printf_buffer = str; 155 | text_printf(&text, "%d", 10); 156 | assert_true(&bitmap_prtf1_data, text.error == 0, "text.error want=0, got=%d", text.error); 157 | assert_true(&bitmap_prtf1_data, text.x == 24, "text.x want=24, got=%d", text.x); 158 | return &bitmap_prtf1_data; 159 | } 160 | 161 | static struct TestData bitmap_mtric_data = {0, 0, {}}; 162 | struct TestData* test_text_mtric(struct Bitmap* bitmap) { 163 | struct BitmapText text; 164 | text_init(&text, liberation_sans_18, bitmap); 165 | uint8_t w = text_char_width(&text, '0'); 166 | assert_true(&bitmap_mtric_data, w == 12, "char_width want=12, got=%d", w); 167 | w = text_char_width(&text, 200); 168 | assert_true(&bitmap_mtric_data, w == 0, "char_width want=0, got=%d", w); 169 | w = text_strlen_width(&text, "Hi", 2); 170 | assert_true(&bitmap_mtric_data, w == 21, "char_width want=21, got=%d", w); 171 | w = text_str_width(&text, "OK"); 172 | assert_true(&bitmap_mtric_data, w == 30, "char_width want=30, got=%d", w); 173 | uint8_t h = text_height(&text); 174 | assert_true(&bitmap_mtric_data, h == 18, "char_height want=18, got=%d", h); 175 | return &bitmap_mtric_data; 176 | } -------------------------------------------------------------------------------- /include/sharpdisp/bitmap.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_BITMAP 2 | #define LIB_BITMAP 3 | 4 | #include 5 | #include 6 | 7 | // Transfer modes 8 | #define BITMAP_WHITE 0x01 // a 1 in the data creates a white pixel 9 | #define BITMAP_BLACK 0x02 // a 1 in the data creates a black pixel 10 | #define BITMAP_INVERSE 0x03 // a 1 in the data inverts the current pixels 11 | 12 | // A Bitmap is used to hold the image data that will be sent to the screen. 13 | // It can also hold other image data that can be drawn to off-screen 14 | // 15 | // The format is 8 bits per pixel. Each byte represents a 8x1 pixel slice. 16 | // consecutive bytes orient from left to right with wrapping at the point 17 | // where the width is exceeded. 18 | // 19 | // If the width is not a multiple of 8, then some buts at the end of the 20 | // last byte in a row will go unused. 21 | struct Bitmap { 22 | uint16_t width; 23 | // width_bytes is equal to (width + 7) / 8. Normally it's not a great idea to 24 | // represent the same number with two variables in case someone changes just one. 25 | // But in this case, there is the benefit of making the pixel operations quite 26 | // a bit more efficient (as they don't have to calculate the byte width on every pixel 27 | // set/clear). 28 | uint16_t width_bytes; 29 | uint16_t height; 30 | // Draw function will use this transfer mode. See above. 31 | uint8_t mode; 32 | // 0x00 for a black background, 0xFF for a white background 33 | uint8_t clear_byte; 34 | uint8_t* data; 35 | }; 36 | 37 | #define BITMAP_SIZE(width, height) ((((width) + 7) >> 3) * (height)) 38 | 39 | void bitmap_init( 40 | struct Bitmap* b, 41 | uint8_t* buff, 42 | uint16_t width, 43 | uint16_t height, 44 | uint8_t mode, 45 | uint8_t clear_byte); 46 | 47 | // Copies one bitmap to another one. Rules: 48 | // - Bitmaps must point to different data 49 | // - Bitmaps must have the same widths 50 | // Violating either of these rules will cause this function to do nothing. 51 | // If this is too restrictive for your usecase, consider the slower 52 | // bitmap_copy_rect() or bitmap_blit() functions. 53 | void bitmap_copy(struct Bitmap* dest, const struct Bitmap* src); 54 | 55 | // This function is intended to copy a rectangular area 56 | // of a larger bitmap to a smaller one. The width and height 57 | // of dest represent the rect size. 58 | void bitmap_copy_rect( 59 | struct Bitmap* dest, 60 | const struct Bitmap* src, 61 | int16_t src_x, 62 | int16_t src_y); 63 | 64 | // Blits a bitmap onto another one. Uses dest->mode 65 | // in the blit operation thus you might need to preclear 66 | // the area with bitmap_filled_rect() in some cases. 67 | void bitmap_blit( 68 | struct Bitmap* dest, 69 | int16_t dest_x, 70 | int16_t dest_y, 71 | const struct Bitmap* src); 72 | 73 | // A transfer mode helper for rendering functions 74 | // data_byte is expected to already be pointing to the 75 | // correct byte in bitmap->data, this is inconvienent for 76 | // users but can save extra calculations within drawing functions. 77 | // Users should look to higher-level functions such as bitmap_setpoint 78 | // as well as the bitmapshapes library. 79 | static inline void bitmap_apply( 80 | uint8_t* data_byte, 81 | uint8_t mode, 82 | uint8_t data) { 83 | switch (mode) { 84 | case BITMAP_WHITE: 85 | *data_byte |= data; 86 | break; 87 | case BITMAP_BLACK: 88 | *data_byte &= ~data; 89 | break; 90 | case BITMAP_INVERSE: 91 | *data_byte ^= data; 92 | break; 93 | default: 94 | break; 95 | } 96 | } 97 | 98 | // bitmap_point can be an inefficient way to doing things in cases where 99 | // bitmap_set_byte (or just a memset) can do the job. Also look at 100 | // bitmapshapes.h for shape utils that are usually more efficient than calling 101 | // bitmap_point in a loop. 102 | static inline void bitmap_point_nocheck(struct Bitmap* b, uint16_t x, uint16_t y) { 103 | bitmap_apply( 104 | b->data + (y * b->width_bytes) + (x >> 3), 105 | b->mode, 106 | 0x80 >> (x & 0x07)); 107 | } 108 | static inline void bitmap_point(struct Bitmap* b, uint16_t x, uint16_t y) { 109 | if ((x < b->width) && (y < b->height)) { 110 | bitmap_point_nocheck(b, x, y); 111 | } 112 | } 113 | 114 | // returns zero if clear, non zero (not necessarily 1) if set. 115 | static inline uint8_t bitmap_get_point_no_check( 116 | const struct Bitmap* b, 117 | uint16_t x, 118 | uint16_t y) { 119 | return b->data[y * b->width_bytes + (x >> 3)] & (0x80 >> (x & 0x07)); 120 | } 121 | static inline uint8_t bitmap_get_point( 122 | const struct Bitmap* b, 123 | uint16_t x, 124 | uint16_t y) { 125 | if ((x < b->width) && (y < b->height)) { 126 | return bitmap_get_point_no_check(b, x, y); 127 | } 128 | return b->clear_byte; 129 | } 130 | 131 | // set an 8x1 pixel stripe with provisions for out-of-bounds 132 | static inline void bitmap_apply_stripe( 133 | struct Bitmap* b, 134 | int16_t x, 135 | int16_t y, 136 | uint8_t data) { 137 | if (!data) { 138 | return; 139 | } 140 | const uint16_t bwidth = b->width; 141 | const uint16_t bheight = b->height; 142 | const uint16_t wbytes = b->width_bytes; 143 | const uint8_t mode = b->mode; 144 | // First, the common case of everything being in bounds 145 | if ((x >= 0) && (y >= 0) && (y < bheight) && (x < bwidth)) { 146 | const uint16_t column = x >> 3; 147 | uint8_t* base = b->data + (y * wbytes) + column; 148 | const uint8_t shift = x & 0x07; 149 | bitmap_apply(base, mode, data >> shift); 150 | if (shift && (column < (wbytes - 1))) { 151 | bitmap_apply(base + 1, mode, data << (8 - shift)); 152 | if (column == (wbytes - 2) && (bwidth & 0x07)) { 153 | // This is the last byte in a bitmap row which contains some 154 | // "unused" bits. Even though these are unused, they still 155 | // need to be cleared out so that functions that expect these 156 | // bits to just be zero will not run into bugs (such as 157 | // drawing random artifacts on the right edge). 158 | base[1] &= (0xFF << (8 - (bwidth & 0x07))); 159 | } 160 | } else if (column == (wbytes - 1) && (bwidth & 0x07)) { 161 | // The same "last byte" issue as described above with the 162 | // same solution. 163 | base[0] &= (0xFF << (8 - (bwidth & 0x07))); 164 | } 165 | return; 166 | } 167 | 168 | // the next case to check is things off the screen 169 | if ((x <= -8) || (y < 0) || (x >= bwidth) || (y >= bheight)) { 170 | return; 171 | } 172 | 173 | // at this point, x is either partially shifted off the left 174 | uint8_t* base = b->data + (y * b->width_bytes); 175 | bitmap_apply(base, mode, data << (-x)); 176 | } 177 | 178 | // returns an 8x1 pixel stripe. Out of bounds return zeros 179 | static inline uint8_t bitmap_get_stripe( 180 | const struct Bitmap* b, 181 | int16_t x, 182 | int16_t y) { 183 | const uint16_t bwidth = b->width; 184 | const uint16_t bheight = b->height; 185 | // First, the common case of everything being in bounds 186 | if ((x >= 0) && (y >= 0) && (y < bheight) && (x <= bwidth)) { 187 | const uint16_t column = x >> 3; 188 | uint8_t* base = b->data + (y * b->width_bytes) + column; 189 | const uint8_t shift = x & 0x07; 190 | if (shift) { 191 | if (column < (bwidth - 1)) { 192 | return (base[0] << shift) | (base[1] >> (8 - shift)); 193 | } else { 194 | return base[0] << shift; 195 | } 196 | } else { 197 | return base[0]; 198 | } 199 | } 200 | 201 | // the next case to check is things off the screen 202 | if ((x <= -8) || (y < 0) || (x >= bwidth) || (y >= bheight)) { 203 | return 0x00; 204 | } 205 | 206 | // at this point, x is either partially shifted off the left or the right 207 | uint8_t* base = b->data + (y * b->width_bytes); 208 | if (x < 0) { 209 | return base[0] >> (-x); 210 | } 211 | 212 | return base[x >> 3] << (x & 0x07); 213 | } 214 | 215 | // Clear the entire bitmap. Change Bitmap.clear_byte to 0x00 for a black 216 | // backgroud or 0xFF for a white one. This information is stored in the 217 | // structure so that functions that need-to-know it (like console scrolling) 218 | // can have access. 219 | static inline void bitmap_clear(struct Bitmap* b) { 220 | memset(b->data, b->clear_byte, b->width_bytes * b->height); 221 | } 222 | 223 | #endif 224 | -------------------------------------------------------------------------------- /test/tests.inc: -------------------------------------------------------------------------------- 1 | // THIS FILE IS AUTOGENERATED 2 | // Run make or ./gen_testnames.py to create it 3 | 4 | struct TestData* test_bitmap_apst1(struct Bitmap* bitmap); 5 | struct TestData* test_bitmap_apst2(struct Bitmap* bitmap); 6 | struct TestData* test_bitmap_blit0(struct Bitmap* bitmap); 7 | struct TestData* test_bitmap_blit1(struct Bitmap* bitmap); 8 | struct TestData* test_bitmap_clr0(struct Bitmap* bitmap); 9 | struct TestData* test_bitmap_clr1(struct Bitmap* bitmap); 10 | struct TestData* test_bitmap_copy1(struct Bitmap* bitmap); 11 | struct TestData* test_bitmap_copy2(struct Bitmap* bitmap); 12 | struct TestData* test_bitmap_copy3(struct Bitmap* bitmap); 13 | struct TestData* test_bitmap_cpyr1(struct Bitmap* bitmap); 14 | struct TestData* test_bitmap_cpyr2(struct Bitmap* bitmap); 15 | struct TestData* test_bitmap_gtst1(struct Bitmap* bitmap); 16 | struct TestData* test_bitmap_gtst2(struct Bitmap* bitmap); 17 | struct TestData* test_bitmap_pnt(struct Bitmap* bitmap); 18 | struct TestData* test_image_imgba(struct Bitmap* bitmap); 19 | struct TestData* test_image_imgx1(struct Bitmap* bitmap); 20 | struct TestData* test_image_imgx2(struct Bitmap* bitmap); 21 | struct TestData* test_image_imgx3(struct Bitmap* bitmap); 22 | struct TestData* test_image_imgx4(struct Bitmap* bitmap); 23 | struct TestData* test_image_imgx5(struct Bitmap* bitmap); 24 | struct TestData* test_image_imgx6(struct Bitmap* bitmap); 25 | struct TestData* test_image_imgx7(struct Bitmap* bitmap); 26 | struct TestData* test_image_imgy1(struct Bitmap* bitmap); 27 | struct TestData* test_image_imgy2(struct Bitmap* bitmap); 28 | struct TestData* test_image_imgy3(struct Bitmap* bitmap); 29 | struct TestData* test_image_imgy4(struct Bitmap* bitmap); 30 | struct TestData* test_image_imgy5(struct Bitmap* bitmap); 31 | struct TestData* test_image_imgy6(struct Bitmap* bitmap); 32 | struct TestData* test_image_imgy7(struct Bitmap* bitmap); 33 | struct TestData* test_image_tile0(struct Bitmap* bitmap); 34 | struct TestData* test_image_tile1(struct Bitmap* bitmap); 35 | struct TestData* test_shapes_circ1(struct Bitmap* bitmap); 36 | struct TestData* test_shapes_circ2(struct Bitmap* bitmap); 37 | struct TestData* test_shapes_cons1(struct Bitmap* bitmap); 38 | struct TestData* test_shapes_cons2(struct Bitmap* bitmap); 39 | struct TestData* test_shapes_cons3(struct Bitmap* bitmap); 40 | struct TestData* test_shapes_cons4(struct Bitmap* bitmap); 41 | struct TestData* test_shapes_fcir1(struct Bitmap* bitmap); 42 | struct TestData* test_shapes_fcir2(struct Bitmap* bitmap); 43 | struct TestData* test_shapes_fill1(struct Bitmap* bitmap); 44 | struct TestData* test_shapes_fill2(struct Bitmap* bitmap); 45 | struct TestData* test_shapes_fill3(struct Bitmap* bitmap); 46 | struct TestData* test_shapes_fovl1(struct Bitmap* bitmap); 47 | struct TestData* test_shapes_fovl2(struct Bitmap* bitmap); 48 | struct TestData* test_shapes_fovl3(struct Bitmap* bitmap); 49 | struct TestData* test_shapes_frct1(struct Bitmap* bitmap); 50 | struct TestData* test_shapes_frct2(struct Bitmap* bitmap); 51 | struct TestData* test_shapes_frct3(struct Bitmap* bitmap); 52 | struct TestData* test_shapes_frct4(struct Bitmap* bitmap); 53 | struct TestData* test_shapes_frct5(struct Bitmap* bitmap); 54 | struct TestData* test_shapes_frct6(struct Bitmap* bitmap); 55 | struct TestData* test_shapes_frct7(struct Bitmap* bitmap); 56 | struct TestData* test_shapes_hline(struct Bitmap* bitmap); 57 | struct TestData* test_shapes_line(struct Bitmap* bitmap); 58 | struct TestData* test_shapes_line2(struct Bitmap* bitmap); 59 | struct TestData* test_shapes_oval1(struct Bitmap* bitmap); 60 | struct TestData* test_shapes_oval2(struct Bitmap* bitmap); 61 | struct TestData* test_shapes_oval3(struct Bitmap* bitmap); 62 | struct TestData* test_shapes_rect1(struct Bitmap* bitmap); 63 | struct TestData* test_shapes_rect2(struct Bitmap* bitmap); 64 | struct TestData* test_shapes_rect3(struct Bitmap* bitmap); 65 | struct TestData* test_shapes_rect4(struct Bitmap* bitmap); 66 | struct TestData* test_shapes_rect5(struct Bitmap* bitmap); 67 | struct TestData* test_shapes_rect6(struct Bitmap* bitmap); 68 | struct TestData* test_shapes_rect7(struct Bitmap* bitmap); 69 | struct TestData* test_shapes_vline(struct Bitmap* bitmap); 70 | struct TestData* test_text_char1(struct Bitmap* bitmap); 71 | struct TestData* test_text_char2(struct Bitmap* bitmap); 72 | struct TestData* test_text_char3(struct Bitmap* bitmap); 73 | struct TestData* test_text_char4(struct Bitmap* bitmap); 74 | struct TestData* test_text_char5(struct Bitmap* bitmap); 75 | struct TestData* test_text_char6(struct Bitmap* bitmap); 76 | struct TestData* test_text_char7(struct Bitmap* bitmap); 77 | struct TestData* test_text_char8(struct Bitmap* bitmap); 78 | struct TestData* test_text_char9(struct Bitmap* bitmap); 79 | struct TestData* test_text_chr10(struct Bitmap* bitmap); 80 | struct TestData* test_text_chr11(struct Bitmap* bitmap); 81 | struct TestData* test_text_mtric(struct Bitmap* bitmap); 82 | struct TestData* test_text_prtf1(struct Bitmap* bitmap); 83 | struct TestData* test_text_slen1(struct Bitmap* bitmap); 84 | struct TestData* test_text_str1(struct Bitmap* bitmap); 85 | 86 | struct TestData* (*tests[])(struct Bitmap*) = { 87 | test_bitmap_apst1, 88 | test_bitmap_apst2, 89 | test_bitmap_blit0, 90 | test_bitmap_blit1, 91 | test_bitmap_clr0, 92 | test_bitmap_clr1, 93 | test_bitmap_copy1, 94 | test_bitmap_copy2, 95 | test_bitmap_copy3, 96 | test_bitmap_cpyr1, 97 | test_bitmap_cpyr2, 98 | test_bitmap_gtst1, 99 | test_bitmap_gtst2, 100 | test_bitmap_pnt, 101 | test_image_imgba, 102 | test_image_imgx1, 103 | test_image_imgx2, 104 | test_image_imgx3, 105 | test_image_imgx4, 106 | test_image_imgx5, 107 | test_image_imgx6, 108 | test_image_imgx7, 109 | test_image_imgy1, 110 | test_image_imgy2, 111 | test_image_imgy3, 112 | test_image_imgy4, 113 | test_image_imgy5, 114 | test_image_imgy6, 115 | test_image_imgy7, 116 | test_image_tile0, 117 | test_image_tile1, 118 | test_shapes_circ1, 119 | test_shapes_circ2, 120 | test_shapes_cons1, 121 | test_shapes_cons2, 122 | test_shapes_cons3, 123 | test_shapes_cons4, 124 | test_shapes_fcir1, 125 | test_shapes_fcir2, 126 | test_shapes_fill1, 127 | test_shapes_fill2, 128 | test_shapes_fill3, 129 | test_shapes_fovl1, 130 | test_shapes_fovl2, 131 | test_shapes_fovl3, 132 | test_shapes_frct1, 133 | test_shapes_frct2, 134 | test_shapes_frct3, 135 | test_shapes_frct4, 136 | test_shapes_frct5, 137 | test_shapes_frct6, 138 | test_shapes_frct7, 139 | test_shapes_hline, 140 | test_shapes_line, 141 | test_shapes_line2, 142 | test_shapes_oval1, 143 | test_shapes_oval2, 144 | test_shapes_oval3, 145 | test_shapes_rect1, 146 | test_shapes_rect2, 147 | test_shapes_rect3, 148 | test_shapes_rect4, 149 | test_shapes_rect5, 150 | test_shapes_rect6, 151 | test_shapes_rect7, 152 | test_shapes_vline, 153 | test_text_char1, 154 | test_text_char2, 155 | test_text_char3, 156 | test_text_char4, 157 | test_text_char5, 158 | test_text_char6, 159 | test_text_char7, 160 | test_text_char8, 161 | test_text_char9, 162 | test_text_chr10, 163 | test_text_chr11, 164 | test_text_mtric, 165 | test_text_prtf1, 166 | test_text_slen1, 167 | test_text_str1, 168 | }; 169 | 170 | const char* test_names[] = { 171 | "apst1", 172 | "apst2", 173 | "blit0", 174 | "blit1", 175 | "clr0", 176 | "clr1", 177 | "copy1", 178 | "copy2", 179 | "copy3", 180 | "cpyr1", 181 | "cpyr2", 182 | "gtst1", 183 | "gtst2", 184 | "pnt", 185 | "imgba", 186 | "imgx1", 187 | "imgx2", 188 | "imgx3", 189 | "imgx4", 190 | "imgx5", 191 | "imgx6", 192 | "imgx7", 193 | "imgy1", 194 | "imgy2", 195 | "imgy3", 196 | "imgy4", 197 | "imgy5", 198 | "imgy6", 199 | "imgy7", 200 | "tile0", 201 | "tile1", 202 | "circ1", 203 | "circ2", 204 | "cons1", 205 | "cons2", 206 | "cons3", 207 | "cons4", 208 | "fcir1", 209 | "fcir2", 210 | "fill1", 211 | "fill2", 212 | "fill3", 213 | "fovl1", 214 | "fovl2", 215 | "fovl3", 216 | "frct1", 217 | "frct2", 218 | "frct3", 219 | "frct4", 220 | "frct5", 221 | "frct6", 222 | "frct7", 223 | "hline", 224 | "line", 225 | "line2", 226 | "oval1", 227 | "oval2", 228 | "oval3", 229 | "rect1", 230 | "rect2", 231 | "rect3", 232 | "rect4", 233 | "rect5", 234 | "rect6", 235 | "rect7", 236 | "vline", 237 | "char1", 238 | "char2", 239 | "char3", 240 | "char4", 241 | "char5", 242 | "char6", 243 | "char7", 244 | "char8", 245 | "char9", 246 | "chr10", 247 | "chr11", 248 | "mtric", 249 | "prtf1", 250 | "slen1", 251 | "str1", 252 | }; 253 | -------------------------------------------------------------------------------- /examples/midlevel/doublebuffer/ball.c: -------------------------------------------------------------------------------- 1 | #include "ball.h" 2 | #include "common.h" 3 | #include 4 | #include 5 | #include "hardware/structs/rosc.h" 6 | #include 7 | #include 8 | #include 9 | 10 | // Note: This file has nothing all to do with double buffering. If we 11 | // were not double buffering, the code in this file would be exactly 12 | // the same. If you are trying to understand the double buffering API, 13 | // see main.c 14 | 15 | #define BALL_COUNT 50 16 | 17 | // If xv and yv were pixel integers, we would have some limitations on 18 | // possible ball angles. Thus we shift everything up by 4 (<< 4) 19 | // to allow 4 bits of fractional value. The final draw shifts those 20 | // back out to convert to real pixels. 21 | // 22 | // As a reminder that these values are shifted, relevant variables have 23 | // a "4" in them. e.g the "x" coordinate is "x4" which suggests 24 | // "pixel_x_coordinate << 4" 25 | 26 | static const int16_t radius4 = RADIUS << 4; 27 | static const int16_t diameter4 = (RADIUS * 2) << 4; 28 | static const int32_t diameter4sq = diameter4 * diameter4; 29 | static const int16_t max4x = (WIDTH - RADIUS) << 4; 30 | static const int16_t max4y = (HEIGHT - RADIUS - 18) << 4; 31 | static struct BitmapText text; 32 | float energy_gain = 1.00f; // used to add energyor remove energy from the system 33 | 34 | // some good-enough random generation 35 | static int16_t rand16(int16_t min, int16_t max) { 36 | uint16_t v = 0x0000; 37 | for (int i=0; i<16; ++i, v<<=1) { 38 | if (rosc_hw->randombit) { 39 | v |= 0x0001; 40 | } 41 | } 42 | return min + (v % (max - min)); 43 | } 44 | 45 | struct Ball { 46 | int16_t x4; 47 | int16_t y4; 48 | int8_t xv4; 49 | int8_t yv4; 50 | char letter; 51 | }; 52 | 53 | struct Ball ball[BALL_COUNT]; 54 | 55 | static bool balls_are_touching(struct Ball* b1, struct Ball* b2) { 56 | const int32_t dx4 = b1->x4 - b2->x4; 57 | if ((dx4 > diameter4) || (dx4 < -diameter4)) { 58 | // optimization early exit on far-away balls 59 | return false; 60 | } 61 | const int32_t dy4 = b1->y4 - b2->y4; 62 | if ((dy4 > diameter4) || (dy4 < -diameter4)) { 63 | // optimization early exit on far-away balls 64 | return false; 65 | } 66 | // pythagorean theorem 67 | return ((dx4 * dx4) + (dy4 * dy4)) <= diameter4sq; 68 | } 69 | 70 | static void init_a_ball(struct Ball* b) { 71 | b->x4 = rand16(radius4, max4x); 72 | b->y4 = rand16(radius4, max4y); 73 | b->xv4 = rand16(-32, 32); 74 | b->yv4 = rand16(-32, 32); 75 | b->letter = (char)(rand16('A', 'Z' + 1)); 76 | } 77 | 78 | static bool ball_touching_anything(struct Ball* b, int num_added) { 79 | for (int i=0; ix4 += b->xv4; 107 | b->y4 += b->yv4; 108 | // if things are stopping completly, then add some energy in 109 | if ((b->xv4 == 0) && (b->yv4 == 0)) { 110 | a_ball_is_stopped = true; 111 | } 112 | // make sure the velocity is not getting out of control 113 | if (b->xv4 > 32) { 114 | b->xv4 = 32; 115 | a_ball_is_fast = true; 116 | } else if (b->xv4 < -32) { 117 | b->xv4 = -32; 118 | a_ball_is_fast = false; 119 | } 120 | } 121 | if (a_ball_is_stopped && (energy_gain < 1.05)) { 122 | energy_gain += 0.002; 123 | } 124 | if (a_ball_is_fast && (energy_gain > 0.95)) { 125 | energy_gain -= 0.002; 126 | } 127 | } 128 | 129 | static void bounce_a_ball_off_walls(struct Ball* b) { 130 | if ((b->x4 < radius4) || (b->x4 > max4x)) { 131 | // off the edge of the screen in the x dimension 132 | // reverse the direction and back off the previous change 133 | b->xv4 = -b->xv4; 134 | b->x4 += b->xv4; 135 | // sometimes we are still off the screen. This can happen 136 | // when a ball collides with another one and is off the 137 | // screen at the same time. For these cases, we simply pull 138 | // things back onto the screen. 139 | if (b->x4 < radius4) { 140 | b->x4 = radius4; 141 | energy_gain -= 0.01; 142 | } 143 | if (b->x4 > max4x) { 144 | b->x4 = max4x; 145 | energy_gain -= 0.01; 146 | } 147 | } 148 | // now everything that was done for x should be done for y 149 | // maybe this could be factored out but the number of parms 150 | // to inject is high so maybe harder to read than it already is. 151 | if ((b->y4 < radius4) || (b->y4 > max4y)) { 152 | b->yv4 = -b->yv4; 153 | b->y4 += b->yv4; 154 | if (b->yv4 > 32) { 155 | b->yv4 = 32; 156 | } else if (b->yv4 < -32) { 157 | b->yv4 = -32; 158 | } 159 | if (b->y4 < radius4) { 160 | b->y4 = radius4; 161 | } 162 | if (b->y4 > max4y) { 163 | b->y4 = max4y; 164 | } 165 | } 166 | } 167 | 168 | static void bounce_balls_off_walls(void) { 169 | for (int i=0; i < BALL_COUNT; ++i) { 170 | bounce_a_ball_off_walls(ball + i); 171 | } 172 | } 173 | 174 | // This function assumes the the balls are definitely touching 175 | // or overlapping and applies the dense/nasty vector math to 176 | // execute the bounce. 177 | static void bounce_a_ball_off_a_ball(struct Ball* b1, struct Ball* b2) { 178 | // Assume that move_balls() got us in this situation and undo the 179 | // operation 180 | b1->x4 -= b1->xv4; 181 | b1->y4 -= b1->yv4; 182 | b2->x4 -= b2->xv4; 183 | b2->y4 -= b2->yv4; 184 | // There is a slight chance that the balls are still overlapping. This 185 | // can happen when multiple balls collide at the same time or if a wall 186 | // is involved. This logic will allow the situation to eventually 187 | // resolve itself, although it may not look pretty. 188 | if (balls_are_touching(b1, b2)) { 189 | b2->x4 -= b2->xv4; 190 | b2->y4 -= b2->yv4; 191 | } 192 | // may as well use (slower) floats here since a bunch of pruning was already 193 | // done to get to this point 194 | const float b1xv = b1->xv4; 195 | const float b1yv = b1->yv4; 196 | const float b2xv = b2->xv4; 197 | const float b2yv = b2->yv4; 198 | 199 | const float dx = b2->x4 - b1->x4; 200 | const float dy = b2->y4 - b1->y4; 201 | const float length = sqrtf(dx*dx + dy*dy); 202 | // normalize the vectors 203 | const float nx = dx / length; 204 | const float ny = dy / length; 205 | // normalize the velocities 206 | const float v1mag = nx * b1xv + ny * b1yv; 207 | const float v1nx = nx * v1mag; 208 | const float v1ny = ny * v1mag; 209 | 210 | const float v2mag = nx * b2xv + ny * b2yv; 211 | const float v2nx = nx * v2mag; 212 | const float v2ny = ny * v2mag; 213 | // Adjust the velocities 214 | b1->xv4 = (int16_t)((b1xv + v2nx - v1nx) * energy_gain); 215 | b1->yv4 = (int16_t)((b1yv + v2ny - v1ny) * energy_gain); 216 | b2->xv4 = (int16_t)((b2xv + v1nx - v2nx) * energy_gain); 217 | b2->yv4 = (int16_t)((b2yv + v1ny - v2ny) * energy_gain); 218 | } 219 | 220 | static void maybe_bounce_a_ball_off_a_ball(struct Ball* b1, struct Ball* b2) { 221 | if (balls_are_touching(b1, b2)) { 222 | bounce_a_ball_off_a_ball(b1, b2); 223 | } 224 | } 225 | 226 | static void bounce_balls_off_balls(void) { 227 | // Yeah, this could be optimized but the complexity increase might not 228 | // be worth it for a "demo" 229 | for (int j=1; j < BALL_COUNT; ++j) { 230 | for (int i=0; ix4 >> 4; 240 | const uint16_t y = b->y4 >> 4; 241 | bitmap_filled_circle(text.bitmap, x, y, RADIUS); 242 | text.bitmap->mode = BITMAP_WHITE; 243 | text.x = x - 5; 244 | text.y = y - 6; 245 | text_char(&text, b->letter); 246 | text.bitmap->mode = BITMAP_BLACK; 247 | } 248 | move_balls(); 249 | bounce_balls_off_balls(); 250 | bounce_balls_off_walls(); 251 | } 252 | -------------------------------------------------------------------------------- /tools/make_images.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Turns a description .yaml into an image outfile file (c source) 3 | 4 | Encoding is for potential efficiencies on a Sharp Memory Display which 5 | has pixels laid out like this 6 | 7 | 01234567|01234567|... 8 | 01234567|01234567|... 9 | 10 | To make RLE more effective, we use vertical column stripes 8 pixels wide. 11 | It looks like this 12 | 13 | 0|3|6 14 | 1|4|7 15 | 2|5|8 16 | 17 | The example above would represent a 24x3 image. 18 | 19 | If an imagewidth is not a multiple of 8 (say it's 9), then a 20 | full extra byte is used to hold the extra. So a 9-pixel-wide image 21 | would be represented with 2 horizontal bytes, enough for 16 pixels. 22 | 23 | This may seem wasteful, but the RLE can be a big help. For example 24 | the 9 pixel wide image can only be either 0x00 or 0x80 in the second byte 25 | which leads to favorible compression odds. 26 | """ 27 | 28 | from typing import Any, Dict, IO, List, Tuple 29 | 30 | from dataclasses import dataclass 31 | import os 32 | import pathlib 33 | import sys 34 | import yaml 35 | 36 | from PIL import Image 37 | 38 | from lib import rle, codegen 39 | 40 | class Error(Exception): 41 | pass 42 | 43 | class UnknownKeyError(Error): 44 | pass 45 | 46 | 47 | class ConfigSection: 48 | """Used to document and present keys""" 49 | 50 | def __init__(self, section_data: Dict[str, Any]) -> None: 51 | self.config_keys = ( 52 | ('path', 'Path to the image'), 53 | ('name', 'The #define name. Default to a names that is based on the file name.'), 54 | ('invert', 'If true, then white pixels become the 1\'s in the image'), 55 | ('tile_x', 'If > 0, break the image into tiles.'), 56 | ('tile_y', 'If > 0, break the image into tiles.'), 57 | ) 58 | 59 | known_keys = set(c[0] for c in self.config_keys) 60 | unknown_keys = set(section_data).difference(known_keys) 61 | if unknown_keys: 62 | raise UnknownKeyError( 63 | 'Unknown keys in config: %s. Valid keys include: %s' % ( 64 | sorted(unknown_keys), sorted(known_keys))) 65 | 66 | self.data = section_data 67 | 68 | def get(self, key: str, default: Any = None) -> Any: 69 | if default is None: 70 | return self.data[key] 71 | return self.data.get(key, default) 72 | 73 | @dataclass 74 | class ImageProps: 75 | name: str 76 | invert: bool 77 | image: Image.Image 78 | rle: List[int] 79 | 80 | 81 | def add_image_as_tiles( 82 | image_list: List[ImageProps], 83 | defines: Dict[str, Any], 84 | img: Image.Image, 85 | name: str, 86 | invert: bool, 87 | tile_x: int, 88 | tile_y: int) -> None: 89 | if (tile_x == 0) or (tile_x > img.width): 90 | tile_x = img.width 91 | if (tile_y == 0) or (tile_y > img.height): 92 | tile_y = img.height 93 | 94 | # need to round up the values 95 | columns = int((img.width + tile_x - 1) / tile_x) 96 | rows = int((img.height + tile_y - 1) / tile_y) 97 | 98 | defines[f'{name}_COLUMNS'] = columns 99 | defines[f'{name}_ROWS'] = rows 100 | 101 | for row in range(0, rows): 102 | for column in range(0, columns): 103 | left = column * tile_x 104 | right = min(left + tile_x, img.width) 105 | top = row * tile_y 106 | bottom = min(top + tile_y, img.height) 107 | image_list.append(ImageProps( 108 | name=f'{name}_{column}_{row}', 109 | invert=invert, 110 | image=img.crop([left, top, right, bottom]), 111 | rle=[] 112 | )) 113 | 114 | 115 | def process_section( 116 | image_list: List[ImageProps], 117 | defines: Dict[str, Any], 118 | section: ConfigSection) -> None: 119 | name = section.get('Name', '') 120 | path = section.get('path') 121 | if not name: 122 | name = os.path.splitext(os.path.split(path)[1])[0].upper() 123 | name = name + "_IMG" 124 | first_char = name[0] 125 | if first_char < 'A' or first_char > 'Z': 126 | name = 'I' + name 127 | 128 | with Image.open(path) as img: 129 | tile_x = section.get('tile_x', 0) 130 | tile_y = section.get('tile_y', 0) 131 | 132 | if not tile_x and not tile_y: 133 | image_list.append(ImageProps( 134 | name=name, 135 | invert=section.get('invert', False), 136 | image=img.copy(), 137 | rle=[] 138 | )) 139 | else: 140 | add_image_as_tiles( 141 | image_list, 142 | defines, 143 | img, 144 | name, 145 | section.get('invert', False), 146 | tile_x, 147 | tile_y) 148 | 149 | 150 | def generate_offsets(fout: IO, image_list: List[ImageProps]) -> None: 151 | fout.write(' // Image Offsets\n') 152 | # 2 bytes width, 2 bytes height, 4 bytes offset 153 | offset = len(image_list) * 8 154 | index = 0; 155 | for i in image_list: 156 | line = ''.join(( 157 | ' ', 158 | '%s, ' % u16_to_hexstr(i.image.width), 159 | '%s, ' % u16_to_hexstr(i.image.height), 160 | '%s, ' % u32_to_hexstr(offset), 161 | f'// {index}: {i.name} w={i.image.width}, h={i.image.height}, off={offset}\n' 162 | )) 163 | fout.write(line) 164 | offset += len(i.rle) 165 | index += 1 166 | 167 | 168 | def u16_to_hexstr(val: int) -> str: 169 | return "0x%02X, 0x%02X" % ( 170 | val & 0xFF, 171 | (val >> 8) & 0xFF, 172 | ) 173 | 174 | def u32_to_hexstr(val: int) -> str: 175 | return "0x%02X, 0x%02X, 0x%02X, 0x%02X" % ( 176 | val & 0xFF, 177 | (val >> 8) & 0xFF, 178 | (val >> 16) & 0xFF, 179 | (val >> 24) & 0xFF 180 | ) 181 | 182 | 183 | def scale_image_if_needed( 184 | img: Image.Image, max_image_comment_size: int) -> Image.Image: 185 | if (img.width <= max_image_comment_size and 186 | img.height <= max_image_comment_size): 187 | return img 188 | 189 | if img.width >= img.height: 190 | # make the width the larger dimension 191 | new_width = 80 192 | new_height = img.height * 80 // img.width 193 | else: 194 | new_width = img.width * 80 // img.height 195 | new_height = 80 196 | if new_width < 1: 197 | new_width = 1 198 | if new_height < 1: 199 | new_height = 1 200 | 201 | return img.resize((new_width, new_height)) 202 | 203 | 204 | def generate_image_comment( 205 | index: int, 206 | i: ImageProps, 207 | max_image_comment_size: int, 208 | fout: IO 209 | ) -> None: 210 | """Generates a comment block for an image.""" 211 | fout.write(f' // Image {index}: {i.name} w={i.image.width} h={i.image.height}\n') 212 | img = scale_image_if_needed(i.image, max_image_comment_size) 213 | for y in range(img.height): 214 | fout.write(' // ') 215 | for x in range(img.width): 216 | fout.write('#' if bool(img.getpixel((x,y)) == i.invert) else '-') 217 | fout.write('\n') 218 | 219 | 220 | def dump_sources( 221 | path: str, 222 | image_list: List[ImageProps], 223 | defines: Dict[str, Any], 224 | max_image_comment_size: int) -> None: 225 | out_path = pathlib.Path(path).with_suffix('.c') 226 | var_name = out_path.with_suffix('').name.replace('.', '_').replace('-', '_') 227 | define_list = [] 228 | if defines: 229 | for k, v in sorted(defines.items()): 230 | define_list.append((k, v)) 231 | define_list.extend((i.name, idx) for idx, i in enumerate(image_list)) 232 | codegen.dump_c_header(path, var_name, define_list) 233 | 234 | with out_path.open('w', encoding='utf8') as fout: 235 | fout.write('\n'.join(( 236 | '// Generated image data for %s' % var_name, 237 | '', 238 | '#include ', 239 | '#include ', 240 | '', 241 | '// Note: all values are little endian per r2040 spec', 242 | '', 243 | 'const uint8_t %s[] __in_flash() = {' % var_name, 244 | ' 0x53, 0x48, 0x49, 0x31, // id: SHI1', 245 | ' %s, // image count (%d)' % (u32_to_hexstr(len(image_list)), len(image_list)), 246 | '' 247 | ))) 248 | 249 | for i in image_list: 250 | i.rle = rle.create_rle_data(i.image.height, i.image, i.invert) 251 | generate_offsets(fout, image_list) 252 | 253 | fout.write(' // Image data\n') 254 | index = 0 255 | for i in image_list: 256 | generate_image_comment(index, i, max_image_comment_size, fout) 257 | codegen.generate_character_data(i.rle, fout) 258 | 259 | fout.write('};\n') 260 | 261 | print('Wrote %s' % out_path) 262 | 263 | 264 | def main(): 265 | """Entry point.""" 266 | if len(sys.argv) != 2: 267 | sys.exit('Usage: make_images.py ') 268 | 269 | path = sys.argv[1] 270 | with open(path, encoding='utf8') as f: 271 | cfg = yaml.safe_load(f) 272 | 273 | max_image_comment_size = cfg.get('max_image_comment_size', 80) 274 | 275 | # creates a list of (name, image) tuples 276 | config_sections = (ConfigSection(section) for section in cfg['images']) 277 | image_list = [] 278 | defines = {} 279 | for section in config_sections: 280 | process_section(image_list, defines, section) 281 | dump_sources(path, image_list, defines, max_image_comment_size) 282 | 283 | 284 | if __name__ == '__main__': 285 | main() 286 | -------------------------------------------------------------------------------- /examples/midlevel/bitmapshapes/main.c: -------------------------------------------------------------------------------- 1 | #include "pico/stdlib.h" 2 | #include "hardware/structs/rosc.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define WIDTH 400 11 | #define HEIGHT 240 12 | #define SLEEP_MS 17 13 | 14 | uint8_t disp_buffer[BITMAP_SIZE(WIDTH, HEIGHT)]; 15 | uint8_t disp_buffer2[BITMAP_SIZE(WIDTH, HEIGHT)]; 16 | struct SharpDisp display; 17 | struct DoubleBuffer dbl_buff; 18 | struct BitmapText text; 19 | 20 | #define FILL_BUFF_SIZE 1024 21 | uint32_t fill_buff[FILL_BUFF_SIZE]; 22 | 23 | #define FRAMES 400 24 | 25 | // some good-enough random generation 26 | static int16_t rand16(int16_t min, int16_t max) { 27 | uint16_t v = 0x0000; 28 | for (int i=0; i<16; ++i, v<<=1) { 29 | if (rosc_hw->randombit) { 30 | v |= 0x0001; 31 | } 32 | } 33 | return min + (v % (max - min)); 34 | } 35 | 36 | struct Demo { 37 | uint8_t clear_byte; 38 | uint8_t mode; 39 | void (*fn)(void); 40 | }; 41 | 42 | static void title(const char* str) { 43 | const uint8_t shadow = 2; 44 | const uint8_t xpad = 40; 45 | const uint8_t ypad = 40; 46 | const uint16_t x = WIDTH - text_str_width(&text, str) - xpad; 47 | const uint16_t y = HEIGHT - text_height(&text) - ypad; 48 | const uint8_t old_mode = dbl_buff.bitmap.mode; 49 | 50 | text.x = x + shadow; 51 | text.y = y + shadow; 52 | dbl_buff.bitmap.mode = dbl_buff.bitmap.clear_byte ? BITMAP_WHITE : BITMAP_BLACK; 53 | text_str(&text, str); 54 | text.x = x; 55 | text.y = y; 56 | dbl_buff.bitmap.mode = dbl_buff.bitmap.clear_byte ? BITMAP_BLACK : BITMAP_WHITE; 57 | text_str(&text, str); 58 | dbl_buff.bitmap.mode = old_mode; 59 | } 60 | 61 | void points(void) { 62 | int8_t j_dir = 1; 63 | uint8_t j = 10; 64 | uint16_t i=0; 65 | for (; i (offset * 2); offset += 5) { 201 | if (filled) { 202 | bitmap_filled_rect( 203 | &dbl_buff.bitmap, 204 | j + offset, 205 | j + offset, 206 | WIDTH - offset * 2, 207 | HEIGHT - offset * 2); 208 | } else { 209 | bitmap_rect( 210 | &dbl_buff.bitmap, 211 | j + offset, 212 | j + offset, 213 | WIDTH - offset * 2, 214 | HEIGHT - offset * 2); 215 | } 216 | } 217 | 218 | j+=j_dir; 219 | if (i == (FRAMES / 2)) { 220 | j_dir = -j_dir; 221 | j+=j_dir; 222 | } 223 | 224 | title(titlestr); 225 | doublebuffer_swap(&dbl_buff); 226 | } 227 | } 228 | 229 | void rect(void) { 230 | rect_common(false, "Rectangles"); 231 | } 232 | 233 | void filled_rect(void) { 234 | rect_common(true, "Filled Rectangles"); 235 | } 236 | 237 | void oval_common(bool filled, const char* titlestr) { 238 | uint16_t i=0; 239 | 240 | for (; i < FRAMES; ++i) { 241 | bitmap_clear(&dbl_buff.bitmap); 242 | 243 | const uint8_t f = i & 0x3f; 244 | for (int16_t y=-10; y < (HEIGHT + 128); y += 128) { 245 | for (int16_t x=-25; x < (WIDTH + 128); x += 128) { 246 | if (filled) { 247 | bitmap_filled_oval(&dbl_buff.bitmap, x, y, f, 0x3f - f); 248 | } else { 249 | bitmap_oval(&dbl_buff.bitmap, x, y, f, 0x3f - f); 250 | } 251 | } 252 | } 253 | 254 | title(titlestr); 255 | doublebuffer_swap(&dbl_buff); 256 | } 257 | } 258 | 259 | void oval(void) { 260 | oval_common(false, "Ovals"); 261 | } 262 | 263 | void filled_oval(void) { 264 | oval_common(true, "Filled Ovals"); 265 | } 266 | 267 | void map_point(uint16_t p, uint16_t* x, uint16_t* y) { 268 | const uint16_t full = WIDTH * 2 + HEIGHT * 2; 269 | // normalize to the same screen 270 | while (p >= full) { 271 | p -= full; 272 | } 273 | 274 | if (p < WIDTH) { 275 | // top 276 | *y = 0; 277 | *x = p; 278 | return; 279 | } 280 | 281 | if (p < (WIDTH + HEIGHT)) { 282 | // right 283 | *x = WIDTH - 1; 284 | *y = p - WIDTH; 285 | return; 286 | } 287 | 288 | if (p < (WIDTH * 2 + HEIGHT)) { 289 | // bottom 290 | *x = WIDTH - (p - WIDTH - HEIGHT); 291 | *y = HEIGHT - 1; 292 | return; 293 | } 294 | 295 | // left 296 | *x = 0; 297 | *y = HEIGHT - (p - WIDTH * 2 - HEIGHT); 298 | } 299 | 300 | void flood_fill(void) { 301 | uint16_t i=0; 302 | 303 | for (; i < FRAMES; ++i) { 304 | bitmap_clear(&dbl_buff.bitmap); 305 | 306 | // Three triangular points that trace around the screen 307 | uint16_t p0 = i; 308 | uint16_t p1 = i * 3 + WIDTH + 3; 309 | uint16_t p2 = i * 4 + WIDTH + HEIGHT + WIDTH / 2 + 7; 310 | 311 | uint16_t x0; 312 | uint16_t y0; 313 | uint16_t x1; 314 | uint16_t y1; 315 | uint16_t x2; 316 | uint16_t y2; 317 | map_point(p0, &x0, &y0); 318 | map_point(p1, &x1, &y1); 319 | map_point(p2, &x2, &y2); 320 | bitmap_line(&dbl_buff.bitmap, x0, y0, x1, y1); 321 | bitmap_line(&dbl_buff.bitmap, x1, y1, x2, y2); 322 | bitmap_line(&dbl_buff.bitmap, x2, y2, x0, y0); 323 | 324 | // find the center of the triangle 325 | const uint16_t x01 = (x0 + x1) >> 1; 326 | const uint16_t y01 = (y0 + y1) >> 1; 327 | const uint16_t x02 = (x0 + x2) >> 1; 328 | const uint16_t y02 = (y0 + y2) >> 1; 329 | const uint16_t cx = (x01 + x02) >> 1; 330 | const uint16_t cy = (y01 + y02) >> 1; 331 | 332 | bitmap_flood_fill(&dbl_buff.bitmap, cx, cy, fill_buff, FILL_BUFF_SIZE); 333 | 334 | title("Flood Fill"); 335 | doublebuffer_swap(&dbl_buff); 336 | } 337 | } 338 | 339 | struct Demo demos[] = { 340 | {0x00, BITMAP_WHITE, points}, 341 | {0xFF, BITMAP_BLACK, points}, 342 | {0x00, BITMAP_WHITE, lines}, 343 | {0xFF, BITMAP_BLACK, lines}, 344 | {0x00, BITMAP_WHITE, lines2}, 345 | {0xFF, BITMAP_BLACK, lines2}, 346 | {0x00, BITMAP_WHITE, lines3}, 347 | {0xFF, BITMAP_BLACK, lines3}, 348 | {0x00, BITMAP_WHITE, text_array}, 349 | {0xFF, BITMAP_BLACK, text_array}, 350 | {0x00, BITMAP_WHITE, rect}, 351 | {0xFF, BITMAP_BLACK, rect}, 352 | {0x00, BITMAP_INVERSE, filled_rect}, 353 | {0xFF, BITMAP_INVERSE, filled_rect}, 354 | {0x00, BITMAP_WHITE, oval}, 355 | {0xFF, BITMAP_BLACK, oval}, 356 | {0x00, BITMAP_INVERSE, filled_oval}, 357 | {0xFF, BITMAP_INVERSE, filled_oval}, 358 | {0x00, BITMAP_WHITE, flood_fill}, 359 | }; 360 | 361 | int main(void) { 362 | sleep_ms(100); // allow voltage to stabilize 363 | sharpdisp_init_default(&display, disp_buffer, WIDTH, HEIGHT, 0x00); 364 | doublebuffer_init(&dbl_buff, &display, disp_buffer2, 16); 365 | text_init(&text, liberation_sans_24, &dbl_buff.bitmap); 366 | while (1) { 367 | int i=0; 368 | for (; i < (sizeof(demos) / sizeof(demos[0])); ++i) { 369 | struct Demo* demo = demos + i; 370 | dbl_buff.bitmap.clear_byte = demo->clear_byte; 371 | dbl_buff.bitmap.mode = demo->mode; 372 | demo->fn(); 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /tools/make_var_font.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Turns a description .yaml into a output file (debug or c source). 3 | 4 | Encoding is for potential efficiencies on a Sharp Memory Display which 5 | has pixels laid out like this 6 | 7 | 01234567|01234567|... 8 | 01234567|01234567|... 9 | 10 | To make RLE more effective, we use vertical column stripes 8 pixels wide. 11 | It looks like this 12 | 13 | 0|3|6 14 | 1|4|7 15 | 2|5|8 16 | 17 | The example above would represent a 24x3 font (not very realistic but it's 18 | an example) Each number is a 8x1 pixel slice and the data file 19 | contains the bytes in the numbers as-shown. 20 | 21 | If a character width is not a multiple of 8 (say it's 9), then a 22 | full extra byte is used to hold the extra. So a 9-picel-wide font 23 | would be represented with 2 horizontal bytes, enough for 16 pixels. 24 | 25 | This may seem wasteful, but the RLE can be a big help. For example 26 | the 9 pixel wide font can only be either 0x00 or 0x80 in the second byte 27 | which leads to favorible compression odds. 28 | """ 29 | 30 | from typing import Any, Dict, List, IO, Optional, Set 31 | 32 | import pathlib 33 | import sys 34 | from PIL import Image, ImageDraw, ImageFont 35 | import yaml 36 | from lib import rle, codegen 37 | 38 | class Error(Exception): 39 | pass 40 | 41 | class DuplicateCharacterError(Error): 42 | pass 43 | 44 | class InvalidOutputTypeError(Error): 45 | pass 46 | 47 | class InvalidSectionTypeError(Error): 48 | pass 49 | 50 | class NotAMultipleError(Error): 51 | pass 52 | 53 | class UnknownKeyError(Error): 54 | pass 55 | 56 | 57 | class ConfigSection: 58 | """Used to document and present keys""" 59 | 60 | def __init__(self, section_data: Dict[str, Any]) -> None: 61 | self.config_keys = ( 62 | ('chars', 'Characters to include. If omitted, characters 32-127 will be used'), 63 | ('col_width', 'Fix the column width in pixels. If omitted, the characters reported column with will be used'), 64 | ('first_char', 'Used with the image_grid type. Defines the number for the first character to use.'), 65 | ('font_size', 'The font size to use'), 66 | ('left_trim', 'Removes x pixels from the left side of each character. Default is 0'), 67 | ('path', 'Path to the font / image grid'), 68 | ('right_trim', 'Removes x pixels from the right side of each character. Default is 0'), 69 | ('type', 'Choose between ttf and image types.'), 70 | ('x_offset', 'Change the base coordinate of where the character is drawn. 0 is the default.'), 71 | ('x_pad', 'Change the padding to put around each character (both sides). Default is 1'), 72 | ('x_scale', 'Can be used to scale a character up or down. Normally you would change the font size if possible.'), 73 | ('y_offset', 'Change the base coordinate of where the character is drawn. 0 is the default.'), 74 | ) 75 | 76 | known_keys = set(c[0] for c in self.config_keys) 77 | unknown_keys = set(section_data).difference(known_keys) 78 | if unknown_keys: 79 | raise UnknownKeyError( 80 | 'Unknown keys in config: %s. Valid keys include: %s' % ( 81 | sorted(unknown_keys), sorted(known_keys))) 82 | 83 | self.data = section_data 84 | 85 | def get(self, key: str, default: Any = None) -> Any: 86 | if default is None: 87 | return self.data[key] 88 | return self.data.get(key, default) 89 | 90 | 91 | def make_chars(section: ConfigSection) -> Set[str]: 92 | return set(section.get('chars', ('%c' % x for x in range(32, 127)))) 93 | 94 | def process_ttf_section( 95 | char_to_img: Dict[str, Image.Image], 96 | height: int, 97 | section: ConfigSection) -> None: 98 | """Processes a yaml sectin of type ttf.""" 99 | width = section.get('col_width', 0) 100 | x_offset = section.get('x_offset', 0) 101 | y_offset = section.get('y_offset', 0) 102 | x_pad = section.get('x_pad', 1) 103 | chars = make_chars(section) 104 | fnt = ImageFont.truetype(section.get('path'), int(section.get('font_size'))) 105 | 106 | def make_char(c): 107 | if width == 0: 108 | cwidth = int(fnt.getsize(c)[0] + 1) + x_pad * 2 109 | else: 110 | cwidth = width 111 | img = Image.new("1", (cwidth, height)) 112 | d = ImageDraw.Draw(img) 113 | d.text((x_offset + x_pad, y_offset), c, font=fnt, fill=1) 114 | if section.get('x_scale', 0): 115 | # post scale the image 116 | new_width = int(img.width * section.get('x_scale')) 117 | img = img.resize((new_width, img.height)) 118 | 119 | if section.get('right_trim', 0): 120 | # trim a litle off the right 121 | new_right = img.width - section.get('right_trim') 122 | img = img.crop((0, 0, new_right, img.height)) 123 | 124 | if section.get('left_trim', 0): 125 | # trim a litle off the right 126 | img = img.crop((section.get('left_trim'), 0, img.width, img.height)) 127 | 128 | return img 129 | 130 | for c in chars: 131 | if c in char_to_img: 132 | raise DuplicateCharacterError('Duplicate character: %s' % c) 133 | char_to_img[c] = make_char(c) 134 | 135 | 136 | def process_image_grid_section( 137 | char_to_img: Dict[str, Image.Image], 138 | height: int, 139 | section: ConfigSection) -> None: 140 | """Processes a yaml sectin of type image_grid.""" 141 | width = section.get('col_width') 142 | 143 | with Image.open(section.get('path')) as img: 144 | if img.height % height: 145 | raise NotAMultipleError( 146 | 'grid height %d is not a multiple of image height %d' % ( 147 | height, img.height)) 148 | if img.width % width: 149 | raise NotAMultipleError( 150 | 'grid width %d is not a multiple of image width %d' % ( 151 | width, img.width)) 152 | 153 | num_columns = img.width // width 154 | c = '%c' % section.get('first_char') 155 | for y in range(img.height // height): 156 | for x in range(num_columns): 157 | if c in char_to_img: 158 | raise DuplicateCharacterError('Duplicate character: %s' % c) 159 | cimg = img.crop(( 160 | x * width, 161 | y * height, 162 | x * width + width, 163 | y * height + height)) 164 | char_to_img[c] = Image.eval(cimg, lambda v: 1-v) 165 | c = '%c' % (ord(c) + 1) 166 | 167 | def process_section( 168 | char_to_img: Dict[str, Image.Image], 169 | height: int, 170 | section: ConfigSection) -> None: 171 | if section.get('type') == 'ttf': 172 | process_ttf_section(char_to_img, height, section) 173 | elif section.get('type') == 'image_grid': 174 | process_image_grid_section(char_to_img, height, section) 175 | else: 176 | raise InvalidSectionTypeError('Invalid section type: %s' % section.get('type')) 177 | 178 | 179 | def debug_dump(char_to_img: Dict[str, Image.Image]) -> None: 180 | def dump_char(c, img): 181 | print() 182 | print('%s:' % c) 183 | for y in range(img.height): 184 | for x in range(img.width): 185 | if img.getpixel((x,y)): 186 | print('*', end='') 187 | else: 188 | print('.', end='') 189 | print() 190 | 191 | for c, img in sorted(char_to_img.items()): 192 | dump_char(c, img) 193 | 194 | def generate_character_comment(c: str, img: Image.Image, fout: IO) -> None: 195 | if ord(c) < 32 or ord(c) > 128: 196 | c_rep = '' 197 | else: 198 | c_rep = ' (%c)' % c 199 | fout.write(' // Character %d (0x%02X)%s\n' % (ord(c), ord(c), c_rep)) 200 | for y in range(img.height): 201 | fout.write(' // ') 202 | for x in range(img.width): 203 | fout.write('#' if img.getpixel((x,y)) else '-') 204 | fout.write('\n') 205 | 206 | special_chars = '\'\\' 207 | 208 | def generate_offsets( 209 | fout: IO, 210 | char_to_img: Dict[str, Image.Image], 211 | char_to_data: Dict[str, List[int]]) -> None: 212 | fout.write(' // Character offsets\n') 213 | # 3 bytes per character plus the end byte 214 | offset = len(char_to_img) * 4 215 | for c, img in sorted(char_to_img.items()): 216 | if ord(c) < 32 or ord(c) > 128 or c in special_chars: 217 | c_rep = '%d' % ord(c) 218 | else: 219 | c_rep = '\'%c\'' % c 220 | fout.write(' %s, %d, 0x%02X, 0x%02X, // off=%d\n' % ( 221 | c_rep, img.width, offset >> 8, offset & 0xFF, offset)) 222 | offset += len(char_to_data[c]) 223 | 224 | 225 | def variable_font_dump( 226 | path: str, 227 | char_to_img: Dict[str, Image.Image]) -> None: 228 | """Dumps .c and .h files.""" 229 | out_path = pathlib.Path(path).with_suffix('.c') 230 | var_name = out_path.with_suffix('').name.replace('.', '_').replace('-', '_') 231 | codegen.dump_c_header(path, var_name) 232 | 233 | height = next(iter(char_to_img.values())).height 234 | 235 | with out_path.open('w', encoding='utf8') as fout: 236 | fout.write('\n'.join(( 237 | '// Generated font data for %s' % var_name, 238 | '', 239 | '#include ', 240 | '#include ', 241 | '', 242 | 'const uint8_t %s[] __in_flash() = {' % var_name, 243 | ' 0x53, 0x48, 0x46, 0x31, // id: SHF1', 244 | ' 0x%02X, // num_chars' % len(char_to_img), 245 | ' 0x%02X, // height' % height, 246 | '', 247 | '', 248 | ))) 249 | 250 | char_to_data = { 251 | c:rle.create_rle_data(height, img) for c, img in char_to_img.items()} 252 | generate_offsets(fout, char_to_img, char_to_data) 253 | 254 | fout.write(' // Character data\n') 255 | for c, img in sorted(char_to_img.items()): 256 | generate_character_comment(c, img, fout) 257 | codegen.generate_character_data(char_to_data[c], fout) 258 | 259 | fout.write('};\n') 260 | 261 | print('Wrote %s' % out_path) 262 | 263 | 264 | def main(): 265 | """Entry point.""" 266 | if len(sys.argv) != 2: 267 | sys.exit('Usage: make_var_font.py ') 268 | 269 | path = sys.argv[1] 270 | with open(path, encoding='utf8') as f: 271 | cfg = yaml.safe_load(f) 272 | 273 | # creates a map of character to image 274 | char_to_img = {} 275 | 276 | height = int(cfg['height']) 277 | for section in cfg['sections']: 278 | process_section(char_to_img, height, ConfigSection(section)) 279 | 280 | if cfg['output_type'] == 'debug': 281 | debug_dump(char_to_img) 282 | elif cfg['output_type'] == 'SharpMemoryFont': 283 | variable_font_dump(path, char_to_img) 284 | else: 285 | raise InvalidOutputTypeError( 286 | 'Invalid output type: %s' % cfg['output_type']) 287 | 288 | 289 | if __name__ == '__main__': 290 | main() 291 | -------------------------------------------------------------------------------- /examples/midlevel/mapscroll/map.h: -------------------------------------------------------------------------------- 1 | #ifndef MAP_H 2 | #define MAP_H 3 | // Generated data for map.yaml 4 | 5 | #define MAP_IMG_COLUMNS 20 6 | #define MAP_IMG_ROWS 14 7 | #define MAP_IMG_0_0 0 8 | #define MAP_IMG_1_0 1 9 | #define MAP_IMG_2_0 2 10 | #define MAP_IMG_3_0 3 11 | #define MAP_IMG_4_0 4 12 | #define MAP_IMG_5_0 5 13 | #define MAP_IMG_6_0 6 14 | #define MAP_IMG_7_0 7 15 | #define MAP_IMG_8_0 8 16 | #define MAP_IMG_9_0 9 17 | #define MAP_IMG_10_0 10 18 | #define MAP_IMG_11_0 11 19 | #define MAP_IMG_12_0 12 20 | #define MAP_IMG_13_0 13 21 | #define MAP_IMG_14_0 14 22 | #define MAP_IMG_15_0 15 23 | #define MAP_IMG_16_0 16 24 | #define MAP_IMG_17_0 17 25 | #define MAP_IMG_18_0 18 26 | #define MAP_IMG_19_0 19 27 | #define MAP_IMG_0_1 20 28 | #define MAP_IMG_1_1 21 29 | #define MAP_IMG_2_1 22 30 | #define MAP_IMG_3_1 23 31 | #define MAP_IMG_4_1 24 32 | #define MAP_IMG_5_1 25 33 | #define MAP_IMG_6_1 26 34 | #define MAP_IMG_7_1 27 35 | #define MAP_IMG_8_1 28 36 | #define MAP_IMG_9_1 29 37 | #define MAP_IMG_10_1 30 38 | #define MAP_IMG_11_1 31 39 | #define MAP_IMG_12_1 32 40 | #define MAP_IMG_13_1 33 41 | #define MAP_IMG_14_1 34 42 | #define MAP_IMG_15_1 35 43 | #define MAP_IMG_16_1 36 44 | #define MAP_IMG_17_1 37 45 | #define MAP_IMG_18_1 38 46 | #define MAP_IMG_19_1 39 47 | #define MAP_IMG_0_2 40 48 | #define MAP_IMG_1_2 41 49 | #define MAP_IMG_2_2 42 50 | #define MAP_IMG_3_2 43 51 | #define MAP_IMG_4_2 44 52 | #define MAP_IMG_5_2 45 53 | #define MAP_IMG_6_2 46 54 | #define MAP_IMG_7_2 47 55 | #define MAP_IMG_8_2 48 56 | #define MAP_IMG_9_2 49 57 | #define MAP_IMG_10_2 50 58 | #define MAP_IMG_11_2 51 59 | #define MAP_IMG_12_2 52 60 | #define MAP_IMG_13_2 53 61 | #define MAP_IMG_14_2 54 62 | #define MAP_IMG_15_2 55 63 | #define MAP_IMG_16_2 56 64 | #define MAP_IMG_17_2 57 65 | #define MAP_IMG_18_2 58 66 | #define MAP_IMG_19_2 59 67 | #define MAP_IMG_0_3 60 68 | #define MAP_IMG_1_3 61 69 | #define MAP_IMG_2_3 62 70 | #define MAP_IMG_3_3 63 71 | #define MAP_IMG_4_3 64 72 | #define MAP_IMG_5_3 65 73 | #define MAP_IMG_6_3 66 74 | #define MAP_IMG_7_3 67 75 | #define MAP_IMG_8_3 68 76 | #define MAP_IMG_9_3 69 77 | #define MAP_IMG_10_3 70 78 | #define MAP_IMG_11_3 71 79 | #define MAP_IMG_12_3 72 80 | #define MAP_IMG_13_3 73 81 | #define MAP_IMG_14_3 74 82 | #define MAP_IMG_15_3 75 83 | #define MAP_IMG_16_3 76 84 | #define MAP_IMG_17_3 77 85 | #define MAP_IMG_18_3 78 86 | #define MAP_IMG_19_3 79 87 | #define MAP_IMG_0_4 80 88 | #define MAP_IMG_1_4 81 89 | #define MAP_IMG_2_4 82 90 | #define MAP_IMG_3_4 83 91 | #define MAP_IMG_4_4 84 92 | #define MAP_IMG_5_4 85 93 | #define MAP_IMG_6_4 86 94 | #define MAP_IMG_7_4 87 95 | #define MAP_IMG_8_4 88 96 | #define MAP_IMG_9_4 89 97 | #define MAP_IMG_10_4 90 98 | #define MAP_IMG_11_4 91 99 | #define MAP_IMG_12_4 92 100 | #define MAP_IMG_13_4 93 101 | #define MAP_IMG_14_4 94 102 | #define MAP_IMG_15_4 95 103 | #define MAP_IMG_16_4 96 104 | #define MAP_IMG_17_4 97 105 | #define MAP_IMG_18_4 98 106 | #define MAP_IMG_19_4 99 107 | #define MAP_IMG_0_5 100 108 | #define MAP_IMG_1_5 101 109 | #define MAP_IMG_2_5 102 110 | #define MAP_IMG_3_5 103 111 | #define MAP_IMG_4_5 104 112 | #define MAP_IMG_5_5 105 113 | #define MAP_IMG_6_5 106 114 | #define MAP_IMG_7_5 107 115 | #define MAP_IMG_8_5 108 116 | #define MAP_IMG_9_5 109 117 | #define MAP_IMG_10_5 110 118 | #define MAP_IMG_11_5 111 119 | #define MAP_IMG_12_5 112 120 | #define MAP_IMG_13_5 113 121 | #define MAP_IMG_14_5 114 122 | #define MAP_IMG_15_5 115 123 | #define MAP_IMG_16_5 116 124 | #define MAP_IMG_17_5 117 125 | #define MAP_IMG_18_5 118 126 | #define MAP_IMG_19_5 119 127 | #define MAP_IMG_0_6 120 128 | #define MAP_IMG_1_6 121 129 | #define MAP_IMG_2_6 122 130 | #define MAP_IMG_3_6 123 131 | #define MAP_IMG_4_6 124 132 | #define MAP_IMG_5_6 125 133 | #define MAP_IMG_6_6 126 134 | #define MAP_IMG_7_6 127 135 | #define MAP_IMG_8_6 128 136 | #define MAP_IMG_9_6 129 137 | #define MAP_IMG_10_6 130 138 | #define MAP_IMG_11_6 131 139 | #define MAP_IMG_12_6 132 140 | #define MAP_IMG_13_6 133 141 | #define MAP_IMG_14_6 134 142 | #define MAP_IMG_15_6 135 143 | #define MAP_IMG_16_6 136 144 | #define MAP_IMG_17_6 137 145 | #define MAP_IMG_18_6 138 146 | #define MAP_IMG_19_6 139 147 | #define MAP_IMG_0_7 140 148 | #define MAP_IMG_1_7 141 149 | #define MAP_IMG_2_7 142 150 | #define MAP_IMG_3_7 143 151 | #define MAP_IMG_4_7 144 152 | #define MAP_IMG_5_7 145 153 | #define MAP_IMG_6_7 146 154 | #define MAP_IMG_7_7 147 155 | #define MAP_IMG_8_7 148 156 | #define MAP_IMG_9_7 149 157 | #define MAP_IMG_10_7 150 158 | #define MAP_IMG_11_7 151 159 | #define MAP_IMG_12_7 152 160 | #define MAP_IMG_13_7 153 161 | #define MAP_IMG_14_7 154 162 | #define MAP_IMG_15_7 155 163 | #define MAP_IMG_16_7 156 164 | #define MAP_IMG_17_7 157 165 | #define MAP_IMG_18_7 158 166 | #define MAP_IMG_19_7 159 167 | #define MAP_IMG_0_8 160 168 | #define MAP_IMG_1_8 161 169 | #define MAP_IMG_2_8 162 170 | #define MAP_IMG_3_8 163 171 | #define MAP_IMG_4_8 164 172 | #define MAP_IMG_5_8 165 173 | #define MAP_IMG_6_8 166 174 | #define MAP_IMG_7_8 167 175 | #define MAP_IMG_8_8 168 176 | #define MAP_IMG_9_8 169 177 | #define MAP_IMG_10_8 170 178 | #define MAP_IMG_11_8 171 179 | #define MAP_IMG_12_8 172 180 | #define MAP_IMG_13_8 173 181 | #define MAP_IMG_14_8 174 182 | #define MAP_IMG_15_8 175 183 | #define MAP_IMG_16_8 176 184 | #define MAP_IMG_17_8 177 185 | #define MAP_IMG_18_8 178 186 | #define MAP_IMG_19_8 179 187 | #define MAP_IMG_0_9 180 188 | #define MAP_IMG_1_9 181 189 | #define MAP_IMG_2_9 182 190 | #define MAP_IMG_3_9 183 191 | #define MAP_IMG_4_9 184 192 | #define MAP_IMG_5_9 185 193 | #define MAP_IMG_6_9 186 194 | #define MAP_IMG_7_9 187 195 | #define MAP_IMG_8_9 188 196 | #define MAP_IMG_9_9 189 197 | #define MAP_IMG_10_9 190 198 | #define MAP_IMG_11_9 191 199 | #define MAP_IMG_12_9 192 200 | #define MAP_IMG_13_9 193 201 | #define MAP_IMG_14_9 194 202 | #define MAP_IMG_15_9 195 203 | #define MAP_IMG_16_9 196 204 | #define MAP_IMG_17_9 197 205 | #define MAP_IMG_18_9 198 206 | #define MAP_IMG_19_9 199 207 | #define MAP_IMG_0_10 200 208 | #define MAP_IMG_1_10 201 209 | #define MAP_IMG_2_10 202 210 | #define MAP_IMG_3_10 203 211 | #define MAP_IMG_4_10 204 212 | #define MAP_IMG_5_10 205 213 | #define MAP_IMG_6_10 206 214 | #define MAP_IMG_7_10 207 215 | #define MAP_IMG_8_10 208 216 | #define MAP_IMG_9_10 209 217 | #define MAP_IMG_10_10 210 218 | #define MAP_IMG_11_10 211 219 | #define MAP_IMG_12_10 212 220 | #define MAP_IMG_13_10 213 221 | #define MAP_IMG_14_10 214 222 | #define MAP_IMG_15_10 215 223 | #define MAP_IMG_16_10 216 224 | #define MAP_IMG_17_10 217 225 | #define MAP_IMG_18_10 218 226 | #define MAP_IMG_19_10 219 227 | #define MAP_IMG_0_11 220 228 | #define MAP_IMG_1_11 221 229 | #define MAP_IMG_2_11 222 230 | #define MAP_IMG_3_11 223 231 | #define MAP_IMG_4_11 224 232 | #define MAP_IMG_5_11 225 233 | #define MAP_IMG_6_11 226 234 | #define MAP_IMG_7_11 227 235 | #define MAP_IMG_8_11 228 236 | #define MAP_IMG_9_11 229 237 | #define MAP_IMG_10_11 230 238 | #define MAP_IMG_11_11 231 239 | #define MAP_IMG_12_11 232 240 | #define MAP_IMG_13_11 233 241 | #define MAP_IMG_14_11 234 242 | #define MAP_IMG_15_11 235 243 | #define MAP_IMG_16_11 236 244 | #define MAP_IMG_17_11 237 245 | #define MAP_IMG_18_11 238 246 | #define MAP_IMG_19_11 239 247 | #define MAP_IMG_0_12 240 248 | #define MAP_IMG_1_12 241 249 | #define MAP_IMG_2_12 242 250 | #define MAP_IMG_3_12 243 251 | #define MAP_IMG_4_12 244 252 | #define MAP_IMG_5_12 245 253 | #define MAP_IMG_6_12 246 254 | #define MAP_IMG_7_12 247 255 | #define MAP_IMG_8_12 248 256 | #define MAP_IMG_9_12 249 257 | #define MAP_IMG_10_12 250 258 | #define MAP_IMG_11_12 251 259 | #define MAP_IMG_12_12 252 260 | #define MAP_IMG_13_12 253 261 | #define MAP_IMG_14_12 254 262 | #define MAP_IMG_15_12 255 263 | #define MAP_IMG_16_12 256 264 | #define MAP_IMG_17_12 257 265 | #define MAP_IMG_18_12 258 266 | #define MAP_IMG_19_12 259 267 | #define MAP_IMG_0_13 260 268 | #define MAP_IMG_1_13 261 269 | #define MAP_IMG_2_13 262 270 | #define MAP_IMG_3_13 263 271 | #define MAP_IMG_4_13 264 272 | #define MAP_IMG_5_13 265 273 | #define MAP_IMG_6_13 266 274 | #define MAP_IMG_7_13 267 275 | #define MAP_IMG_8_13 268 276 | #define MAP_IMG_9_13 269 277 | #define MAP_IMG_10_13 270 278 | #define MAP_IMG_11_13 271 279 | #define MAP_IMG_12_13 272 280 | #define MAP_IMG_13_13 273 281 | #define MAP_IMG_14_13 274 282 | #define MAP_IMG_15_13 275 283 | #define MAP_IMG_16_13 276 284 | #define MAP_IMG_17_13 277 285 | #define MAP_IMG_18_13 278 286 | #define MAP_IMG_19_13 279 287 | 288 | #include 289 | #include 290 | 291 | extern const uint8_t map[] __in_flash(); 292 | 293 | #endif // MAP_H 294 | --------------------------------------------------------------------------------