├── demo.jpg ├── linux ├── bb_spi_lcd.c ├── examples │ ├── simple_text │ │ ├── Makefile │ │ └── main.c │ ├── hello_cpp │ │ ├── Makefile │ │ └── main.cpp │ ├── hatpic │ │ ├── Makefile │ │ └── main.c │ └── gif_player │ │ ├── Makefile │ │ └── main.cpp ├── make_cpp ├── README └── Makefile ├── fontconvert ├── Makefile └── main.c ├── library.properties ├── examples ├── spi_touch_test │ └── spi_touch_test.ino ├── g5_image_test │ ├── g5_image_test.ino │ └── smiley.h ├── spilcd_prop_font │ ├── spilcd_prop_font.ino │ └── FreeSerif12pt7b.h ├── generic_display │ └── generic_display.ino ├── cyd_touch_demo │ └── cyd_touch_demo.ino ├── blur_demo │ └── blur_demo.ino ├── parallel_shield_demo │ └── parallel_shield_demo.ino ├── atoms3_gif │ └── atoms3_gif.ino ├── alpha_blend │ └── alpha_blend.ino ├── callback_demo │ └── callback_demo.ino ├── masked_tint │ └── masked_tint.ino ├── wio_dma_gif_demo │ └── wio_dma_gif_demo.ino ├── shared_spi │ └── shared_spi.ino ├── cyd_gif_demo │ └── cyd_gif_demo.ino ├── UNO_shield_demo │ └── UNO_shield_demo.ino ├── big_framebuffer │ └── big_framebuffer.ino └── spi_lcd_demo │ └── spi_lcd_demo.ino ├── src ├── s3_simd_byteswap.S ├── Group5.cpp ├── s3_simd_alphablend.S ├── s3_simd_alphatrans.S ├── s3_simd_tint.S ├── s3_simd_blur.S ├── s3_simd_tile53.S ├── esp_lcd_jd9165.h ├── Group5.h ├── g5enc.inl ├── g5dec.inl └── linux_io.inl ├── README.md └── LICENSE /demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbank2/bb_spi_lcd/HEAD/demo.jpg -------------------------------------------------------------------------------- /linux/bb_spi_lcd.c: -------------------------------------------------------------------------------- 1 | #include "../src/bb_spi_lcd.cpp" 2 | #include "../src/bb_parallel.cpp" 3 | 4 | -------------------------------------------------------------------------------- /linux/examples/simple_text/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-c -Wall -O2 -D__LINUX__ 2 | LIBS = -lbb_spi_lcd -lgpiod -lm -lpthread 3 | 4 | all: sample 5 | 6 | sample: sample.o 7 | $(CC) sample.o $(LIBS) -o sample 8 | 9 | sample.o: sample.c 10 | $(CC) $(CFLAGS) sample.c 11 | 12 | clean: 13 | rm *.o sample 14 | -------------------------------------------------------------------------------- /linux/examples/hello_cpp/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-c -Wall -O2 -D__LINUX__ 2 | LIBS = -lbb_spi_lcd -lgpiod -lm -lpthread 3 | 4 | all: hello_cpp 5 | 6 | hello_cpp: main.o 7 | $(CC) main.o $(LIBS) -o hello_cpp 8 | 9 | main.o: main.cpp 10 | $(CXX) $(CFLAGS) main.cpp 11 | 12 | clean: 13 | rm *.o hello_cpp 14 | -------------------------------------------------------------------------------- /linux/examples/hatpic/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-c -Wall -O2 -D__LINUX__ 2 | LIBS = -lbb_spi_lcd -larmbianio -lm -lpthread 3 | 4 | all: hatpic 5 | 6 | hatpic: hatpic.o libbb_spi_lcd.a 7 | $(CC) hatpic.o $(LIBS) -o hatpic 8 | 9 | hatpic.o: hatpic.c 10 | $(CC) $(CFLAGS) hatpic.c 11 | 12 | clean: 13 | rm *.o hatpic 14 | -------------------------------------------------------------------------------- /linux/examples/gif_player/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-D__LINUX__ -Wall -O2 -I../../ 2 | LIBS=-lbb_spi_lcd -lAnimatedGIF -lgpiod 3 | 4 | all: gif_player 5 | 6 | gif_player: main.o 7 | $(CXX) main.o $(LIBS) -o gif_player 8 | 9 | main.o: main.cpp 10 | $(CXX) $(CFLAGS) -c main.cpp 11 | 12 | clean: 13 | rm -rf *.o gif_player 14 | -------------------------------------------------------------------------------- /fontconvert/Makefile: -------------------------------------------------------------------------------- 1 | all: fontconvert 2 | 3 | CC = gcc 4 | CFLAGS = -Wall -I/usr/local/include/freetype2 -I/usr/include/freetype2 -I/usr/include -I/opt/homebrew/include/freetype2 5 | LIBS = -L/opt/homebrew/lib -lfreetype 6 | 7 | fontconvert: main.c 8 | $(CC) $(CFLAGS) main.c $(LIBS) -o fontconvert 9 | strip fontconvert 10 | 11 | clean: 12 | rm -f fontconvert 13 | -------------------------------------------------------------------------------- /linux/make_cpp: -------------------------------------------------------------------------------- 1 | CFLAGS=-c -Wall -O2 -D__LINUX__ -I../src 2 | LIBS = -lm -lpthread 3 | 4 | all: libbb_spi_lcd.a 5 | 6 | libbb_spi_lcd.a: bb_spi_lcd.o 7 | ar -rc libbb_spi_lcd.a bb_spi_lcd.o ;\ 8 | sudo cp libbb_spi_lcd.a /usr/local/lib ;\ 9 | sudo cp ../src/bb_spi_lcd.h /usr/local/include 10 | 11 | bb_spi_lcd.o: ../src/bb_spi_lcd.cpp ../src/bb_spi_lcd.h 12 | $(CXX) $(CFLAGS) ../src/bb_spi_lcd.cpp 13 | 14 | clean: 15 | rm *.o libbb_spi_lcd.a 16 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=bb_spi_lcd 2 | version=2.9.7 3 | author=Larry Bank 4 | maintainer=Larry Bank 5 | sentence=Color SPI LCD+OLED library with many unique features. 6 | paragraph=Supports most popular LCD and OLED color display controllers. Includes DMA support, display Windows BMP (and RLE), bitmap rotation, transparent text, translucent 1-bpp masks and optimized primitives. Optional back buffer for fast memory operations. 7 | category=Display 8 | url=https://github.com/bitbank2/bb_spi_lcd 9 | architectures=* 10 | includes=bb_spi_lcd.h 11 | 12 | -------------------------------------------------------------------------------- /linux/README: -------------------------------------------------------------------------------- 1 | bb_spi_lcd Linux instructions 2 | ----------------------------- 3 | For Linux, the bb_spi_lcd library gets built as a static C library. 4 | 5 | Compile the library by running make from the command line. The include file and binary will be copied to the directories where they can be accessed by all. 6 | 7 | Next, compile and run the sample program like so: 8 | >make -f make_sample 9 | >sudo ./sample 10 | 11 | This will initialize the display and draw some blue text. Press ENTER to exit. 12 | 13 | To display images/animations on your TFT hat, build the hatpic sample program: 14 | >make -f make_hatpic 15 | >sudo ./hatpic 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/spi_touch_test/spi_touch_test.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | BB_SPI_LCD lcd; 4 | TOUCHINFO ti; 5 | 6 | void setup() 7 | { 8 | Serial.begin(115200); 9 | Serial.println("Starting..."); 10 | lcd.begin(DISPLAY_CYD_543); 11 | lcd.fillScreen(TFT_BLACK); 12 | lcd.setTextColor(TFT_GREEN, TFT_BLACK); 13 | lcd.setFont(FONT_12x16); 14 | lcd.println("SPI Resistive Touch Test"); 15 | lcd.rtInit(); // the resistive touch configuration is already linked to the display type 16 | } 17 | void loop() 18 | { 19 | while (1) { 20 | if (lcd.rtReadTouch(&ti)) { 21 | lcd.drawPixel(ti.x[0], ti.y[0], TFT_WHITE); 22 | //Serial.printf("Touch x: %d y: %d\n", ti.x[0], ti.y[0]);; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/g5_image_test/g5_image_test.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Group5 compressed image test 3 | // 4 | #include 5 | #include "smiley.h" 6 | BB_SPI_LCD lcd; 7 | 8 | const uint16_t u16Color[8] = {TFT_WHITE, TFT_RED, TFT_GREEN, TFT_BLUE, TFT_GREY, TFT_YELLOW, TFT_CYAN, TFT_MAGENTA}; 9 | void setup() 10 | { 11 | float f; 12 | int i; 13 | 14 | lcd.begin(DISPLAY_CYD_543); 15 | lcd.fillScreen(TFT_BLACK); 16 | // bb_spi_lcd can draw compressed graphics directly onto a display 17 | // without any local framebuffer 18 | for (i=0; i<50; i++) { 19 | f = 0.02f * (float)i; 20 | // Draw the smiley image scaled and in different colors 21 | lcd.drawG5Image(smiley, rand() % lcd.width(), rand() % lcd.height(), TFT_BLACK, u16Color[i & 7], f); 22 | } 23 | } /* setup() */ 24 | 25 | void loop() 26 | { 27 | 28 | } /* loop() */ 29 | -------------------------------------------------------------------------------- /linux/Makefile: -------------------------------------------------------------------------------- 1 | # Allow building as memory-only on MacOS 2 | OPSYS:=$(shell uname) 3 | PROC:=$(shell uname -m) 4 | ifeq ($(OPSYS), Darwin) 5 | CFLAGS=-c -ggdb -Wall -O2 -D__MEM_ONLY__ -D__LINUX__ -I../src 6 | else ifeq ($(PROC), x86_64) 7 | CFLAGS=-c -ggdb -Wall -O2 -D__MEM_ONLY__ -D__LINUX__ -I../src 8 | else 9 | CFLAGS=-c -ggdb -Wall -O2 -D__LINUX__ -I../src 10 | endif 11 | LIBS = -lm -lpthread 12 | 13 | all: libbb_spi_lcd.a 14 | 15 | libbb_spi_lcd.a: bb_spi_lcd.o bb_parallel.o 16 | ar -rc libbb_spi_lcd.a bb_spi_lcd.o bb_parallel.o ;\ 17 | sudo cp libbb_spi_lcd.a /usr/local/lib ;\ 18 | sudo cp ../src/bb_spi_lcd.h /usr/local/include 19 | 20 | bb_spi_lcd.o: ../src/bb_spi_lcd.cpp ../src/linux_io.inl ../src/bb_spi_lcd.h 21 | $(CXX) $(CFLAGS) ../src/bb_spi_lcd.cpp 22 | 23 | bb_parallel.o: ../src/bb_parallel.cpp ../src/bb_spi_lcd.h 24 | $(CXX) $(CFLAGS) ../src/bb_parallel.cpp 25 | 26 | clean: 27 | rm *.o libbb_spi_lcd.a 28 | -------------------------------------------------------------------------------- /examples/spilcd_prop_font/spilcd_prop_font.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Simple demo of custom font drawing with the bb_spi_lcd library 3 | // 4 | 5 | #include 6 | #include "FreeSerif12pt7b.h" 7 | 8 | SPILCD lcd; 9 | 10 | // ILI9341 LCD info for my custom ESP32 rig with an ILI9341 11 | #define LED_PIN 16 12 | #define DC_PIN 12 13 | #define CS_PIN 4 14 | #define SCK_PIN 18 15 | #define MISO_PIN 19 16 | #define MOSI_PIN 23 17 | 18 | void setup() 19 | { 20 | // spilcdInit(&lcd, LCD_ILI9341, FLAGS_NONE, 32000000, CS_PIN, DC_PIN, -1, LED_PIN, MISO_PIN, MOSI_PIN, SCK_PIN); // custom ESP32 rig 21 | spilcdInit(&lcd, LCD_ST7789_135, FLAGS_NONE, 32000000, 5, 16, -1, 4, -1, 19, 18); // TTGO T-Display pin numbering 22 | 23 | // spilcdSetOrientation(&lcd, LCD_ORIENTATION_90); // 90 degrees rotated 24 | spilcdFill(&lcd, 0, DRAW_TO_LCD); 25 | } /* setup() */ 26 | 27 | void loop() 28 | { 29 | int iFG, y; 30 | char szTemp[32]; 31 | uint16_t usPal[] = {0xf800, 0x07e0, 0x001f, 0x7ff, 0xf81f, 0xffe0, 0xffff, 0x73ef}; 32 | 33 | for (y=0; y<2000; y++) 34 | { 35 | sprintf(szTemp, "%04d", y); 36 | spilcdWriteStringCustom(&lcd, (GFXfont *)&FreeSerif12pt7b, 0, 30, szTemp, 0xffff, 0, 1, DRAW_TO_LCD); 37 | } 38 | delay(4000); 39 | for (iFG=0; iFG<8; iFG++) 40 | { 41 | for (y=20; y<240; y+=20) 42 | { 43 | spilcdWriteStringCustom(&lcd, (GFXfont *)&FreeSerif12pt7b, 0, y, (char *)"Hello World!", usPal[iFG], 0, 0, DRAW_TO_LCD); 44 | } 45 | } 46 | } // loop 47 | -------------------------------------------------------------------------------- /src/s3_simd_byteswap.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2025 BitBank Software, Inc. 5 | // Project started March 30, 2025 6 | // 7 | #if defined (ARDUINO_ARCH_ESP32) && !defined(NO_SIMD) 8 | #if __has_include ("dsps_fft2r_platform.h") 9 | #include "dsps_fft2r_platform.h" 10 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 11 | .text 12 | .align 4 13 | 14 | .type s3_byteswap, @function 15 | .align 4 16 | .global s3_byteswap 17 | 18 | # Fast big/little endian byte swap function 19 | # The input pointer must be 16-byte aligned 20 | # and the pixel count must be a multiple of 16 21 | # 22 | # A2 A3 A4 23 | # Call as void s3_byteswap(uint16_t *pSrc, uint16_t *pDest, int iPixelCount); 24 | s3_byteswap: 25 | .align 4 26 | entry a1,16 27 | 28 | .byteswap_loop: 29 | 30 | ee.vld.128.ip q0,a2,16 # load 16 RGB565 src pixels into q0 & q1 31 | ee.vld.128.ip q1,a2,16 32 | mv.qr q6,q0 # swap endian order 33 | mv.qr q7,q1 34 | ee.vunzip.8 q6,q0 # swap the byte order 35 | ee.vunzip.8 q7,q1 36 | ee.vzip.8 q0,q6 37 | ee.vzip.8 q1,q7 38 | 39 | ee.vst.128.ip q0,a3,16 # store 16 finished destination pixels 40 | ee.vst.128.ip q1,a3,16 41 | addi.n a4,a4,-16 # decrement pixel count by 16 42 | bnez.n a4,.byteswap_loop 43 | 44 | # return value of 0 45 | retw.n 46 | 47 | #endif // dsps_fft2r_sc16_aes3_enabled 48 | #endif // __has_include 49 | #endif // ESP32 50 | -------------------------------------------------------------------------------- /examples/g5_image_test/smiley.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created with imageconvert, written by Larry Bank 3 | // 100 x 100 x 1-bit per pixel 4 | // compressed image data size = 239 bytes 5 | // 6 | // For non-Arduino systems 7 | #ifndef PROGMEM 8 | #define PROGMEM 9 | #endif 10 | const uint8_t smiley[] PROGMEM = { 11 | 0xbf,0xbb,0x64,0x00,0x64,0x00,0xe7,0x00,0x32,0xff,0x3a,0x82,0x93,0xa3,0x3d,0x04, 12 | 0x0e,0x10,0x70,0x83,0x84,0x72,0x62,0x83,0x84,0x72,0x05,0x43,0xa3,0x8d,0x38,0xe1, 13 | 0x02,0x06,0x1d,0x04,0x1b,0xa0,0x83,0x70,0x82,0x0d,0xd2,0x61,0xd2,0x6e,0x82,0x0d, 14 | 0xd2,0x6e,0x93,0xe9,0xba,0x46,0x5a,0x5b,0xa4,0x61,0xf4,0x20,0xdb,0xa4,0x9a,0x6d, 15 | 0xe9,0x6d,0xe9,0x5a,0xb7,0x55,0xbb,0xd5,0xab,0xd2,0xf6,0xf5,0xef,0x55,0xbb,0xd5, 16 | 0xab,0xd7,0xef,0x5e,0xf5,0xfb,0xd7,0xbf,0xfe,0xbf,0x7a,0xdd,0xfb,0x5f,0xfa,0xff, 17 | 0xbd,0x2f,0xbe,0xd3,0xff,0xaf,0xb4,0xff,0xeb,0xd3,0x4d,0x3f,0x11,0xb5,0xff,0xff, 18 | 0xf3,0x10,0x46,0x68,0xbd,0x34,0xea,0xf6,0xbe,0xd6,0xd7,0xff,0xd6,0xfe,0xb7,0xfa, 19 | 0x7e,0xda,0xda,0x5d,0x26,0xfe,0xb7,0xfb,0x09,0xfb,0x41,0xaa,0xde,0xd6,0xbb,0x25, 20 | 0x4c,0x22,0x77,0xf6,0xd3,0x0d,0x26,0x12,0xdc,0x43,0x69,0x0d,0x6d,0x86,0x12,0x5b, 21 | 0xb6,0x95,0x6d,0x86,0x12,0x5b,0xb0,0xc2,0x55,0xb6,0x0c,0x10,0x4b,0x6c,0x33,0x8e, 22 | 0x10,0x92,0xdb,0x14,0xb7,0x6a,0xad,0x86,0x12,0x5b,0x61,0x2d,0xb0,0xd2,0x56,0xc1, 23 | 0x82,0x4a,0xd8,0x30,0x49,0x5b,0x15,0xda,0x56,0xd2,0xb0,0xc2,0x56,0xd2,0x86,0xd2, 24 | 0xb0,0xc2,0x0a,0xc3,0x09,0x58,0x61,0x28,0x60,0xc1,0x05,0x67,0x20,0x51,0x43,0x39, 25 | 0x31,0x41,0x43,0x10,0xa1,0x85,0x0c,0x28,0x30,0x53,0xa8,0x29,0x32,0xef,0x00}; 26 | -------------------------------------------------------------------------------- /examples/generic_display/generic_display.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Generic SPI display example 3 | // This example shows how to use bb_spi_lcd if you are in possession 4 | // of a generic SPI LCD that isn't part of a pre-configured system. 5 | // For example, if you have a Feather board and connect it to a 6 | // ST7735 LCD sold separately 7 | // 8 | // Written by Larry Bank 9 | // Copyright (c) 2025 BitBank Software, Inc. 10 | // bitbank@pobox.com 11 | // 12 | #include 13 | BB_SPI_LCD lcd; 14 | 15 | // Pins for the Keyboard Featherwing with a FeatherS3 board controlling it 16 | #define CS_PIN 1 17 | #define DC_PIN 3 18 | #define RESET_PIN -1 19 | #define LED_PIN -1 20 | #define MISO_PIN MISO 21 | #define MOSI_PIN MOSI 22 | #define CLK_PIN SCK 23 | 24 | void setup() 25 | { 26 | // The begin method prototype: 27 | // begin(int iType, int iFlags, int iFreq, int iCSPin, int iDCPin, int iResetPin, int iLEDPin, int iMISOPin, int iMOSIPin, int iCLKPin) 28 | // For unused pins, pass -1 29 | lcd.begin(LCD_ILI9341, FLAGS_NONE, 40000000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, MISO_PIN, MOSI_PIN, CLK_PIN); 30 | lcd.setRotation(90); 31 | // The default orientation is portrait. To set landscape mode use setRotation(270) 32 | lcd.fillScreen(TFT_BLACK); // the default is to not keep a local copy of the framebuffer since it's large 33 | lcd.setTextColor(TFT_GREEN); // all functions will send data to the display and be visible immediately 34 | lcd.setFont(FONT_12x16); 35 | lcd.println("Generic SPI display"); 36 | lcd.println("Using bb_spi_lcd"); 37 | } 38 | 39 | void loop() 40 | { 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/Group5.cpp: -------------------------------------------------------------------------------- 1 | #include "g5enc.inl" 2 | #include "g5dec.inl" 3 | // 4 | // Group5 1-bit image compression library 5 | // Written by Larry Bank 6 | // Copyright (c) 2024 BitBank Software, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | //=========================================================================== 18 | // 19 | 20 | // 21 | // Decoder C++ wrapper functions 22 | // 23 | int G5DECODER::init(int iWidth, int iHeight, uint8_t *pData, int iDataSize) 24 | { 25 | return g5_decode_init(&_g5dec, iWidth, iHeight, pData, iDataSize); 26 | } /* init() */ 27 | 28 | int G5DECODER::decodeLine(uint8_t *pOut) 29 | { 30 | return g5_decode_line(&_g5dec, pOut); 31 | } /* decodeLine() */ 32 | 33 | // 34 | // Encoder C++ wrapper functions 35 | // 36 | int G5ENCODER::init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize) 37 | { 38 | return g5_encode_init(&_g5enc, iWidth, iHeight, pOut, iOutSize); 39 | } /* init() */ 40 | 41 | int G5ENCODER::encodeLine(uint8_t *pPixels) 42 | { 43 | return g5_encode_encodeLine(&_g5enc, pPixels); 44 | } /* encodeLine() */ 45 | 46 | int G5ENCODER::size() 47 | { 48 | return g5_encode_getOutSize(&_g5enc); 49 | } /* size() */ 50 | 51 | -------------------------------------------------------------------------------- /examples/cyd_touch_demo/cyd_touch_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // CYD Resistive Touch Demo 3 | // (cheap yellow display) 4 | // 5 | // This example shows how to use the XPT2046 resistive touch support 6 | // within bb_spi_lcd to read touch info from the LCD. Since the code 7 | // is part of bb_spi_lcd, it automatically scales and rotates the touch 8 | // points to match the display resolution and orientation. Another 9 | // goal of the code is to use the same data structure as the bb_captouch 10 | // library to make it easier to share code between capacitive and resistive 11 | // devices. 12 | // 13 | // Copyright (c) 2024 BitBank Software, Inc. 14 | // written by Larry Bank (bitbank@pobox.com) 15 | // project started March 5, 2024 16 | // 17 | #include 18 | BB_SPI_LCD lcd; 19 | // These pin definitions are for the most common 2.8" ILI9341 CYD 20 | #define TOUCH_MISO 39 21 | #define TOUCH_MOSI 32 22 | #define TOUCH_CLK 25 23 | #define TOUCH_CS 33 24 | 25 | void setup() { 26 | Serial.begin(115200); 27 | lcd.begin(DISPLAY_CYD); 28 | lcd.rtInit(TOUCH_MOSI, TOUCH_MISO, TOUCH_CLK, TOUCH_CS); // initialize touch AFTER initializing the LCD 29 | lcd.fillScreen(TFT_BLACK); 30 | lcd.setTextColor(TFT_GREEN, TFT_BLACK); 31 | lcd.setFont(FONT_12x16); 32 | lcd.println("Touch Test"); 33 | lcd.println("Clear by touching here"); 34 | } /* setup() */ 35 | 36 | void loop() { 37 | TOUCHINFO ti; 38 | while (1) { 39 | if (lcd.rtReadTouch(&ti)) { 40 | if (ti.y[0] < 32) { // user touched the top of the LCD, clear to black 41 | lcd.fillRect(0, 32, 320, 208, TFT_BLACK); 42 | } else { // draw a blue filled circle on the touch spot 43 | lcd.fillRect(ti.x[0], ti.y[0], 4, 4, TFT_BLUE); 44 | } 45 | } // if touched 46 | } // while (1) 47 | } /* loop() */ -------------------------------------------------------------------------------- /examples/blur_demo/blur_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Example showing how to use the blurSprite() method 3 | // This function provides a fast 3x3 Gaussian filter to blur 4 | // a source sprite into a destination 5 | // On the ESP32-S3, this code is accelerated 400% by using 6 | // the SIMD instructions (separate ASM code) 7 | // 8 | #include 9 | #include 10 | #include "bart_5clr.h" 11 | PNG png; 12 | BB_SPI_LCD lcd, sprite; 13 | int x_off, y_off; 14 | 15 | // 16 | // Load the example image into a sprite 17 | // 18 | void PNGDraw(PNGDRAW *pDraw) 19 | { 20 | if (pDraw->y < sprite.height()) { 21 | uint16_t *d = (uint16_t *)sprite.getBuffer(); 22 | d += pDraw->y * sprite.width(); // point to the correct line of the framebuffer 23 | png.getLineAsRGB565(pDraw, d, PNG_RGB565_BIG_ENDIAN, 0xffffffff); 24 | } 25 | } /* PNGDraw() */ 26 | 27 | void setup() 28 | { 29 | int rc; 30 | lcd.begin(DISPLAY_CYD_4848/*DISPLAY_WS_AMOLED_18*/); 31 | lcd.fillScreen(TFT_BLACK); 32 | // Load a PNG image into a sprite 33 | rc = png.openRAM((uint8_t *)bart_5clr, sizeof(bart_5clr), PNGDraw); 34 | if (rc == PNG_SUCCESS) { 35 | // Center it on the LCD 36 | x_off = (lcd.width() - png.getWidth())/2; 37 | y_off = (lcd.height() - png.getHeight())/2; 38 | // Create a sprite to hold the blurred image 39 | sprite.createVirtual(png.getWidth(), png.getHeight()); 40 | png.decode(NULL, 0); // decode the image into sprite1 41 | png.close(); 42 | } else { 43 | lcd.setTextColor(TFT_RED); 44 | lcd.setFont(FONT_12x16); 45 | lcd.println("Error opening PNG"); 46 | while (1) {}; 47 | } 48 | } 49 | 50 | void loop() 51 | { 52 | int i; 53 | 54 | lcd.setTextColor(TFT_GREEN); 55 | lcd.setFont(FONT_12x16); 56 | lcd.setCursor(120, 4); 57 | lcd.print("Original"); 58 | // Display the original image 59 | lcd.drawSprite(x_off, y_off, &sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); 60 | delay(3000); 61 | 62 | long l; 63 | for (i=0; i<40; i++) { 64 | lcd.setCursor(100,4); 65 | // Each iteration causes the image blur to increase 66 | lcd.printf("Iteration: %d", i); 67 | l = micros(); 68 | sprite.blurGaussian(); 69 | l = micros() - l; 70 | lcd.drawSprite(x_off, y_off, &sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); 71 | } // for i 72 | lcd.setCursor(70, lcd.height()-20); 73 | lcd.printf("blur time = %d us", (int)l); 74 | while (1) {}; 75 | } -------------------------------------------------------------------------------- /linux/examples/hello_cpp/main.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // SPI LCD test program 3 | // Written by Larry Bank 4 | // 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define PIMORONI_HAT 13 | 14 | #ifdef PIMORONI_HAT 15 | #define MISO_PIN 0 16 | #define MOSI_PIN 0 17 | #define CLK_PIN 1 18 | #define DC_PIN 9 19 | #define RESET_PIN -1 20 | #define CS_PIN 7 21 | #define LED_PIN 13 22 | #define LCD_TYPE LCD_ST7789 23 | // For Radxa Dragon Q6A 24 | // GPIO CHIP number 25 | //#define MISO_PIN 4 26 | // SPI bus number 27 | //#define MOSI_PIN 12 28 | //#define DC_PIN 48 29 | //#define RESET_PIN -1 30 | //#define CS_PIN 55 31 | //#define LED_PIN 56 32 | #else 33 | // Pin definitions for Adafruit PiTFT HAT 34 | uint8_t u8DataPins[8] = {14,15,16,17,18,19,20,21}; 35 | #define LCD_TYPE LCD_ILI9488 36 | #define RESET_PIN 13 37 | #define RD_PIN -1 38 | #define WR_PIN 27 39 | #define CS_PIN 22 40 | #define DC_PIN 4 41 | #define BUS_WIDTH 8 42 | //#define DC_PIN 25 43 | //#define RESET_PIN -1 44 | //#define CS_PIN 8 45 | //#define LED_PIN -1 46 | //#define LCD_TYPE LCD_ILI9341 47 | #endif 48 | 49 | BB_SPI_LCD lcd; 50 | static uint8_t ucBuffer[4096]; 51 | const uint16_t colors[8] = {TFT_BLACK, TFT_BLUE, TFT_GREEN, TFT_RED, TFT_MAGENTA, TFT_YELLOW, TFT_CYAN, TFT_WHITE}; 52 | int main(int argc, char *argv[]) 53 | { 54 | int i; 55 | 56 | #ifdef PIMORONI_HAT 57 | // int spilcdInit(int iLCDType, int bFlipRGB, int bInvert, int bFlipped, int32_t iSPIFreq, int iCSPin, int iDCPin, int iResetPin, int iLEDPin, int iMISOPin, int iMOSIPin, int iCLKPin); 58 | i = lcd.begin(LCD_TYPE, FLAGS_NONE, 20000000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, MISO_PIN, MOSI_PIN, CLK_PIN); 59 | #else 60 | i = lcd.beginParallel(LCD_TYPE, FLAGS_NONE, RESET_PIN, RD_PIN, WR_PIN, CS_PIN 61 | , DC_PIN, BUS_WIDTH, u8DataPins, 0); 62 | #endif 63 | if (i == 0) { 64 | lcd.setRotation(90); 65 | lcd.fillScreen(0); 66 | for (int j=0; j<200; j++) { 67 | lcd.setTextColor(colors[j & 7], TFT_BLACK); 68 | for (i=0; i<30; i++) { 69 | lcd.drawStringFast((char *)"Hello World!", 0, i*8, FONT_8x8); 70 | } // for i 71 | } // for j 72 | printf("Successfully initialized bb_spi_lcd library\n"); 73 | printf("Press ENTER to quit\n"); 74 | getchar(); 75 | //lcd.spilcdShutdown(&lcd); 76 | } else { 77 | printf("Unable to initialize the spi_lcd library\n"); 78 | } 79 | return 0; 80 | } /* main() */ 81 | -------------------------------------------------------------------------------- /linux/examples/simple_text/main.c: -------------------------------------------------------------------------------- 1 | // 2 | // SPI LCD test program 3 | // Written by Larry Bank 4 | // demo written for the Waveshare 1.3" 240x240 IPS LCD "Hat" 5 | // or the Pimoroni mini display HAT 6 | // 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "bb_spi_lcd.h" 13 | 14 | //#define PIMORONI_HAT 15 | //#define UNO_SHIELD 16 | #define PITFT_HAT 17 | 18 | #ifdef PIMORONI_HAT 19 | #define DC_PIN 9 20 | #define RESET_PIN -1 21 | #define CS_PIN 7 22 | #define LED_PIN 13 23 | #define LCD_TYPE LCD_ST7789 24 | #endif 25 | 26 | #ifdef PITFT_HAT 27 | // Pin definitions for Adafruit PiTFT HAT 28 | // GPIO 25 = Pin 22 29 | #define DC_PIN 25 30 | // GPIO 27 = Pin 13 31 | #define RESET_PIN -1 32 | // GPIO 8 = Pin 24 33 | <<<<<<< HEAD:linux/sample.c 34 | #define CS_PIN -1 35 | // GPIO 24 = Pin 18 36 | #define LED_PIN 18 37 | //#define LCD_TYPE LCD_ST7789_240 38 | // dev/spidev0.0 39 | #define MOSI_PIN 0 40 | #define MISO_PIN 0 41 | #define LCD_TYPE LCD_ILI9341 42 | #endif 43 | 44 | #ifdef UNO_SHIELD 45 | #define LCD_TYPE LCD_ILI9341 46 | #define RESET_PIN 13 47 | #define WR_PIN 27 48 | #define RD_PIN -1 49 | #define CS_PIN 22 50 | #define DC_PIN 4 51 | uint8_t u8DataPins[8] = {14,15,16,17,18,19,20,21}; 52 | ======= 53 | #define CS_PIN 8 54 | // GPIO 24 = Pin 18 55 | #define LED_PIN -1 56 | #define LCD_TYPE LCD_ILI9341 57 | >>>>>>> linux:linux/examples/simple_text/main.c 58 | #endif 59 | 60 | SPILCD lcd; 61 | 62 | int main(int argc, char *argv[]) 63 | { 64 | int i; 65 | 66 | // int spilcdInit(int iLCDType, int bFlipRGB, int bInvert, int bFlipped, int32_t iSPIFreq, int iCSPin, int iDCPin, int iResetPin, int iLEDPin, int iMISOPin, int iMOSIPin, int iCLKPin); 67 | i = spilcdInit(&lcd, LCD_TYPE, FLAGS_NONE, 62500000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, MISO_PIN, MOSI_PIN,-1,1); 68 | // i = spilcdParallelInit(&lcd, LCD_TYPE, FLAGS_NONE, RESET_PIN, RD_PIN, WR_PIN, CS_PIN, DC_PIN, 8, u8DataPins, 20000000); 69 | if (i == 0) 70 | { 71 | spilcdSetOrientation(&lcd, LCD_ORIENTATION_90); 72 | spilcdFill(&lcd, 0, DRAW_TO_LCD); 73 | for (i=0; i<30; i++) 74 | spilcdWriteString(&lcd, 0,i*8,(char *)"Hello World!", 0xf81f,0,FONT_8x8, DRAW_TO_LCD); 75 | printf("Successfully initialized bb_spi_lcd library\n"); 76 | printf("Press ENTER to quit\n"); 77 | getchar(); 78 | spilcdShutdown(&lcd); 79 | } 80 | else 81 | { 82 | printf("Unable to initialize the spi_lcd library\n"); 83 | } 84 | return 0; 85 | } /* main() */ 86 | -------------------------------------------------------------------------------- /examples/parallel_shield_demo/parallel_shield_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Demo sketch to show how to use 3 | // the Kuman 3.5" UNO LCD Shield 4 | // (8-bit parallel ILI9486) 5 | // with bb_spi_lcd on the RP2040 6 | // written by Larry Bank 7/22/2022 7 | // bitbank@pobox.com 8 | // 9 | #include 10 | #include 11 | #include "homer_bushes.h" 12 | 13 | #define WIDTH 480 14 | #define HEIGHT 320 15 | 16 | BB_SPI_LCD lcd; 17 | AnimatedGIF gif; 18 | int x_offset, y_offset; 19 | uint16_t *pImage; 20 | 21 | // Draw a line of image directly on the LCD 22 | void GIFDraw(GIFDRAW *pDraw) 23 | { 24 | uint8_t c, *s, ucTransparent; 25 | uint16_t *d, *usPalette; 26 | int x, iWidth; 27 | 28 | if (pDraw->y == 0) { // first line, allocate a backing buffer 29 | pImage = (uint16_t *)malloc(pDraw->iWidth * pDraw->iHeight * 2); 30 | lcd.setAddrWindow(x_offset+pDraw->iX, y_offset+pDraw->iY, pDraw->iWidth, pDraw->iHeight); 31 | } 32 | iWidth = pDraw->iWidth; 33 | if (iWidth > lcd.width()) 34 | iWidth = lcd.width(); 35 | usPalette = pDraw->pPalette; 36 | 37 | s = pDraw->pPixels; 38 | d = &pImage[pDraw->y * pDraw->iWidth]; 39 | if (pDraw->ucDisposalMethod == 2) // restore to background color 40 | { 41 | memset(d, 0, pDraw->iWidth * 2); // set background color to black 42 | } 43 | ucTransparent = pDraw->ucTransparent; 44 | for (x=0; xiWidth); 51 | if (pDraw->y == pDraw->iHeight-1) { // last line 52 | free(pImage); // free backing buffer 53 | } 54 | } /* GIFDraw() */ 55 | 56 | void playGIF(void) 57 | { 58 | if (gif.open((uint8_t *)homer_bushes, sizeof(homer_bushes), GIFDraw)) 59 | { 60 | x_offset = (lcd.width() - gif.getCanvasWidth())/2; 61 | if (x_offset < 0) x_offset = 0; 62 | y_offset = (lcd.height() - gif.getCanvasHeight())/2; 63 | if (y_offset < 0) y_offset = 0; 64 | while (gif.playFrame(false, NULL)) 65 | { 66 | } 67 | gif.close(); 68 | } // if GIF opened 69 | } /* playGIF() */ 70 | 71 | void setup() { 72 | lcd.begin(DISPLAY_KUMAN_35); 73 | lcd.fillScreen(0); 74 | lcd.setFont(FONT_12x16); 75 | lcd.setTextColor(0x7e0,0x0000); 76 | lcd.println("Starting GIF Demo..."); 77 | gif.begin(GIF_PALETTE_RGB565_BE); // big endian pixels 78 | delay(3000); 79 | lcd.fillScreen(0); 80 | } /* setup() */ 81 | 82 | void loop() { 83 | playGIF(); 84 | } /* loop() */ 85 | -------------------------------------------------------------------------------- /examples/atoms3_gif/atoms3_gif.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "thisisfine_128x128.h" 4 | 5 | #define WIDTH 128 6 | #define HEIGHT 128 7 | 8 | #define BUTTON_USER 41 9 | #define SDA_PIN 38 10 | #define SCL_PIN 39 11 | 12 | BB_SPI_LCD lcd; 13 | AnimatedGIF gif; 14 | 15 | static uint16_t *pImage; // buffered GIF image 16 | int x_offset, y_offset; 17 | 18 | // Draw a line of image directly on the LCD 19 | void GIFDraw(GIFDRAW *pDraw) 20 | { 21 | uint8_t c, *s, ucTransparent; 22 | uint16_t *d, *usPalette; 23 | int x, iWidth; 24 | 25 | if (pDraw->y == 0) { // first line, allocate a backing buffer 26 | pImage = (uint16_t *)malloc(gif.getCanvasWidth() * gif.getCanvasHeight() * 2); 27 | lcd.setAddrWindow(x_offset+pDraw->iX, y_offset+pDraw->iY, pDraw->iWidth, pDraw->iHeight); 28 | } 29 | iWidth = pDraw->iWidth; 30 | if (iWidth > lcd.width()) 31 | iWidth = lcd.width(); 32 | usPalette = pDraw->pPalette; 33 | 34 | s = pDraw->pPixels; 35 | d = &pImage[pDraw->iX + (pDraw->y + pDraw->iY) * pDraw->iWidth]; 36 | if (pDraw->ucDisposalMethod == 2) // restore to background color 37 | { 38 | memset(d, 0, pDraw->iWidth * 2); // set background color to black 39 | } 40 | ucTransparent = pDraw->ucTransparent; 41 | for (x=0; xiWidth); 48 | if (pDraw->y == pDraw->iHeight-1) { // last line 49 | free(pImage); // free backing buffer 50 | } 51 | } /* GIFDraw() */ 52 | 53 | void setup() { 54 | // Serial.begin(115200); 55 | // delay(3000); 56 | // Serial.println("Starting..."); 57 | pinMode(BUTTON_USER, INPUT); 58 | lcd.begin(DISPLAY_M5STACK_ATOMS3); 59 | lcd.allocBuffer(); 60 | lcd.fillScreen(0); 61 | lcd.setFont(FONT_12x16); 62 | lcd.setTextColor(0x7e0,0x0000); 63 | lcd.println("Starting..."); 64 | gif.begin(GIF_PALETTE_RGB565_BE); // big endian pixels 65 | delay(3000); 66 | } /* setup() */ 67 | 68 | void playGIF(void) 69 | { 70 | long lTime = millis(); 71 | int iFrames = 0; 72 | if (gif.open((uint8_t *)thisisfine_128x128, sizeof(thisisfine_128x128), GIFDraw)) 73 | { 74 | x_offset = (lcd.width() - gif.getCanvasWidth())/2; 75 | if (x_offset < 0) x_offset = 0; 76 | y_offset = (lcd.height() - gif.getCanvasHeight())/2; 77 | if (y_offset < 0) y_offset = 0; 78 | // Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); 79 | // Serial.flush(); 80 | while (gif.playFrame(true, NULL)) 81 | { 82 | iFrames++; 83 | } 84 | gif.close(); 85 | lTime = millis() - lTime; 86 | //Serial.printf("%d frames in %d ms\n", iFrames, (int)lTime); 87 | } // if GIF opened 88 | } /* playGIF() */ 89 | 90 | void loop() { 91 | playGIF(); 92 | } /* loop() */ 93 | -------------------------------------------------------------------------------- /examples/alpha_blend/alpha_blend.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "shiny_hand.h" 3 | #include "flowers_96x96.h" 4 | #include "smiley_transparent.h" 5 | 6 | BB_SPI_LCD lcd; 7 | BB_SPI_LCD fg_sprite, bg_sprite, dest_sprite; 8 | 9 | void setup() { 10 | lcd.begin(DISPLAY_VIEWE_2432); //DISPLAY_WS_AMOLED_18); 11 | pinMode(13, OUTPUT); 12 | digitalWrite(13, 0); // turn off backlight 13 | lcd.fillScreen(TFT_BLACK); 14 | lcd.setTextColor(TFT_GREEN); 15 | lcd.setFont(FONT_12x16); 16 | lcd.println("bb_spi_lcd ESP32-S3 SIMD Alpha Blending"); 17 | //lcd.sleep(true); 18 | //delay(10000); 19 | //esp_sleep_enable_timer_wakeup(10000000); 20 | //esp_deep_sleep_start(); 21 | 22 | fg_sprite.createVirtual(96, 96); 23 | bg_sprite.createVirtual(96, 96); 24 | dest_sprite.createVirtual(96, 96); 25 | fg_sprite.drawBMP(shiny_hand, 0, 0, 0, -1, DRAW_TO_RAM); 26 | bg_sprite.drawBMP(flowers_96x96, 0, 0, 0, -1, DRAW_TO_RAM); 27 | } 28 | 29 | void loop() { 30 | int i, x, y; 31 | long l; 32 | uint8_t u8Alpha; 33 | x = (lcd.width() - 96)/2; 34 | y = (lcd.height() - 96)/2; 35 | for (i=0; i<3; i++) { 36 | for (uint8_t u8Alpha = 0; u8Alpha < 32; u8Alpha++) { 37 | l = micros(); 38 | lcd.blendSprite(&fg_sprite, &bg_sprite, &dest_sprite, u8Alpha); 39 | l = micros() - l; 40 | delay(40); 41 | lcd.drawSprite(x, y, &dest_sprite, 1.0f, -1, DRAW_TO_LCD); 42 | } 43 | for (u8Alpha = 31; u8Alpha >= 1; u8Alpha--) { 44 | lcd.blendSprite(&fg_sprite, &bg_sprite, &dest_sprite, u8Alpha); 45 | delay(40); 46 | lcd.drawSprite(x, y, &dest_sprite, 1.0f, -1, DRAW_TO_LCD); 47 | } 48 | } // for i 49 | lcd.setCursor(0, lcd.height() - 16); 50 | lcd.printf("96x96 blend - %d microseconds", (int)l); 51 | delay(5000); 52 | lcd.fillScreen(TFT_BLACK); 53 | lcd.println("Alpha Blend w/tranparent color"); 54 | // fill the display with the flower 55 | bg_sprite.drawBMP(flowers_96x96, 0, 0, 0, -1, DRAW_TO_RAM); 56 | fg_sprite.drawBMP(smiley_transparent, 0, 0, 0, -1, DRAW_TO_RAM); 57 | for (i=0; i<5; i++) { 58 | for (u8Alpha = 0; u8Alpha<=32; u8Alpha++) { 59 | // loop over the images and increment the alpha 60 | for (y=0; y0; u8Alpha--) { 68 | // loop over the images and increment the alpha 69 | for (y=0; y 2 | // 3 | // callback_demo 4 | // 5 | // An example sketch for using the callback functions of bb_spi_lcd to allow connection to displays 6 | // other than SPI. This allows you to write 2 simple functions specific to your target CPU and display 7 | // while making use of all of the features of bb_spi_lcd 8 | // 9 | // The particular display used for this code is a Kuman 3.5" 320x480 LCD + touch shield for Uno and ATmega2560 10 | // (ILI9486) 11 | // 12 | // Port C bits 13 | #define LCD_RST 0x10 14 | #define LCD_CS 0x8 15 | #define LCD_RS 0x4 16 | #define LCD_WR 0x2 17 | #define LCD_RD 0x1 18 | // Data Lines 19 | // D0-D1 -> PORTB 0, 1 20 | // D2-D7 -> PORTD 2-7 21 | 22 | SPILCD lcd; 23 | // 24 | // Called once during spilcdInit() 25 | // Use this function to set up the GPIO ports 26 | // and reset the display (if needed) 27 | // 28 | void ResetCallback(void) 29 | { 30 | int i; 31 | 32 | for (i=2; i<10; i++) 33 | pinMode(i, OUTPUT); 34 | for (i=A0; i 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | AnimatedGIF gif; 10 | BB_SPI_LCD lcd; 11 | //#define RADXA 12 | //#define PARALLEL_LCD 13 | #define ADAFRUIT_PITFT 14 | 15 | #ifdef ADAFRUIT_PITFT 16 | #define LCD_TYPE LCD_ILI9341 17 | #define DC_PIN 25 18 | #define RESET_PIN 27 19 | #define CS_PIN 8 20 | #define LED_PIN -1 21 | #define SPI_NUM 0 22 | #define GPIO_CHIP 0 23 | #endif // ADAFRUIT_PITFT 24 | 25 | #ifdef RADXA 26 | #define LCD_TYPE LCD_ST7789 27 | #define DC_PIN 48 28 | #define RESET_PIN -1 29 | #define CS_PIN 55 30 | #define LED_PIN 56 31 | #define GPIO_CHIP 4 32 | #define SPI_NUM 12 33 | #endif 34 | #ifdef PARALLEL_LCD 35 | // definitions for parallel 3.5" Kumon LCD shield 36 | uint8_t u8DataPins[8] = {14,15,16,17,18,19,20,21}; 37 | #define LCD_TYPE LCD_ILI9488 38 | #define RESET_PIN 13 39 | #define RD_PIN -1 40 | #define WR_PIN 27 41 | #define CS_PIN 22 42 | #define DC_PIN 4 43 | #define BUS_WIDTH 8 44 | #endif // PARALLEL_LCD 45 | 46 | int MilliTime() 47 | { 48 | int iTime; 49 | struct timespec res; 50 | 51 | clock_gettime(CLOCK_MONOTONIC, &res); 52 | iTime = 1000*res.tv_sec + res.tv_nsec/1000000; 53 | 54 | return iTime; 55 | } /* MilliTime() */ 56 | 57 | int main(int argc, char *argv[]) 58 | { 59 | int iTime, iFrame; 60 | int w, h; 61 | uint8_t *pStart; 62 | int iLoops = 1; 63 | if (argc != 2 && argc != 3) { 64 | printf("bb_spi_lcd gif_player\nUsage: gif_player \n"); 65 | return -1; 66 | } 67 | #ifndef PARALLEL_LCD 68 | lcd.begin(LCD_TYPE, FLAGS_NONE, 62500000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, GPIO_CHIP, SPI_NUM, 0); 69 | // lcd.beginQSPI(LCD_ST77916B, 0, 17, 20, 13, 14, 15, 16, 18, 3); 70 | #else 71 | lcd.beginParallel(LCD_TYPE, FLAGS_SWAP_RB, RESET_PIN, RD_PIN, WR_PIN, CS_PIN 72 | , DC_PIN, BUS_WIDTH, u8DataPins, 18); // last parameter is the delay cycles for parallel data output 73 | #endif // PARALLEL_LCD 74 | if (lcd.width() < lcd.height()) { 75 | lcd.setRotation(90); 76 | } 77 | lcd.fillScreen(TFT_BLACK); 78 | 79 | if (argc == 3) iLoops = atoi(argv[2]); 80 | 81 | gif.begin(BIG_ENDIAN_PIXELS); 82 | iFrame = 0; 83 | if (gif.open(argv[1], NULL)) { 84 | w = gif.getCanvasWidth(); 85 | h = gif.getCanvasHeight(); 86 | printf("Successfully opened GIF; Canvas size = %d x %d\n", w, h); 87 | gif.setFrameBuf((uint8_t*)malloc(w*h*3)); 88 | pStart = gif.getFrameBuf(); 89 | pStart += w*h; // point to RGB565 pixels 90 | gif.setDrawType(GIF_DRAW_COOKED); // fully prepare pixels 91 | iFrame = 0; 92 | iTime = MilliTime(); 93 | for (int i=0; i 2 | #include 3 | #include "pattern_368x448.h" 4 | PNG png; 5 | BB_SPI_LCD lcd; 6 | BB_SPI_LCD bg_sprite, fg_sprite, mask_sprite; 7 | int x, y; 8 | 9 | void PNGDraw(PNGDRAW *pDraw) 10 | { 11 | if (pDraw->y < lcd.height()) { 12 | uint16_t *d = (uint16_t *)lcd.getBuffer(); 13 | d += pDraw->y * lcd.width(); // point to the correct line of the framebuffer 14 | png.getLineAsRGB565(pDraw, d, PNG_RGB565_BIG_ENDIAN, 0xffffffff); 15 | } 16 | } /* PNGDraw() */ 17 | 18 | void setup() { 19 | int rc; 20 | long l; 21 | 22 | lcd.begin(DISPLAY_WS_AMOLED_18); // Waveshare 1.8" 368x448 AMOLED 23 | lcd.allocBuffer(); 24 | lcd.fillScreen(TFT_BLACK); 25 | lcd.setTextColor(TFT_GREEN); 26 | lcd.println("Tint color example"); 27 | 28 | // Use our PNG as the display background image 29 | rc = png.openRAM((uint8_t *)pattern_368x448, sizeof(pattern_368x448), PNGDraw); 30 | if (rc == PNG_SUCCESS) { 31 | png.decode(NULL, 0); // simple decode, no options 32 | png.close(); 33 | lcd.display(); // copy framebuffer to display 34 | } else { 35 | lcd.println("Error opening PNG"); 36 | while (1) {}; 37 | } 38 | // Grab a 128x128 slice of the display so that we can use it for pattern tinting 39 | bg_sprite.createVirtual(128, 128); 40 | x = (lcd.width() - 128)/2; // center it 41 | y = (lcd.height() - 128)/2; 42 | lcd.readImage(x, y, 128, 128, (uint16_t *)bg_sprite.getBuffer()); 43 | // Create a sprite to hold the tinted results 44 | fg_sprite.createVirtual(128, 128); 45 | // Prepare a rounded rect as a pushbutton 46 | mask_sprite.createVirtual(128, 128); 47 | mask_sprite.fillScreen(TFT_BLACK); 48 | mask_sprite.fillRoundRect(0, 0, 128, 128, 12, TFT_WHITE); // white will be our mask 49 | mask_sprite.setTextColor(TFT_BLACK, TFT_WHITE); 50 | mask_sprite.setFont(FONT_12x16); 51 | mask_sprite.setCursor(40, 40); 52 | mask_sprite.print("Push"); 53 | mask_sprite.setCursor(28, 72); 54 | mask_sprite.print("Button"); 55 | for (int i = 0; i<2; i++) { 56 | // Draw a red tinted button 57 | uint16_t u16Tint = TFT_RED; 58 | uint8_t alpha; 59 | for (alpha = 0; alpha < 32; alpha++) { 60 | l = micros(); 61 | fg_sprite.maskedTint(&bg_sprite, &mask_sprite, 0, 0, u16Tint, alpha); 62 | l = micros() - l; 63 | lcd.drawSprite(x, y, &fg_sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); // draw on the display 64 | delay(50); 65 | } // for each alpha 66 | for (alpha = 32; alpha > 0; alpha--) { 67 | fg_sprite.maskedTint(&bg_sprite, &mask_sprite, 0, 0, u16Tint, alpha); 68 | lcd.drawSprite(x, y, &fg_sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); // draw on the display 69 | delay(50); 70 | } 71 | u16Tint = TFT_GREEN; // green 72 | for (alpha = 0; alpha < 32; alpha++) { 73 | fg_sprite.maskedTint(&bg_sprite, &mask_sprite, 0, 0, u16Tint, alpha); 74 | lcd.drawSprite(x, y, &fg_sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); // draw on the display 75 | delay(50); 76 | } // for each alpha 77 | for (alpha = 32; alpha > 0; alpha--) { 78 | fg_sprite.maskedTint(&bg_sprite, &mask_sprite, 0, 0, u16Tint, alpha); 79 | lcd.drawSprite(x, y, &fg_sprite, 1.0f, 0xffffffff, DRAW_TO_LCD); // draw on the display 80 | delay(50); 81 | } 82 | } // for i 83 | lcd.setFont(FONT_12x16); 84 | lcd.setTextColor(TFT_GREEN, TFT_BLACK); 85 | lcd.setCursor(0, lcd.height() - 16); 86 | lcd.printf("tint time = %d microseconds", (int)l); 87 | } /* setup() */ 88 | 89 | void loop() { 90 | } 91 | -------------------------------------------------------------------------------- /examples/wio_dma_gif_demo/wio_dma_gif_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Demo to display an animated GIF 3 | // sequence on the 320x240 LCD 4 | // of the Seeed Studio Wio Termina 5 | // To get a faster framerate, DMA is used 6 | // on the SPI writes so that the CPU can 7 | // continue decoding GIF data while pixels 8 | // are written to the display 9 | // 10 | #include 11 | #include 12 | #include "badger.h" 13 | 14 | #define DISPLAY_WIDTH 320 15 | #define DISPLAY_HEIGHT 240 16 | 17 | // Wio Terminal Arduino pin definitions 18 | #define DC_PIN 70 19 | #define RESET_PIN 71 20 | #define LED_PIN 72 21 | #define CS_PIN PIN_SPI3_SS 22 | // No need to define these since the Adafruit_ZeroDMA lib manages them 23 | #define MOSI_PIN -1 24 | #define MISO_PIN -1 25 | #define CLK_PIN -1 26 | 27 | AnimatedGIF gif; 28 | static SPILCD lcd; 29 | static uint8_t ucTXBuf[1024]; 30 | 31 | // Draw a line of image directly on the LCD 32 | void GIFDraw(GIFDRAW *pDraw) 33 | { 34 | uint8_t *s; 35 | uint16_t *d, *usPalette, usTemp[320]; 36 | int x, y, iWidth; 37 | 38 | usPalette = pDraw->pPalette; 39 | y = pDraw->iY + pDraw->y; // current line 40 | iWidth = pDraw->iWidth; 41 | if (iWidth > DISPLAY_WIDTH) 42 | iWidth = DISPLAY_WIDTH; 43 | s = pDraw->pPixels; 44 | if (pDraw->ucDisposalMethod == 2) // restore to background color 45 | { 46 | for (x=0; xucTransparent) 49 | s[x] = pDraw->ucBackground; 50 | } 51 | pDraw->ucHasTransparency = 0; 52 | } 53 | // Apply the new pixels to the main image 54 | if (pDraw->ucHasTransparency) // if transparency used 55 | { 56 | uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 57 | int x, iCount; 58 | pEnd = s + iWidth; 59 | x = 0; 60 | iCount = 0; // count non-transparent pixels 61 | while(x < iWidth) 62 | { 63 | c = ucTransparent-1; 64 | d = usTemp; 65 | while (c != ucTransparent && s < pEnd) 66 | { 67 | c = *s++; 68 | if (c == ucTransparent) // done, stop 69 | { 70 | s--; // back up to treat it like transparent 71 | } 72 | else // opaque 73 | { 74 | *d++ = usPalette[c]; 75 | iCount++; 76 | } 77 | } // while looking for opaque pixels 78 | if (iCount) // any opaque pixels? 79 | { 80 | spilcdSetPosition(&lcd, pDraw->iX+x, y, iCount, 1, DRAW_TO_LCD); 81 | spilcdWriteDataBlock(&lcd, (uint8_t *)usTemp, iCount*2, DRAW_TO_LCD | DRAW_WITH_DMA); 82 | x += iCount; 83 | iCount = 0; 84 | } 85 | // no, look for a run of transparent pixels 86 | c = ucTransparent; 87 | while (c == ucTransparent && s < pEnd) 88 | { 89 | c = *s++; 90 | if (c == ucTransparent) 91 | iCount++; 92 | else 93 | s--; 94 | } 95 | if (iCount) 96 | { 97 | x += iCount; // skip these 98 | iCount = 0; 99 | } 100 | } 101 | } 102 | else 103 | { 104 | s = pDraw->pPixels; 105 | // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 106 | for (x=0; xiX, y, iWidth, 1, DRAW_TO_LCD); 109 | spilcdWriteDataBlock(&lcd, (uint8_t *)usTemp, iWidth*2, DRAW_TO_LCD | DRAW_WITH_DMA); 110 | } 111 | } /* GIFDraw() */ 112 | 113 | void setup() { 114 | 115 | // A TX buffer needs to be defined to use DMA for writing 116 | spilcdSetTXBuffer(ucTXBuf, sizeof(ucTXBuf)); 117 | spilcdInit(&lcd, LCD_ILI9341, FLAGS_NONE, 30000000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, MISO_PIN, MOSI_PIN, CLK_PIN); 118 | spilcdSetOrientation(&lcd, LCD_ORIENTATION_270); 119 | spilcdFill(&lcd, 0, DRAW_TO_LCD); 120 | 121 | gif.begin(BIG_ENDIAN_PIXELS); 122 | } /* setup() */ 123 | 124 | void loop() { 125 | 126 | if (gif.open((uint8_t *)badger, sizeof(badger), GIFDraw)) 127 | { 128 | Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", gif.getCanvasWidth(), gif.getCanvasHeight()); 129 | while (gif.playFrame(true, NULL)) 130 | { 131 | } 132 | gif.close(); 133 | } 134 | } /* loop() */ 135 | -------------------------------------------------------------------------------- /src/s3_simd_alphablend.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2025 BitBank Software, Inc. 5 | // Project started March 7, 2025 6 | // 7 | #if defined (ARDUINO_ARCH_ESP32) && !defined(NO_SIMD) 8 | #if __has_include ("dsps_fft2r_platform.h") 9 | #include "dsps_fft2r_platform.h" 10 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 11 | .text 12 | .align 4 13 | 14 | .type s3_alpha_blend_be, @function 15 | .align 4 16 | .global s3_alpha_blend_be 17 | 18 | # Alpha blend RGB565 big-endian pixels 19 | # The alpha value can be from 0 to 32 20 | # The pixel count must be a multiple of 8 21 | # Ideally it should take less than 10 clocks per pixel 22 | # This code should be much simpler, but the S3 SIMD limitations 23 | # cause extra thinking and work. For example, the only right shift 24 | # available is 32-bits and arithmetic (copies sign bit) 25 | # this messes up manipulation of 16-bit pixels (see below) 26 | # 27 | # A2 A3 A4 A5 28 | # Call as void s3_alpha_blend_be(uint16_t *pFG, uint16_t *pBG, uint16_t *pDest, uint32_t count, uint8_t alpha, const uint16_t *pMasks); 29 | s3_alpha_blend_be: 30 | # no idea what this frequency keyword does 31 | # .frequency 1.000 0.000 32 | .align 4 33 | entry a1,16 34 | 35 | # make the alpha value as a pair of 16-bit values for later 36 | slli a8,a6,16 37 | or a8,a8,a6 # now we have the alpha in 16-bit slots 38 | 39 | ee.movi.32.q q5,a8,0 # set up alpha value in 8 slots of Q5 40 | ee.movi.32.q q5,a8,1 41 | ee.movi.32.q q5,a8,2 42 | ee.movi.32.q q5,a8,3 43 | 44 | movi.n a8,32 # inverted alpha for destination 45 | sub a6,a8,a6 46 | slli a8,a6,16 # prepare it for 16-bit slots 47 | or a8,a8,a6 48 | ee.movi.32.q q6,a8,0 # set up inverted alpha in 8 slots of Q6 49 | ee.movi.32.q q6,a8,1 50 | ee.movi.32.q q6,a8,2 51 | ee.movi.32.q q6,a8,3 52 | 53 | movi.n a8,0 # prepare shift amounts 54 | movi.n a9,5 55 | 56 | .alpha_loop_be: 57 | 58 | wsr.sar a8 # SAR (shift amount register) = 0 59 | 60 | ee.vld.128.ip q1,a2,16 # load 8 RGB565 src pixels into q1 61 | ee.vld.128.ip q2,a3,16 # load 8 RGB565 dest pixels into q2 62 | mv.qr q7,q1 # big endian data, need to swap the byte order 63 | ee.vunzip.8 q7,q1 # swap the byte order to be big-endian 64 | ee.vzip.8 q1,q7 65 | 66 | mv.qr q7,q2 67 | ee.vunzip.8 q7,q2 68 | ee.vzip.8 q2,q7 69 | 70 | ee.vldbc.16.ip q0,a7,2 # load the blue mask into all 8 slots 71 | ee.andq q3,q1,q0 # apply the mask to all pixels 72 | ee.andq q4,q2,q0 73 | ee.vmul.u16 q3,q3,q5 # multiply source pixels by source alpha 74 | ee.vmul.u16 q4,q4,q6 # multiply dest pixels by dest alpha 75 | ee.vadds.s16 q7,q3,q4 # combine src+dest blue 76 | wsr.sar a9 # set up right shift by 5 77 | ee.vsr.32 q7,q7 # shift blue back to normal position 78 | ee.andq q7,q7,q0 79 | 80 | ee.vldbc.16.ip q0,a7,2 # load the green mask 81 | ee.andq q3,q1,q0 82 | ee.andq q4,q2,q0 83 | ee.vmul.u16 q3,q3,q5 # mult green by src+dest alpha 84 | ee.vmul.u16 q4,q4,q6 # leave the right shift of 5 85 | ee.vadds.s16 q4,q3,q4 # combine src+dest green 86 | ee.andq q4,q4,q0 87 | ee.orq q7,q7,q4 # combine green+blue 88 | 89 | ee.vldbc.16.ip q0,a7,2 # load red mask for arith shift problem 90 | ee.vsr.32 q1,q1 # shift red down 5 bits to not overflow 91 | ee.vsr.32 q2,q2 92 | ee.andq q1,q1,q0 # mask at the new position 93 | ee.andq q2,q2,q0 94 | ee.vmul.u16 q1,q1,q5 # mult red by src+dest alpha 95 | ee.vmul.u16 q2,q2,q6 96 | ee.vadds.s16 q1,q1,q2 # combine src+dst red 97 | ee.vsl.32 q1,q1 # now shift left 5 to fix the position 98 | ee.vldbc.16 q0,a7 99 | ee.andq q1,q1,q0 100 | ee.orq q1,q7,q1 # combine R + G + B 101 | 102 | # we now have 16-bit slots with the results we want 103 | mv.qr q7,q1 # reverse byte order from little to big endian 104 | ee.vunzip.8 q7,q1 105 | ee.vzip.8 q1,q7 106 | 107 | ee.vst.128.ip q1,a4,16 # store 8 finished destination pixels 108 | addi.n a7,a7,-6 # reset color masks pointer 109 | addi.n a5,a5,-8 # decrement pixel count by 8 110 | bnez.n a5,.alpha_loop_be 111 | 112 | # return value of 0 113 | retw.n 114 | 115 | #endif // dsps_fft2r_sc16_aes3_enabled 116 | #endif // __has_include 117 | #endif // ESP32 118 | -------------------------------------------------------------------------------- /src/s3_simd_alphatrans.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2025 BitBank Software, Inc. 5 | // Project started March 7, 2025 6 | // 7 | #if defined (ARDUINO_ARCH_ESP32) && !defined(NO_SIMD) 8 | #if __has_include ("dsps_fft2r_platform.h") 9 | #include "dsps_fft2r_platform.h" 10 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 11 | .text 12 | .align 4 13 | 14 | .type s3_alphatrans_be, @function 15 | .align 4 16 | .global s3_alphatrans_be 17 | 18 | # Alpha blend RGB565 big-endian pixels with a transparent color 19 | # The alpha value can be from 0 to 32 20 | # The pixel count must be a multiple of 8 21 | # Ideally it should take less than 10 clocks per pixel 22 | # This code should be much simpler, but the S3 SIMD limitations 23 | # cause extra thinking and work. For example, the only right shift 24 | # available is 32-bits and arithmetic (copies sign bit) 25 | # this messes up manipulation of 16-bit pixels (see below) 26 | # 27 | # A2 A3 A4 A5 A6 A7 A8 28 | # Call as void s3_alphatrans_be(uint16_t *pFG, uint16_t *pBG, uint16_t *pDest, uint32_t count, uint8_t alpha, uint16_t *transparent, const uint16_t *pMasks); 29 | s3_alphatrans_be: 30 | # no idea what this frequency keyword does 31 | # .frequency 1.000 0.000 32 | .align 4 33 | entry a1,16 34 | l32i.n a8,a1,16 # 7th parameter is passed on the stack 35 | 36 | # make the alpha value as a pair of 16-bit values for later 37 | slli a9,a6,16 38 | or a9,a9,a6 # now we have the alpha in 16-bit slots 39 | 40 | ee.movi.32.q q5,a9,0 # set up alpha value in 8 slots of Q5 41 | ee.movi.32.q q5,a9,1 42 | ee.movi.32.q q5,a9,2 43 | ee.movi.32.q q5,a9,3 44 | 45 | movi.n a9,32 # inverted alpha for destination 46 | sub a6,a9,a6 47 | slli a9,a6,16 # prepare it for 16-bit slots 48 | or a9,a9,a6 49 | ee.movi.32.q q6,a9,0 # set up inverted alpha in 8 slots of Q6 50 | ee.movi.32.q q6,a9,1 51 | ee.movi.32.q q6,a9,2 52 | ee.movi.32.q q6,a9,3 53 | 54 | movi.n a10,0 # prepare shift amounts 55 | movi.n a11,5 56 | 57 | .alphatrans_loop: 58 | 59 | wsr.sar a10 # SAR (shift amount register) = 0 60 | 61 | ee.vld.128.ip q1,a2,16 # load 8 RGB565 src pixels into q1 62 | ee.vld.128.ip q2,a3,16 # load 8 RGB565 dest pixels into q2 63 | 64 | mv.qr q7,q1 # big endian data, need to swap the byte order 65 | ee.vunzip.8 q7,q1 # swap the byte order to be big-endian 66 | ee.vzip.8 q1,q7 67 | 68 | mv.qr q7,q2 69 | ee.vunzip.8 q7,q2 70 | ee.vzip.8 q2,q7 71 | 72 | ee.vldbc.16.ip q0,a7,0 # load transparent color to skip (repeat 8 slots) 73 | ee.vcmp.eq.s16 q4,q0,q1 # compare to source pixels 74 | ee.andq q0,q2,q4 # get destination pixels to replace transparent 75 | ee.notq q4,q4 # combine with source pixels 76 | ee.andq q1,q1,q4 # remove matching source pixels 77 | ee.orq q1,q1,q0 # and replace with destination pixels 78 | 79 | ee.vldbc.16.ip q0,a8,2 # load the blue mask into all 8 slots 80 | ee.andq q3,q1,q0 # apply the mask to all pixels 81 | ee.andq q4,q2,q0 82 | ee.vmul.u16 q3,q3,q5 # multiply source pixels by source alpha 83 | ee.vmul.u16 q4,q4,q6 # multiply dest pixels by dest alpha 84 | ee.vadds.s16 q7,q3,q4 # combine src+dest blue 85 | wsr.sar a11 # set up right shift by 5 86 | ee.vsr.32 q7,q7 # shift blue back to normal position 87 | ee.andq q7,q7,q0 88 | 89 | ee.vldbc.16.ip q0,a8,2 # load the green mask 90 | ee.andq q3,q1,q0 91 | ee.andq q4,q2,q0 92 | ee.vmul.u16 q3,q3,q5 # mult green by src+dest alpha 93 | ee.vmul.u16 q4,q4,q6 # leave the right shift of 5 94 | ee.vadds.s16 q4,q3,q4 # combine src+dest green 95 | ee.andq q4,q4,q0 96 | ee.orq q7,q7,q4 # combine green+blue 97 | 98 | ee.vldbc.16.ip q0,a8,2 # load red mask for arith shift problem 99 | ee.vsr.32 q1,q1 # shift red down 5 bits to not overflow 100 | ee.vsr.32 q2,q2 101 | ee.andq q1,q1,q0 # mask at the new position 102 | ee.andq q2,q2,q0 103 | ee.vmul.u16 q1,q1,q5 # mult red by src+dest alpha 104 | ee.vmul.u16 q2,q2,q6 105 | ee.vadds.s16 q1,q1,q2 # combine src+dst red 106 | ee.vsl.32 q1,q1 # now shift left 5 to fix the position 107 | ee.vldbc.16 q0,a7 108 | ee.andq q1,q1,q0 109 | ee.orq q1,q7,q1 # combine R + G + B 110 | 111 | # we now have 16-bit slots with the results we want 112 | mv.qr q7,q1 # reverse byte order from little to big endian 113 | ee.vunzip.8 q7,q1 114 | ee.vzip.8 q1,q7 115 | 116 | ee.vst.128.ip q1,a4,16 # store 8 finished destination pixels 117 | addi.n a8,a8,-6 # reset color masks pointer 118 | addi.n a5,a5,-8 # decrement pixel count by 8 119 | bnez.n a5,.alphatrans_loop 120 | 121 | # return value of 0 122 | retw.n 123 | 124 | #endif // dsps_fft2r_sc16_aes3_enabled 125 | #endif // __has_include 126 | #endif // ESP32 127 | -------------------------------------------------------------------------------- /src/s3_simd_tint.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2025 BitBank Software, Inc. 5 | // Project started March 7, 2025 6 | // 7 | #if defined (ARDUINO_ARCH_ESP32) && !defined(NO_SIMD) 8 | #if __has_include ("dsps_fft2r_platform.h") 9 | #include "dsps_fft2r_platform.h" 10 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 11 | .text 12 | .align 4 13 | 14 | .type s3_masked_tint_be, @function 15 | .global s3_masked_tint_be 16 | 17 | # Alpha blend a RGB565 color to the masked pixels 18 | # The alpha value can be from 0 to 32 19 | # The pixel count must be a multiple of 8 20 | # Ideally it should take less than 10 clocks per pixel 21 | # This code should be much simpler, but the S3 SIMD limitations 22 | # cause extra thinking and work. For example, the only right shift 23 | # available is 32-bits and arithmetic (copies sign bit) 24 | # this messes up manipulation of 16-bit pixels (see below) 25 | # 26 | # A2 A3 A4 A5 A6 A7 A8 27 | # Call as void s3_masked_tint_be(uint16_t *pDest, uint16_t *Src, uint16_t *pMask, uint16_t tintColor, uint32_t count, uint8_t alpha, const uint16_t *pMasks); 28 | s3_masked_tint_be: 29 | entry a1,32 30 | l32i.n a8,a1,32 # 7th parameter is passed on the stack 31 | 32 | # make the alpha value as a pair of 16-bit values for later 33 | slli a9,a7,16 34 | or a9,a9,a7 # now we have the alpha in 16-bit slots 35 | 36 | ee.movi.32.q q6,a9,0 # set up alpha value in 8 slots of Q6 37 | ee.movi.32.q q6,a9,1 38 | ee.movi.32.q q6,a9,2 39 | ee.movi.32.q q6,a9,3 40 | 41 | movi.n a9,32 # inverted alpha for destination 42 | sub a7,a9,a7 43 | slli a9,a7,16 # prepare it for 16-bit slots 44 | or a9,a9,a7 45 | ee.movi.32.q q5,a9,0 # set up inverted alpha in 8 slots of Q5 46 | ee.movi.32.q q5,a9,1 47 | ee.movi.32.q q5,a9,2 48 | ee.movi.32.q q5,a9,3 49 | 50 | # set up the tint color in q2 51 | slli a9,a5,16 52 | or a9,a9,a5 53 | ee.movi.32.q q2,a9,0 # copy to all slots of Q2 54 | ee.movi.32.q q2,a9,1 55 | ee.movi.32.q q2,a9,2 56 | ee.movi.32.q q2,a9,3 57 | 58 | movi.n a10,0 # prepare shift amounts 59 | movi.n a11,5 60 | 61 | .tint_loop_be: 62 | 63 | wsr.sar a10 # SAR (shift amount register) = 0 64 | 65 | ee.vld.128.ip q1,a3,0 # load 8 RGB565 src pixels into q1 66 | mv.qr q7,q1 # big endian data, need to swap the byte order 67 | ee.vunzip.8 q7,q1 # swap the byte order to be big-endian 68 | ee.vzip.8 q1,q7 69 | 70 | ee.vldbc.16.ip q0,a8,2 # load the blue mask into all 8 slots 71 | ee.andq q3,q1,q0 # apply the mask to all source pixels 72 | ee.andq q4,q2,q0 # apply to the tint color 73 | ee.vmul.u16 q3,q3,q5 # multiply source pixels by source alpha 74 | ee.vmul.u16 q4,q4,q6 # multiply dest pixels by dest alpha 75 | ee.vadds.s16 q7,q3,q4 # combine src+dest blue 76 | wsr.sar a11 # set up right shift by 5 77 | ee.vsr.32 q7,q7 # shift blue back to normal position 78 | ee.andq q7,q7,q0 79 | 80 | ee.vldbc.16.ip q0,a8,2 # load the green mask 81 | ee.andq q3,q1,q0 82 | ee.andq q4,q2,q0 83 | ee.vmul.u16 q3,q3,q5 # mult green by src+dest alpha 84 | ee.vmul.u16 q4,q4,q6 # leave the right shift of 5 85 | ee.vadds.s16 q4,q3,q4 # combine src+dest green 86 | ee.andq q4,q4,q0 87 | ee.orq q7,q7,q4 # combine green+blue 88 | 89 | ee.vldbc.16.ip q0,a8,2 # load red mask for arith shift problem 90 | ee.vsr.32 q1,q1 # shift red down 5 bits to not overflow 91 | ee.vsr.32 q4,q2 92 | ee.andq q1,q1,q0 # mask at the new position 93 | ee.andq q4,q4,q0 94 | ee.vmul.u16 q1,q1,q5 # mult red by src+dest alpha 95 | ee.vmul.u16 q4,q4,q6 96 | ee.vadds.s16 q1,q1,q4 # combine src+dst red 97 | ee.vsl.32 q1,q1 # now shift left 5 to fix the position 98 | ee.vldbc.16 q0,a8 # load final RGB mask for proper position of R 99 | ee.andq q1,q1,q0 100 | ee.orq q1,q7,q1 # combine R + G + B 101 | 102 | # we now have 16-bit slots with the results we want 103 | mv.qr q7,q1 # reverse byte order from little to big endian 104 | ee.vunzip.8 q7,q1 105 | ee.vzip.8 q1,q7 106 | 107 | ee.vld.128.ip q7,a4,16 # load 8 mask pixels into q7 108 | ee.vld.128.ip q3,a3,16 # re-load the 8 source pixels into q3 109 | ee.andq q1,q1,q7 # preserve the processed pixels where mask=FFFF 110 | ee.notq q7,q7 # invert the mask 111 | ee.andq q3,q3,q7 # get source pixels that were not part of the mask 112 | ee.orq q1,q1,q3 # combine for final output 113 | ee.vst.128.ip q1,a2,16 # store 8 finished destination pixels 114 | addi.n a8,a8,-6 # reset color masks pointer 115 | addi.n a6,a6,-8 # decrement pixel count by 8 116 | bnez.n a6,.tint_loop_be 117 | 118 | # return value of 0 119 | retw.n 120 | 121 | #endif // dsps_fft2r_sc16_aes3_enabled 122 | #endif // __has_include 123 | #endif // ESP32 124 | -------------------------------------------------------------------------------- /examples/shared_spi/shared_spi.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Example showing how to share the SPI bus between the display and SD card 3 | // This is a challenging situation because the SPI bus instance handle can be 4 | // reset by the second device to be initialized (e.g. init the LCD, then the SD) 5 | // This is resolved by initilizing the SPI bus outside of both libraries and sharing 6 | // the instance / handle 7 | // 8 | // This example was written for the Waveshare ESP32-C6 1.47" LCD board 9 | // It has a micro SD card slot on the back and a ST7789 172x320 LCD 10 | // on the front. This example plays an animated GIF file read from 11 | // the SD card 12 | // 13 | // N.B. A downside to using the shared SPI bus is that DMA is disabled since the Arduino SPI 14 | // class doesn't support DMA and it is controlling the SPI bus handle 15 | // 16 | #include 17 | #include // https://github.com/bitbank2/AnimatedGIF.git 18 | #include 19 | #include 20 | AnimatedGIF gif; 21 | BB_SPI_LCD lcd; 22 | int iOffX, iOffY; 23 | File file; 24 | uint8_t *pFrameBuffer; 25 | // These GPIOs are for the shared SPI bus 26 | #define SPI_MOSI 6 27 | #define SPI_SCK 7 28 | #define SPI_MISO 5 29 | 30 | // SD card slot 31 | #define SD_CS 4 32 | // LCD 33 | #define TFT_CS 14 34 | #define TFT_DC 15 35 | #define TFT_RESET 21 36 | #define TFT_LED 22 37 | 38 | // Change this to the file you would like to display 39 | #define gif_filename "/thisisfine_307x172.gif" 40 | // 41 | // Draw callback from GIF decoder 42 | // 43 | // called once for each line of the current frame 44 | // MCUs with very little RAM would have to test for disposal methods, transparent pixels 45 | // and translate the 8-bit pixels through the palette to generate the final output. 46 | // The code for MCUs with enough RAM is much simpler because the AnimatedGIF library can 47 | // generate "cooked" pixels that are ready to send to the display 48 | // 49 | void GIFDraw(GIFDRAW *pDraw) 50 | { 51 | if (pDraw->y == 0) { // set the memory window when the first line is rendered 52 | lcd.setAddrWindow(iOffX + pDraw->iX, iOffY + pDraw->iY, pDraw->iWidth, pDraw->iHeight); 53 | } 54 | // For all other lines, just push the pixels to the display 55 | lcd.pushPixels((uint16_t *)pDraw->pPixels, pDraw->iWidth); 56 | } /* GIFDraw() */ 57 | 58 | // Callback functions to access a file on the SD card 59 | void *myOpen(const char *filename, int32_t *size) { 60 | file = SD.open(filename); 61 | if (file) { 62 | *size = file.size(); 63 | } 64 | return (void *)&file; 65 | } 66 | 67 | void myClose(void *pHandle) { 68 | File *f = static_cast(pHandle); 69 | if (f != NULL) 70 | f->close(); 71 | } 72 | int32_t myRead(GIFFILE *pFile, uint8_t *buffer, int32_t length) { 73 | // File *f = (File *)pFile->fHandle; 74 | // return f->read(buffer, length); 75 | int32_t iBytesRead; 76 | iBytesRead = length; 77 | File *f = static_cast(pFile->fHandle); 78 | // Note: If you read a file all the way to the last byte, seek() stops working 79 | if ((pFile->iSize - pFile->iPos) < length) 80 | iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around 81 | if (iBytesRead <= 0) 82 | return 0; 83 | iBytesRead = (int32_t)f->read(buffer, iBytesRead); 84 | pFile->iPos = f->position(); 85 | return iBytesRead; 86 | } 87 | int32_t mySeek(GIFFILE *pFile, int32_t iPosition) 88 | { 89 | // int i = micros(); 90 | File *f = static_cast(pFile->fHandle); 91 | f->seek(iPosition); 92 | pFile->iPos = (int32_t)f->position(); 93 | // i = micros() - i; 94 | // Serial.printf("Seek time = %d us\n", i); 95 | return pFile->iPos; 96 | } /* GIFSeekFile() */ 97 | 98 | void setup() 99 | { 100 | gif.begin(BIG_ENDIAN_PIXELS); 101 | pinMode(SD_CS, OUTPUT); 102 | digitalWrite(SD_CS, 1); // start with the SD card disabled 103 | SPI.end(); // make sure there isn't already an instance using the same pins 104 | SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI, -1); // don't let it control the CS line of either device 105 | lcd.begin(LCD_ST7789_172, FLAGS_NONE, &SPI, TFT_CS, TFT_DC, TFT_RESET, TFT_LED); 106 | lcd.setRotation(90); 107 | lcd.fillScreen(TFT_BLACK); 108 | lcd.setTextColor(TFT_GREEN, TFT_BLACK); 109 | lcd.setFont(FONT_12x16); 110 | lcd.println("Shared SPI Test"); 111 | if (!SD.begin(SD_CS, SPI, 20000000)) { 112 | lcd.println("Card Mount Failed"); 113 | } else { 114 | lcd.println("Card Mount Succeeded"); 115 | } 116 | } /* setup() */ 117 | 118 | void loop() 119 | { 120 | int w, h; 121 | if (gif.open(gif_filename, myOpen, myClose, myRead, mySeek, GIFDraw)) { 122 | w = gif.getCanvasWidth(); 123 | h = gif.getCanvasHeight(); 124 | iOffX = (lcd.width() - w)/2; 125 | iOffY = (lcd.height() - h)/2; 126 | lcd.printf("Canvas size: %dx%d\n", w, h); 127 | lcd.printf("Loop count: %d\n", gif.getLoopCount()); 128 | delay(3000); 129 | lcd.fillScreen(TFT_BLACK); 130 | pFrameBuffer = (uint8_t *)malloc(w*(h+2)); 131 | gif.setDrawType(GIF_DRAW_COOKED); // we want the library to generate ready-made pixels 132 | gif.setFrameBuf(pFrameBuffer); 133 | while (1) { // play forever 134 | while (gif.playFrame(true, NULL)) { 135 | } 136 | gif.reset(); 137 | } // while (1) 138 | } else {// if GIF opened 139 | lcd.println("Error opening GIF"); 140 | while (1) { 141 | vTaskDelay(1); 142 | } 143 | } 144 | } /* loop() */ 145 | -------------------------------------------------------------------------------- /examples/cyd_gif_demo/cyd_gif_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // CYD (Cheap Yellow Display) GIF example 3 | // 4 | #include 5 | #include 6 | #define GIF_NAME earth_128x128 7 | #include "earth_128x128.h" 8 | 9 | uint8_t *pFrameBuffer; 10 | 11 | // Define one of these depending on your device 12 | // CYD_35C = 320x480 3.5" cap touch 13 | // CYD_28C = 240x320 2.8" cap touch 14 | // CYD_28R = 240x320 2.8" resistive touch 15 | // CYD_28R_2USB = 240x320 2.8" resistive touch with both USB-C and USB-microb 16 | // CYD_22C = 240x320 2.2" cap touch 17 | // CYD_128 = 240x240 round 1.28" ESP32-C3 18 | // CYD_MF35 = 320x480 3.5" MakerFabs 16-bit parallel w/cap touch 19 | 20 | #define CYD_MF35 21 | //#define CYD_35C 22 | //#define CYD_128C 23 | //#define CYD_28C 24 | //#define CYD_28R 25 | //#define CYD_28R_2USB 26 | //#define CYD_22C 27 | 28 | // 3.5" 320x480 ILI9488 parallel MakerFabs 3.5" 29 | #ifdef CYD_MF35 30 | #define TOUCH_CAPACITIVE 31 | #define TOUCH_SDA 38 32 | #define TOUCH_SCL 39 33 | #define TOUCH_INT -1 34 | #define TOUCH_RST -1 35 | #define LCD DISPLAY_MAKERFABS_S3 36 | #endif 37 | 38 | // 2.2" 240x320 ST7789 parallel LCD w/cap touch 39 | #ifdef CYD_22C 40 | #define TOUCH_CAPACITIVE 41 | #define TOUCH_SDA 21 42 | #define TOUCH_SCL 22 43 | #define TOUCH_INT -1 44 | #define TOUCH_RST -1 45 | #define LCD DISPLAY_CYD_22C 46 | #endif 47 | 48 | // 3.5" 320x480 LCD w/capacitive touch 49 | #ifdef CYD_28R 50 | #define TOUCH_RESISTIVE 51 | #define LCD DISPLAY_CYD 52 | #endif 53 | // 3.5" 320x480 LCD w/capacitive touch 54 | #ifdef CYD_28R_2USB 55 | #define TOUCH_RESISTIVE 56 | #define LCD DISPLAY_CYD_2USB 57 | #endif 58 | 59 | // 3.5" 320x480 LCD w/capacitive touch 60 | #ifdef CYD_35C 61 | #define TOUCH_CAPACITIVE 62 | #define LCD DISPLAY_CYD_35 63 | #define TOUCH_SDA 33 64 | #define TOUCH_SCL 32 65 | #define TOUCH_INT 21 66 | #define TOUCH_RST 25 67 | #endif 68 | 69 | #ifdef CYD_28C 70 | // 2.8" ESP32 LCD board with the GT911 touch controller 71 | #define TOUCH_CAPACITIVE 72 | #define TOUCH_SDA 33 73 | #define TOUCH_SCL 32 74 | #define TOUCH_INT 21 75 | #define TOUCH_RST 25 76 | #define LCD DISPLAY_CYD 77 | #endif 78 | 79 | #ifdef CYD_128C 80 | // 1.28" ESP32-C3 round LCD board with the CST816D touch controller 81 | #define TOUCH_CAPACITIVE 82 | #define TOUCH_SDA 4 83 | #define TOUCH_SCL 5 84 | #define TOUCH_INT 0 85 | #define TOUCH_RST 1 86 | #define QWIIC_SDA 21 87 | #define QWIIC_SCL 20 88 | #define LCD DISPLAY_CYD_128 89 | #endif 90 | 91 | #ifdef TOUCH_CAPACITIVE 92 | #include 93 | BBCapTouch bbct; 94 | #endif 95 | 96 | AnimatedGIF gif; 97 | BB_SPI_LCD lcd; 98 | int iOffX, iOffY; 99 | // 100 | // Draw callback from GIF decoder 101 | // 102 | // called once for each line of the current frame 103 | // MCUs with very little RAM would have to test for disposal methods, transparent pixels 104 | // and translate the 8-bit pixels through the palette to generate the final output. 105 | // The code for MCUs with enough RAM is much simpler because the AnimatedGIF library can 106 | // generate "cooked" pixels that are ready to send to the display 107 | // 108 | void GIFDraw(GIFDRAW *pDraw) 109 | { 110 | if (pDraw->y == 0) { // set the memory window when the first line is rendered 111 | lcd.setAddrWindow(iOffX + pDraw->iX, iOffY + pDraw->iY, pDraw->iWidth, pDraw->iHeight); 112 | } 113 | // For all other lines, just push the pixels to the display 114 | lcd.pushPixels((uint16_t *)pDraw->pPixels, pDraw->iWidth, DRAW_TO_LCD | DRAW_WITH_DMA); 115 | } /* GIFDraw() */ 116 | 117 | void setup() { 118 | Serial.begin(115200); 119 | 120 | gif.begin(BIG_ENDIAN_PIXELS); 121 | lcd.begin(LCD); 122 | lcd.fillScreen(TFT_BLACK); 123 | lcd.setTextColor(TFT_GREEN, TFT_BLACK); 124 | lcd.setFont(FONT_12x16); 125 | lcd.setCursor(0, 0); 126 | lcd.println("GIF + Touch Test"); 127 | lcd.println("Touch to pause/unpause"); 128 | #ifdef TOUCH_CAPACITIVE 129 | bbct.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT); 130 | lcd.printf("Touch = cap type %d\n", bbct.sensorType()); 131 | #else 132 | lcd.rtInit(); // GPIO pins are already known to bb_spi_lcd 133 | lcd.println("Touch = resistive"); 134 | #endif 135 | delay(3000); 136 | } /* setup() */ 137 | 138 | void loop() { 139 | int w, h; 140 | 141 | if (gif.open((uint8_t *)GIF_NAME, sizeof(GIF_NAME), GIFDraw)) { 142 | w = gif.getCanvasWidth(); 143 | h = gif.getCanvasHeight(); 144 | Serial.printf("Successfully opened GIF; Canvas size = %d x %d\n", w, h); 145 | pFrameBuffer = (uint8_t *)heap_caps_malloc(w*(h+2), MALLOC_CAP_8BIT); 146 | while (1) { 147 | gif.setDrawType(GIF_DRAW_COOKED); // we want the library to generate ready-made pixels 148 | gif.setFrameBuf(pFrameBuffer); 149 | iOffX = (lcd.width() - w)/2; 150 | iOffY = (lcd.height() - h)/2; 151 | while (gif.playFrame(true, NULL)) { 152 | TOUCHINFO ti; 153 | #ifdef TOUCH_CAPACITIVE 154 | if (bbct.getSamples(&ti) && ti.count >= 1) { // a touch event 155 | delay(500); 156 | bbct.getSamples(&ti); // get release event 157 | do { 158 | bbct.getSamples(&ti); 159 | } while (ti.count == 0); 160 | delay(50); 161 | bbct.getSamples(&ti); // get release event 162 | } 163 | #else 164 | if (lcd.rtReadTouch(&ti) && ti.count >= 1) { // a touch event 165 | delay(500); 166 | do { 167 | lcd.rtReadTouch(&ti); 168 | } while (ti.count == 0); 169 | delay(150); 170 | } 171 | #endif 172 | } 173 | gif.reset(); 174 | } 175 | } // while (1) 176 | } /* loop() */ 177 | -------------------------------------------------------------------------------- /examples/UNO_shield_demo/UNO_shield_demo.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Demo sketch to show how to use 3 | // the old Arduino UNO LCD shields 4 | // (8-bit parallel ILI9486/ILI9341) 5 | // with the UM TinyS3 or FeatherS3 6 | // written by Larry Bank 7/22/2022 7 | // bitbank@pobox.com 8 | // 9 | #include 10 | #include 11 | //#include "matrix_320x176.h" 12 | //#include "this_is_fine_240x240.h" 13 | #include "homer_car_240x135.h" 14 | 15 | BB_SPI_LCD lcd; 16 | AnimatedGIF gif; 17 | int x_offset, y_offset; 18 | uint16_t *pImage; 19 | 20 | #define BUS_WIDTH 8 21 | 22 | #define ESP32_D1_R32 23 | 24 | #if defined(ESP32_D1_R32) 25 | // (Wemos + clones) D1 R32 UNO Shaped ESP32 26 | #define LCD_WR 4 27 | #define LCD_RD 2 28 | #define LCD_CS 33 29 | #define LCD_DC 15 30 | #define LCD_RST 32 31 | uint8_t u8Pins[BUS_WIDTH] = {12,13,26,25,17,16,27,14}; 32 | 33 | #elif defined(ARDUINO_TINYS3) 34 | // TinyS3 35 | #define LCD_RD -1 36 | #define LCD_WR 21 37 | #define LCD_CS 43 38 | #define LCD_DC 44 39 | #define LCD_RST -1 40 | uint8_t u8Pins[BUS_WIDTH] = {6,2,3,4,5,9,7,8}; // TinyS3 41 | #elif defined(ARDUINO_TINYS2) 42 | // TinyS2 43 | #define LCD_RD -1 44 | #define LCD_WR 18 45 | #define LCD_CS 43 46 | #define LCD_DC 44 47 | #define LCD_RST -1 48 | uint8_t u8Pins[BUS_WIDTH] = {33,5,6,7,17,9,38,8}; // TinyS2 49 | #elif defined(ARDUINO_FEATHERS3) || defined(ARDUINO_FEATHERS2) 50 | // FeatherS3 51 | #define LCD_RD -1 52 | #define LCD_WR 7 53 | #define LCD_CS 38 54 | #define LCD_DC 3 55 | #define LCD_RST 33 56 | #define SD_CS 1 57 | uint8_t u8Pins[BUS_WIDTH] = {10,11,5,6,12,14,18,17}; // FeatherS3 58 | #endif 59 | // 60 | // ** Select the correct LCD type here ** 61 | // The 240x320 2.x" shields use the ILI9341 62 | // and the 320x480 3.x" shields use the ILI9486 63 | // Some have variations which require inverting 64 | // the colors or the X/Y direction, so enable those 65 | // flags if needed 66 | //#define LCD_TYPE LCD_ILI9486 67 | #define LCD_TYPE LCD_ILI9341 68 | #define LCD_FLAGS FLAGS_NONE 69 | //#define LCD_FLAGS (FLAGS_SWAP_RB | FLAGS_FLIPX) 70 | // The backlight is wired permanently on 71 | #define LCD_BKLT -1 72 | 73 | // Draw a line of image directly on the LCD 74 | void GIFDraw(GIFDRAW *pDraw) 75 | { 76 | uint8_t c, *s, ucTransparent; 77 | uint16_t *d, *usPalette; 78 | int x, iWidth, iHeight; 79 | 80 | if (pDraw->y == 0) { // first line, allocate a backing buffer 81 | iWidth = pDraw->iWidth; iHeight = pDraw->iHeight; 82 | if (iWidth > lcd.width()) iWidth = lcd.width(); 83 | if (iHeight > lcd.height()) iHeight = lcd.height(); 84 | // lcd.setAddrWindow(x_offset+pDraw->iX, y_offset+pDraw->iY, iWidth, iHeight); 85 | // Serial.printf("frame: %d, %d, %d, %d\n", pDraw->iX, pDraw->iY, pDraw->iWidth, pDraw->iHeight); 86 | } 87 | iWidth = pDraw->iWidth; 88 | if (iWidth > lcd.width()) 89 | iWidth = lcd.width(); 90 | usPalette = pDraw->pPalette; 91 | 92 | s = pDraw->pPixels; 93 | d = &pImage[(pDraw->y + pDraw->iY) * pDraw->iWidth]; 94 | if (pDraw->ucDisposalMethod == 2) // restore to background color 95 | { 96 | memset(d, 0, pDraw->iWidth * 2); // set background color to black 97 | } 98 | ucTransparent = pDraw->ucTransparent; 99 | for (x=0; xy + pDraw->iY < lcd.height()) { 106 | lcd.setAddrWindow(x_offset+pDraw->iX, y_offset+pDraw->iY+pDraw->y, iWidth, 1); 107 | lcd.pushPixels(d, pDraw->iWidth); 108 | } 109 | } /* GIFDraw() */ 110 | 111 | void playGIF(void) 112 | { 113 | int iSize; 114 | // if (gif.open((uint8_t *)homer_car_240x135, sizeof(homer_car_240x135), GIFDraw)) 115 | if (gif.open((uint8_t *)this_is_fine_240x240, sizeof(this_is_fine_240x240), GIFDraw)) 116 | // if (gif.open((uint8_t *)matrix_320x176, sizeof(matrix_320x176), GIFDraw)) 117 | { 118 | x_offset = (lcd.width() - gif.getCanvasWidth())/2; 119 | if (x_offset < 0) x_offset = 0; 120 | y_offset = (lcd.height() - gif.getCanvasHeight())/2; 121 | if (y_offset < 0) y_offset = 0; 122 | iSize = gif.getCanvasWidth() * gif.getCanvasHeight() * 2; 123 | // pImage = (uint16_t *)malloc(iSize); 124 | pImage = (uint16_t *)heap_caps_malloc(iSize, MALLOC_CAP_8BIT); 125 | if (pImage) { 126 | while (gif.playFrame(false, NULL)) 127 | { 128 | } 129 | gif.close(); 130 | free(pImage); 131 | } else { 132 | Serial.printf("Alloc of %d bytes failed\n", iSize); 133 | delay(3000); 134 | } 135 | } // if GIF opened 136 | } /* playGIF() */ 137 | 138 | void setup() { 139 | Serial.begin(115200); 140 | // The parallel LCD function also works on RP2040 and Teensy 4.1 boards 141 | lcd.beginParallel(LCD_TYPE, LCD_FLAGS, LCD_RST, LCD_RD, LCD_WR, LCD_CS, LCD_DC, BUS_WIDTH, u8Pins); 142 | lcd.setRotation(90); 143 | #ifdef PERF_TEST 144 | for (int i=0; i<20; i++) { 145 | lcd.fillScreen(0); 146 | lcd.fillScreen(TFT_WHITE); 147 | lcd.fillScreen(TFT_YELLOW); 148 | lcd.fillScreen(TFT_BLUE); 149 | lcd.fillScreen(TFT_RED); 150 | lcd.fillScreen(TFT_GREEN); 151 | lcd.fillScreen(TFT_CYAN); 152 | lcd.fillScreen(TFT_MAGENTA); 153 | } 154 | #else 155 | lcd.fillScreen(0); 156 | #endif // PERF_TEST 157 | lcd.setFont(FONT_12x16); 158 | lcd.setTextColor(0x7e0,0x0000); 159 | lcd.println("Starting GIF Demo..."); 160 | Serial.printf("Free memory = %d bytes\n", heap_caps_get_free_size(MALLOC_CAP_8BIT)); 161 | gif.begin(GIF_PALETTE_RGB565_BE); // big endian pixels 162 | delay(3000); 163 | lcd.fillScreen(0); 164 | } /* setup() */ 165 | 166 | void loop() { 167 | playGIF(); 168 | } /* loop() */ 169 | -------------------------------------------------------------------------------- /src/s3_simd_blur.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2025 BitBank Software, Inc. 5 | // Project started March 21, 2025 6 | // 7 | #if defined (ARDUINO_ARCH_ESP32) && !defined(NO_SIMD) 8 | #if __has_include ("dsps_fft2r_platform.h") 9 | #include "dsps_fft2r_platform.h" 10 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 11 | .text 12 | .align 4 13 | 14 | .type s3_blur_be, @function 15 | .global s3_blur_be 16 | 17 | # Blur a row of RGB565 big-endian pixels with a 3x3 filter 18 | # The pixel count must be a multiple of 4 19 | # A2 A3 A4 A5 20 | # Call as void s3_blur_be(uint16_t *pSrc, uint16_t *pDest, uint32_t count, uint32_t pitch, uint32_t *pMasks); 21 | 22 | s3_blur_be: 23 | entry a1,16 24 | 25 | # load the pixel mask into Q0 and the rounding value into Q1 26 | ee.vldbc.32.ip q0,a6,4 27 | ee.vldbc.32.ip q1,a6,0 28 | 29 | movi.n a11,0 30 | sub a11,a11,a5 # negative pitch value in a11 31 | movi.n a6,8 32 | movi.n a7,1 33 | movi.n a8,2 34 | movi.n a9,4 # prepare shift amounts 35 | movi.n a10,16 36 | 37 | .blur_loop_be: 38 | 39 | wsr.sar a7 # SAR (shift amount register) = 1 40 | wur.sar_byte a9 # SAR_BYTE register set to 4 bytes of shift 41 | 42 | add a2,a2,a11 # move source pointer to line above 43 | ee.vld.l.64.ip q2,a2,8 # load 8 RGB565 src (line above) pixels into q2 44 | ee.vld.h.64.ip q2,a2,-8 # do it as 2 reads because it will be unaligned 45 | add a2,a2,a5 # reset to current line 46 | mv.qr q7,q2 # big endian data, need to swap the byte order 47 | ee.vunzip.8 q7,q2 # swap the byte order to be big-endian 48 | ee.vzip.8 q2,q7 49 | 50 | mv.qr q3,q2 51 | # prepare top left pixel 52 | ee.vzip.16 q2,q3 # double the RGB565 pixels in preparation 53 | ee.andq q2,q2,q0 # mask 8 doubled pixels with 07e0f81f 54 | ee.andq q3,q3,q0 55 | mv.qr q4,q2 # top left pixel starts sum in Q4 56 | 57 | # top center pixel 58 | ee.src.q q7,q2,q3 # shift the register pair down by 1 double pixel 59 | ee.vsl.32 q7,q7 # times 2 60 | ee.vadds.s32 q4,q4,q7 # add to sum 61 | 62 | # top right pixel 63 | wur.sar_byte a6 # set shift to 8 bytes 64 | ee.src.q q7,q2,q3 # shift down another pixel 65 | ee.vadds.s32 q4,q4,q7 # add to sum 66 | 67 | # middle row 68 | ee.vld.l.64.ip q2,a2,8 # load 8 RGB565 src (current line) pixels into q2 69 | ee.vld.h.64.ip q2,a2,-8 # do it as 2 reads because it will be unaligned 70 | mv.qr q7,q2 # big endian data, need to swap the byte order 71 | ee.vunzip.8 q7,q2 # swap the byte order to be big-endian 72 | ee.vzip.8 q2,q7 73 | 74 | mv.qr q3,q2 75 | # prepare middle left pixel 76 | ee.vzip.16 q2,q3 # double the RGB565 pixels in preparation 77 | ee.andq q2,q2,q0 78 | ee.andq q3,q3,q0 # mask 8 doubled pixels with 07e0f81f 79 | ee.vsl.32 q7,q2 # times 2 80 | ee.vadds.s32 q4,q4,q7 # add to sum 81 | 82 | # center (current) pixel 83 | wsr.sar a8 # shift amount = 2 84 | wur.sar_byte a9 # shift byte amount = 4 85 | ee.src.q q7,q2,q3 86 | ee.vsl.32 q7,q7 # times 4 87 | ee.vadds.s32 q4,q4,q7 # add to sum 88 | 89 | # middle right pixel 90 | wur.sar_byte a6 # shift byte amount = 8 91 | wsr.sar a7 # shift amount = 1 92 | ee.src.q q7,q2,q3 93 | ee.vsl.32 q7,q7 # times 2 94 | ee.vadds.s32 q4,q4,q7 # add to sum 95 | 96 | # bottom row 97 | add a2,a2,a5 98 | ee.vld.l.64.ip q2,a2,8 # load 8 RGB565 src (line below) pixels into q2 99 | ee.vld.h.64.ip q2,a2,0 # do it as 2 reads because it will be unaligned 100 | add a2,a2,a11 # a2 points to current row again 101 | mv.qr q7,q2 # big endian data, need to swap the byte order 102 | ee.vunzip.8 q7,q2 # swap the byte order to be big-endian 103 | ee.vzip.8 q2,q7 104 | 105 | mv.qr q3,q2 106 | # prepare bottom left pixel 107 | ee.vzip.16 q2,q3 # double the RGB565 pixels in preparation 108 | ee.andq q2,q2,q0 109 | ee.andq q3,q3,q0 # mask 8 doubled pixels with 07e0f81f 110 | ee.vadds.s32 q4,q4,q2 # add to sum 111 | 112 | # bottom middle pixel 113 | wsr.sar a7 # shift amount = 1 114 | wur.sar_byte a9 # shift byte amount = 4 115 | ee.src.q q7,q2,q3 116 | ee.vsl.32 q7,q7 # times 2 117 | ee.vadds.s32 q4,q4,q7 # add to sum 118 | 119 | # bottom right pixel 120 | wur.sar_byte a6 # set shift to 8 bytes 121 | ee.src.q q7,q2,q3 # shift down another pixel 122 | ee.vadds.s32 q4,q4,q7 # add to sum 123 | 124 | ee.vadds.s32 q4,q4,q1 # add rounding factor 125 | wsr.sar a9 # shift amount to 4 126 | ee.vsr.32 q4,q4 # divide by 16 127 | ee.andq q4,q4,q0 # mask back to 07e0f81f 128 | wsr.sar a10 # shift amount to 16 129 | ee.vsr.32 q5,q4 # shift down upper half 130 | ee.orq q4,q4,q5 # combine upper and lower halves 131 | mv.qr q7,q4 132 | ee.vunzip.16 q7,q4 # get even 16-bit values 133 | 134 | mv.qr q2,q7 # big endian data, need to swap the byte order 135 | ee.vunzip.8 q7,q2 # swap the byte order to be big-endian 136 | ee.vzip.8 q2,q7 137 | 138 | ee.movi.32.a q2,a12,0 # output will always be unaligned, so store 139 | s32i a12,a3,0 # using 32-bit registers 140 | ee.movi.32.a q2,a12,1 141 | s32i a12,a3,4 142 | addi.n a3,a3,8 143 | 144 | addi.n a4,a4,-4 # decrement pixel count by 4 145 | bnez.n a4,.blur_loop_be 146 | 147 | # return value of 0 148 | retw.n 149 | 150 | #endif // dsps_fft2r_sc16_aes3_enabled 151 | #endif // __has_include 152 | #endif // ESP32 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bb_spi_lcd (BitBank SPI Color LCD/OLED library)
2 | Project started 5/15/2017
3 | Copyright (c) 2017-2019 BitBank Software, Inc.
4 | Written by Larry Bank
5 | bitbank@pobox.com
6 |
7 | ![bb_spi_lcd](/demo.jpg?raw=true "bb_spi_lcd") 8 |
9 | The purpose of this code is to easily control color OLED/LCD 10 | displays with a rich set of functions. The code can be built as 11 | both an Arduino and Linux library. For Arduino, there are a wide variety 12 | of target platforms and some don't have enough RAM to support every feature. 13 | Specifically, AVR (Uno/Nano/Mega) don't have enough RAM to have 14 | a back buffer. Without the back buffer, only text, pixel, rectangle and line drawing 15 | are possible. With a back buffer, a host of other functions become available 16 | such as ellipses, translucent/transparent rotating bitmaps, and transparent text.
17 | 18 | ## Features 19 | - Supports the most popular display controllers (SSD1351, ST7735, ST7789, ILI9225, ILI9341, ILI9342, HX8357, ILI9468)
20 | - 5 built in font sizes (6x8, 8x8, 12x16, 16x16, 16x32) 21 | - Supports display modes: 0/90/180/270 degree rotated, inverted, BGR or RGB color order
22 | - Direct display drawing, backbuffer (RAM) drawing or both simultaneously
23 | - Display object structure (SPILCD) allows multiple simultaenous displays of different types to be controlled by a single MCU
24 | - Bit Bang option allows controlling SPI displays on any GPIO pins
25 | - Communication callback functions allows controlling parallel and custom connected displays
26 | - DMA on SAMD21, SAMD51 and ESP32 targets
27 | - Compiles on Arduino and Linux (e.g. Raspberry Pi) 28 | - Optimized primitives for text, lines, rectangles, ellipses and bitmap drawing
29 | - Fast (50x) drawing of Adafruit format custom fonts with optional blanking mode to erase old data without flickering
30 | - 50% scaled drawing of Adafruit_GFX fonts with antialiasing 31 | - Load and display 4, 8 and 16-bit Windows BMP files (including RLE)
32 | - Deferred rendering allows quickly preparing a back buffer, then displaying it
33 | - Callbacks allow working with non-SPI displays (e.g. 8/16-bit parallel)
34 | - Named display configurations of popular products (e.g. M5Stack Core2) 35 |
36 | 37 | ## Named Displays 38 | The following displays are supported through a named constant. Their GPIO connections and (special) initialization sequence are built-in to bb_spi_lcd so that using them is as simple as: 39 | lcd.begin(DISPLAY_CYD);
40 | Special init sequences such as those required for the M5Stack products are included too (PMIC setup). This means that you no longer need to use the M5 unified libraries to work with their products. 41 | 42 | | Name | Description | 43 | | --- | --- | 44 | | `DISPLAY_PYBADGE_M4` | Adafruit's EdgeBadge / PyBadge | 45 | | `DISPLAY_WIO_TERMINAL` | The original Seeed Studio SAMD51 Wio | 46 | | `DISPLAY_TEENSY_ILI9341` | Teensy 4.x LCD shield | 47 | | `DISPLAY_LOLIN_S3_MINI_PRO` | Small white PCB with 0.85" 128x128 LCD | 48 | | `DISPLAY_M5STACK_STICKC` | The original Stick-C with 80x160 ST7735 | 49 | | `DISPLAY_M5STACK_STICKCPLUS` | The newer Stick-C with 135x240 ST7789 | 50 | | `DISPLAY_M5STACK_CORE2` | The M5Stack ESP32 Core2 w/320x240 ILI9342 | 51 | | `DISPLAY_M5STACK_CORES3` | The ESP32-S3 w/320x240 ILI9342 | 52 | | `DISPLAY_T_DONGLE_S3` | LilyGo ESP32-S3 USB dongle with 80x160 ST7735 | 53 | | `DISPLAY_T_DISPLAY_S3` | LilyGo ESP32-S3 1.91" 170x320 ST7789 | 54 | | `DISPLAY_T_DISPLAY_S3_PRO` | LilyGo ESP32-S3 2.23" 222x480 ST7796 | 55 | | `DISPLAY_T_DISPLAY_S3_LONG` | LilyGo ESP32-S3 3.4" 180x640 | 56 | | `DISPLAY_T_DISPLAY_S3_AMOLED` | LilyGo ESP32-S3 1.9" 536x240 AMOLED | 57 | | `DISPLAY_T_DISPLAY_S3_AMOLED_164` | LilyGo ESP32-S3 1.64" 280x456 AMOLED | 58 | | `DISPLAY_T_DISPLAY` | LilyGo original ESP32 135x240 ST7789 | 59 | | `DISPLAY_T_QT` | LilyGo ESP32-S3 w/0.85" 128x128 | 60 | | `DISPLAY_T_QT_C6` | LilyGo ESP32-C6 w/0.85" 128x128 | 61 | | `DISPLAY_T_TRACK` | LilyGo ESP32-S3 126x294 AMOLED | 62 | | `DISPLAY_TUFTY2040` | Pimoroni RP2040 w/2.4" parallel 240x320 ST7789 | 63 | | `DISPLAY_MAKERFABS_S3` | MakerFabs 3.5" ESP32-S3 w/3.5" 320x480 ILI9488 16-bit parallel | 64 | | `DISPLAY_M5STACK_ATOMS3` | M5Stack ESP32-S3 Atom w/0.85" 128x128 | 65 | | `DISPLAY_WT32_SC01_PLUS` | ESP32-S3 w/3.5" 320x480 ST7796 8-bit parallel | 66 | | `DISPLAY_CYD` | The original ESP32-2432S28R Cheap Yellow Display | 67 | | `DISPLAY_CYD_2USB` | The updated 2.8" CYD with 2 USB ports | 68 | | `DISPLAY_CYD_128` | ESP32-C3 1.28" round 240x240 | 69 | | `DISPLAY_CYD_28C` | ESP32 2.8" 240x320 w/capacitive touch | 70 | | `DISPLAY_CYD_24R` | ESP32 2.4" w/resistive touch | 71 | | `DISPLAY_CYD_24C` | ESP32 2.4" w/capacitive touch | 72 | | `DISPLAY_CYD_35` | ESP32 w/ILI9488 SPI | 73 | | `DISPLAY_CYD_35R` | ESP32 w/ILI9488 SPI and resistive touch | 74 | | `DISPLAY_CYD_22C` | ESP32 w/8-bit parallel ST7789 and capactive touch | 75 | | `DISPLAY_CYD_543` | JC4827W543 4.3" ESP32-S3 w/QSPI 480x270 | 76 | | `DISPLAY_CYD_535` | JC3248W535 3.5" ESP32-S3 w/320x480 | 77 | | `DISPLAY_CYD_518` | 1.8" ESP32-S3 round 360x360 QSPI | 78 | | `DISPLAY_CYD_700` | 7.0" ESP32-S3 800x480 ST7262 RGB panel | 79 | | `DISPLAY_CYD_8048` | 4.3 and 5.5" 800x480 ESP32-S3 RGB panel | 80 | | `DISPLAY_CYD_4848` | MakerFabs 4" 480x480 ESP32-S3 RGB panel | 81 | | `DISPLAY_CYD_XIAO_ROUND` | Seeed Studio round LCD shield 240x240 | 82 | | `DISPLAY_CYD_P4_1024x600` | Guition JC1060P470C ESP32-P4 1024x600 MIPI | 83 | | `DISPLAY_WS_AMOLED_18` | Waveshare ESP32-S3 AMOLED Touch 1.8" | 84 | | `DISPLAY_WS_ROUND_146` | Waveshare ESP32-S3 round 1.46" 412x412 | 85 | | `DISPLAY_WS_AMOLED_241` | Waveshare ESP32-S3 2.41" 600x450 AMMOLED | 86 | | `DISPLAY_WS_LCD_169` | Waveshare ESP32-S3 1.69 240x280 ST7789 | 87 | | `DISPLAY_UM_480x480` | UnexpectedMaker ESP32-S3 4" 480x480 RGB Panel | 88 | 89 | If you find this code useful, please consider sponsoring me here on Github or sending a donation to support my work 90 | 91 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SR4F44J2UR8S4) 92 | 93 | -------------------------------------------------------------------------------- /src/s3_simd_tile53.S: -------------------------------------------------------------------------------- 1 | // 2 | // ESP32-S3 SIMD optimized code 3 | // Written by Larry Bank 4 | // Copyright (c) 2024 BitBank Software, Inc. 5 | // Project started March 11, 2024 6 | // 7 | // Draw a 24x24 RGB565 tile scaled to 40x40 8 | // The main purpose of this function is for GameBoy emulation 9 | // Since the original display is 160x144, this function allows it to be 10 | // stretched 166% larger (266x240). Not a perfect fit for 320x240, but better 11 | // Each group of 3x3 pixels becomes a group of 5x5 pixels by averaging the pixels 12 | // 13 | // +-+-+-+ becomes +----+----+----+----+----+ 14 | // |A|B|C| |A |ab |B |bc |C | 15 | // +-+-+-+ +----+----+----+----+----+ 16 | // |D|E|F| |ad |abde|be |becf|cf | 17 | // +-+-+-+ +----+----+----+----+----+ 18 | // |G|H|I| |D |de |E |ef |F | 19 | // +-+-+-+ +----+----+----+----+----+ 20 | // |dg |dgeh|eh |ehfi|fi | 21 | // +----+----+----+----+----+ 22 | // |G |gh |H |hi |I | 23 | // +----+----+----+----+----+ 24 | // 25 | // The x/y coordinates will be scaled as well 26 | // 27 | #ifdef ARDUINO_ARCH_ESP32 28 | 29 | #include "dsps_fft2r_platform.h" 30 | #if (dsps_fft2r_sc16_aes3_enabled == 1) 31 | .text 32 | .align 4 33 | // A2 A3 A4 A5 34 | // Call as void s3_tile53(uint8_t *pTile, uint8_t *pDest ,int iSrcPitch, uint16_t *pConsts); 35 | .global s3_tile53 36 | .type s3_tile53,@function 37 | 38 | s3_tile53: 39 | # no idea what this frequency keyword does 40 | # .frequency 1.000 0.000 41 | entry a1,16 42 | ee.xorq q4,q4,q4 # load Q4 with 0's 43 | ee.vldbc.16.ip q3,a5,2 # get constant 0xf7de as 16-bits in all 128 bits of q3 44 | add.n a6,a2,a4 # point to second source line with A6 45 | add.n a7,a6,a4 # point to third source line with A7 46 | .tile53_top: 47 | ee.vld.128.ip q0,a2,0 # load 8 source pixels 48 | ee.vld.l.64.ip q1,a3,0 # load 8 Cb values into Q1 49 | ee.vld.l.64.ip q2,a4,0 # load 8 Cr values into Q2 50 | ee.vzip.8 q0,q4 # expand 8-bit Y data to 16-bits 51 | ee.xorq q4,q4,q4 # need to reset to 0's 52 | ee.vzip.8 q1,q4 # expand 8-bit Cb data to 16-bits 53 | ee.xorq q4,q4,q4 54 | ee.vzip.8 q2,q4 # expand 8-bit Cr data to 16-bits 55 | 56 | ee.vsubs.s16 q1,q1,q3 # subtract 0x0080 from Cb's 57 | ee.vsubs.s16 q2,q2,q3 # subtract 0x0080 from Cr's 58 | ee.vldbc.16.ip q3,a6,2 # get constant 1.77200 as 16-bits in all 128 bits of q3 59 | movi.n a2,6 # load the shift register with 6 60 | wsr.sar a2 # put it in the SAR (shift amount register) 61 | ee.vmul.s16 q6,q1,q3 # (Cb *= 1.77200) >> 6 62 | ee.vadds.s16 q6,q6,q0 # Cb += y (8 blue pixels in q6) 63 | ee.vldbc.16.ip q3,a6,2 # get constant 1.402 as 16-bits in all 128 bits of q3 64 | ee.vmul.s16 q7,q2,q3 # (Cr *= 1.402) >> 6 65 | ee.vadds.s16 q7,q7,q0 # Cr += y (8 red pixels in q7) 66 | ee.vldbc.16.ip q3,a6,2 # get constant 0.34414 as 16-bits in all 128 bits of q3 67 | movi.n a2,0 # load the shift register with 0 68 | wsr.sar a2 # put it in the SAR (shift amount register) 69 | ee.vmul.s16 q4,q1,q3 # (Cb * 0.34414) >> 0 70 | ee.vldbc.16.ip q3,a6,2 # get constant 0.71414 as 16-bits in all 128 bits of q3 71 | ee.vmul.s16 q3,q2,q3 # (Cr * 0.71414) >> 0 72 | ee.vadds.s16 q3,q3,q4 # (Cb * 0.34414) + (Cr * 0.71414) 73 | ee.vldbc.16.ip q4,a6,2 # get constant 1 (so we can do a 16-bit shift) 74 | movi.n a2,6 # load the shift register with 6 75 | wsr.sar a2 # put it in the SAR (shift amount register) 76 | ee.vmul.s16 q3,q3,q4 # shift right by 6 77 | ee.vsubs.s16 q3,q0,q3 # Y - ((Cb * 0.34414) + (Cr * 0.71414)) = green in Q3 78 | // saturate to 8 bits 79 | ee.xorq q0,q0,q0 80 | ee.vmax.s16 q3,q3,q0 81 | ee.vmax.s16 q6,q6,q0 82 | ee.vmax.s16 q7,q7,q0 83 | ee.vcmp.eq.s16 q1,q1,q1 # create 255 84 | ee.vzip.8 q1,q0 85 | ee.vmin.s16 q3,q3,q1 # clamp to 255 86 | ee.vmin.s16 q6,q6,q1 87 | ee.vmin.s16 q7,q7,q1 88 | // Now we have RGB888, is that the output pixel type? 89 | beqi a7,2,.rgb8888_output 90 | // either RGB565 LE or BE from here 91 | movi.n a2,3 # load the shift register with 3 (for blue and red) 92 | wsr.sar a2 # put it in the SAR (shift amount register) 93 | ee.vmul.s16 q6,q4,q6 # shift blue right by 3 94 | ee.vmul.s16 q7,q4,q7 # shift red right by 3 95 | movi.n a2,2 # load the shift register with 2 (for green) 96 | wsr.sar a2 # put it in the SAR (shift amount register) 97 | ee.vmul.s16 q3,q4,q3 # shift green right by 2 98 | // now combine to form RGB565 pixels 99 | movi.n a2,0 100 | wsr.sar a2 # no shift after multiply 101 | ee.vldbc.16.ip q4,a6,2 # get constant value 32 (to shift green left by 5 bits) 102 | ee.vldbc.16.ip q5,a6,2 # get constant value 2048 (to shift red left by 11 bits) 103 | ee.vmul.s16 q3,q4,q3 # shift green left by 5 104 | ee.vmul.s16 q7,q5,q7 # shift red left by 11 105 | ee.orq q6,q6,q3 # combine blue + green 106 | ee.orq q6,q6,q7 # combine blue + green + red 107 | mv.qr q5,q6 # in case we're generating little endian output 108 | beqi a7,0,.rgb565_exit # RGB565 little endian? 109 | ee.vunzip.8 q6,q5 # swap the byte order to be big-endian 110 | ee.vzip.8 q5,q6 111 | .rgb565_exit: 112 | ee.vst.128.ip q5,a5,0 # store the 8 RGB565 pixels 113 | retw.n 114 | // Create RGBA (32-bit) pixels 115 | .rgb8888_output: 116 | movi.n a2,8 # shift 8 bits 117 | wsr.sar a2 118 | ee.vsl.32 q3,q3 # shift green over 8 bits 119 | ee.orq q7,q7,q3 # combine red and green 120 | ee.vcmp.eq.s16 q1,q1,q1 # create FFs 121 | ee.xorq q2,q2,q2 # create 00s 122 | ee.vzip.8 q2,q1 # create FF00 for Alpha 123 | ee.orq q2,q2,q6 # combine blue + alpha 124 | ee.vzip.16 q7,q2 # create RGB8888 pixels 125 | ee.vst.128.ip q7,a5,16 # store 8 x RGB8888 pixels = 32 bytes 126 | ee.vst.128.ip q2,a5,0 127 | retw.n # done 128 | #endif // dsps_fft2r_sc16_aes3_enabled 129 | #endif // ESP32 130 | -------------------------------------------------------------------------------- /examples/big_framebuffer/big_framebuffer.ino: -------------------------------------------------------------------------------- 1 | // 2 | // Example showing how to work with extra large framebuffers and 'sprites' 3 | // This is mostly aimed at devices like the ESP32 since it has the 4 | // ability to use external PSRAM with a L1 cache to simulate an 5 | // extended address space 6 | // This sketch is written specifically for the Waveshare ESP32-S3 7 | // AMOLED touch 1.8" device. It has a 368x448 color OLED display. 8 | // Allocating a framebuffer for it will require 328K of RAM. The 9 | // ESP32 cannot allocate a block that large within its internal 10 | // static RAM, so we must use PSRAM. 11 | // 12 | // Cause a compilation error if the target is an ESP32 and PSRAM is not enabled 13 | #if defined(ARDUINO_ARCH_ESP32) && !defined(BOARD_HAS_PSRAM) 14 | #error "Please enable PSRAM support" 15 | #endif 16 | 17 | #include 18 | #include "Roboto_Black_40.h" 19 | #include "bart_head.h" 20 | BB_SPI_LCD lcd, sprite, sprite2; 21 | #define SPRITE_SIZE 128 22 | #define BALL_COUNT 10 23 | typedef struct ballstruct 24 | { 25 | int16_t x, y; 26 | int16_t dx, dy; 27 | } BALLSTRUCT; 28 | 29 | BALLSTRUCT balls[BALL_COUNT]; 30 | const uint16_t colors[8] = {TFT_WHITE, TFT_YELLOW, TFT_RED, TFT_BLUE, TFT_GREEN, TFT_MAGENTA, TFT_CYAN, TFT_BLACK}; 31 | void setup() 32 | { 33 | long lTime; 34 | int x, y; 35 | char szTemp[32]; 36 | Serial.begin(115200); 37 | delay(3000); 38 | // 39 | // The default flags for all operations is DRAW_TO_LCD | DRAW_TO_RAM 40 | // This means that all drawing will be sent to the physical LCD and 41 | // to the framebuffer (if it is allocated) 42 | // All drawing methods are overloaded with the flags as the last parameter 43 | // so that the default value is what most people will want, but it can be 44 | // overridden by specifying a different value 45 | // 46 | // lcd.begin(LCD_ILI9341, 0, 40000000, 10, 9, -1, -1, -1, MOSI, SCK); 47 | lcd.begin(DISPLAY_CYD_4848/*DISPLAY_WS_AMOLED_18*/); // initialize the display 48 | lcd.fillScreen(TFT_BLACK); 49 | // if (!lcd.allocBuffer()) { // unable to allocate a buffer 50 | // lcd.setTextColor(TFT_RED); 51 | // lcd.print("allocBuffer() failed!"); 52 | // while (1) {}; // stop 53 | // } 54 | lcd.setFont(FONT_12x16); 55 | // A good example of how direct memory manipulation can save time in preparing graphics 56 | // Let's do a bunch of lines to the display and then into memory 57 | lcd.setTextColor(TFT_GREEN); 58 | lcd.setCursor(0,lcd.height()/2); 59 | lcd.println("Direct Draw Test"); 60 | lTime = millis(); 61 | for (int x=0; x 10 | #include "soc/soc_caps.h" 11 | 12 | #if SOC_MIPI_DSI_SUPPORTED 13 | #include "esp_lcd_panel_vendor.h" 14 | #include "esp_lcd_mipi_dsi.h" 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | /** 21 | * @brief LCD panel initialization commands. 22 | * 23 | */ 24 | typedef struct { 25 | int cmd; /* 8 | 9 | uint8_t ucBombMask[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 10 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 11 | 0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x04,0x21,0x00,0x00, 12 | 0x00,0x00,0x00,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x50,0x00,0x00, 13 | 0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x86,0x38,0xa8,0x00, 14 | 0x00,0x00,0x00,0x01,0x01,0xc0,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x80,0x00, 15 | 0x00,0x00,0x00,0x01,0x00,0x24,0x40,0x00,0x00,0x00,0x00,0x0f,0xe0,0x40,0x20,0x00, 16 | 0x00,0x00,0x00,0x0f,0xe0,0x84,0x10,0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x00,0x00, 17 | 0x00,0x00,0x00,0x3f,0xf8,0x04,0x00,0x00,0x00,0x00,0x00,0xff,0xfe,0x00,0x00,0x00, 18 | 0x00,0x00,0x00,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0x00,0x00,0x00, 19 | 0x00,0x00,0x01,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x03,0xdf,0xff,0x80,0x00,0x00, 20 | 0x00,0x00,0x03,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x03,0xbf,0xff,0x80,0x00,0x00, 21 | 0x00,0x00,0x03,0xbf,0xff,0x80,0x00,0x00,0x00,0x00,0x03,0xbf,0xff,0x80,0x00,0x00, 22 | 0x00,0x00,0x03,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x03,0xdf,0xff,0x80,0x00,0x00, 23 | 0x00,0x00,0x01,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x01,0xf7,0xff,0x00,0x00,0x00, 24 | 0x00,0x00,0x00,0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xfe,0x00,0x00,0x00, 25 | 0x00,0x00,0x00,0x3f,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x00,0x00, 26 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 27 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 28 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 29 | 0x00,0x00}; 30 | 31 | SPILCD lcd; 32 | 33 | void setup() { 34 | //int spilcdInit(int iLCDType, int bFlipRGB, int bInvert, int bFlipped, int32_t iSPIFreq, int iCSPin, int iDCPin, int iResetPin, int iLEDPin, int iMISOPin, int iMOSIPin, int iCLKPin); 35 | #ifdef __AVR__ 36 | spilcdInit(&lcd, LCD_ST7735S, 1, 0, 1, F_CPU/2, 10, 8, 9, A0, -1, -1, -1); // Prototype pin numbering 37 | #else 38 | spilcdInit(&lcd, LCD_ST7789_135, FLAGS_NONE, 40000000, 5, 16, -1, 4, -1, 19, 18); // TTGO T-Display pin numbering, 40Mhz 39 | #endif 40 | spilcdSetOrientation(&lcd, LCD_ORIENTATION_90); 41 | #ifndef __AVR__ 42 | spilcdAllocBackbuffer(&lcd); // can only allocate a back buffer on systems with enough RAM 43 | #endif 44 | spilcdFill(&lcd, 0, DRAW_TO_LCD); 45 | } 46 | 47 | void loop() { 48 | // put your main code here, to run repeatedly: 49 | int i, iFG, x, y, r1, r2; 50 | uint32_t iTime; 51 | char szTemp[32]; 52 | uint16_t usPal[] = {0xf800, 0x07e0, 0x001f, 0x7ff, 0xf81f, 0xffe0, 0xffff, 0x0000}; 53 | 54 | #ifdef __AVR__ 55 | #define WIDTH 160 56 | #define HEIGHT 80 57 | #else 58 | #define WIDTH 240 59 | #define HEIGHT 135 60 | #endif 61 | 62 | static int iLineOffset = 0; 63 | static int iColorOffset = 0; 64 | static int iColorDelta = 1; 65 | static int yLogo = 0; 66 | static int yDelta = 1; 67 | int dx, dy; 68 | 69 | x = y = 0; 70 | dx = dy = 1; 71 | i = 0; // frame 72 | #ifndef __AVR__ 73 | { 74 | uint16_t r, g, b, usColor1, usColor2; 75 | i = 1; 76 | iTime = millis(); 77 | for (x=0; x>5); 108 | b = 0x1f - iColorOffset; 109 | usColor1 = (r<<11) | (g<<5) | b; // start 110 | r = 0x1f - iColorOffset; 111 | g = 0x32 - (((0x17)*iColorOffset)>>5); 112 | b = iColorOffset; 113 | usColor2 = (r<<11) | (g<<5) | b; // end 114 | iColorOffset += iColorDelta; // increase green 115 | if (iColorOffset < 0) 116 | { 117 | iColorOffset = 0; 118 | iColorDelta = -iColorDelta; 119 | } 120 | if (iColorOffset >= 0x1f) 121 | { 122 | iColorOffset = 0x1f; 123 | iColorDelta = -iColorDelta; 124 | } 125 | yLogo += yDelta; 126 | if (yLogo == 0 || yLogo == 155) 127 | yDelta = -yDelta; 128 | 129 | spilcdRectangle(&lcd, 0, 0, WIDTH, HEIGHT, usColor1, usColor2, 1, DRAW_TO_RAM); 130 | spilcdWriteString(&lcd, x,y,(char *)"bb_spi_lcd Library", 0xffff,-1,FONT_8x8, DRAW_TO_RAM); 131 | spilcdWriteString(&lcd, x,y+8,(char *)"by Larry Bank", 0xffff,-1,FONT_8x8, DRAW_TO_RAM); 132 | { 133 | int j; 134 | uint8_t u8Temp[8*40]; // holds the rotated mask 135 | spilcdRotateBitmap((uint8_t *)ucBombMask, u8Temp, 1, 40, 40, 8, 20, 20, i % 360); 136 | j = i % 120; 137 | spilcdDrawPattern(&lcd, u8Temp, 8, j,0,40,40,0x6ff,16); 138 | spilcdDrawPattern(&lcd, u8Temp, 8, 120-j,0,40,40,0xf81f,16); 139 | spilcdDrawPattern(&lcd, u8Temp, 8, j,90,40,40,0x6e0,16); 140 | spilcdDrawPattern(&lcd, u8Temp, 8, 120-j,90,40,40,0x6ff,16); 141 | } 142 | 143 | spilcdShowBuffer(&lcd, 0,0,WIDTH,HEIGHT, DRAW_TO_LCD); 144 | x += dx; y += dy; 145 | if (x == 0) dx = -dx; 146 | else if (x >= WIDTH-145) dx = -dx; 147 | if (y == 0) dy = -dy; 148 | else if (y >= HEIGHT-17) dy = -dy; 149 | i++; 150 | } // for count 151 | } 152 | #else 153 | i = 0; 154 | { 155 | iTime = millis(); 156 | for (x=0; x 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "bb_spi_lcd.h" 16 | #include 17 | #include "../../AnimatedGIF/src/AnimatedGIF.h" 18 | #include "../../AnimatedGIF/src/gif.inl" 19 | #ifdef HAT_ADAFRUIT_PITFT 20 | #define DISPLAY_WIDTH 320 21 | #define DISPLAY_HEIGHT 240 22 | #define LCD_TYPE LCD_ILI9341 23 | 24 | #else // WaveShare 1.3" 240x240 25 | 26 | #define DISPLAY_WIDTH 240 27 | #define DISPLAY_HEIGHT 240 28 | #define LCD_TYPE LCD_ST7789_240 29 | 30 | #endif 31 | 32 | // Common pins 33 | // bb_spi_lcd library uses the header pin number, not the BCM GPIO number 34 | // to reduce confusion 35 | 36 | // GPIO 25 = Pin 22 37 | #define DC_PIN 22 38 | // GPIO 27 = Pin 13 39 | #define RESET_PIN 13 40 | // GPIO 8 = Pin 24 41 | #define CS_PIN 24 42 | // GPIO 24 = Pin 18 43 | #define LED_PIN 18 44 | 45 | SPILCD lcd; 46 | static uint8_t ucBuffer[4096], ucBuffer2[4096]; 47 | GIFIMAGE gif; 48 | uint8_t *pGIFBuf; 49 | 50 | // Draw a line of image directly on the LCD 51 | void GIFDraw(GIFDRAW *pDraw) 52 | { 53 | uint8_t *s; 54 | uint16_t *usPalette, *d; 55 | int x, y, iCanvasWidth, iWidth; 56 | GIFIMAGE *pGIF = (GIFIMAGE *)pDraw->pUser; // access to full gif frame info 57 | 58 | iCanvasWidth = pGIF->iCanvasWidth; // full width of the image 59 | usPalette = pDraw->pPalette; 60 | y = pDraw->iY + pDraw->y; // current line 61 | iWidth = pDraw->iWidth; 62 | s = pDraw->pPixels; 63 | if (pDraw->ucDisposalMethod == 2) // restore to background color 64 | { 65 | for (x=0; xucTransparent) 68 | s[x] = pDraw->ucBackground; 69 | } 70 | pDraw->ucHasTransparency = 0; 71 | } 72 | // Apply the new pixels to the main image 73 | if (pDraw->ucHasTransparency) // if transparency used 74 | { 75 | uint8_t *pEnd, c, ucTransparent = pDraw->ucTransparent; 76 | int x, iCount; 77 | pEnd = s + iWidth; 78 | x = 0; 79 | iCount = 0; // count non-transparent pixels 80 | while(x < iWidth) 81 | { 82 | c = ucTransparent-1; 83 | d = (uint16_t *)&pGIFBuf[y*iCanvasWidth*2 + (x + pDraw->iX) * 2]; 84 | while (c != ucTransparent && s < pEnd) 85 | { 86 | c = *s++; 87 | if (c == ucTransparent) // done, stop 88 | { 89 | s--; // back up to treat it like transparent 90 | } 91 | else // opaque 92 | { 93 | *d++ = usPalette[c]; 94 | iCount++; 95 | } 96 | } // while looking for opaque pixels 97 | if (iCount) // any opaque pixels? 98 | { 99 | x += iCount; 100 | iCount = 0; 101 | } 102 | // no, look for a run of transparent pixels 103 | c = ucTransparent; 104 | while (c == ucTransparent && s < pEnd) 105 | { 106 | c = *s++; 107 | if (c == ucTransparent) 108 | iCount++; 109 | else 110 | s--; 111 | } 112 | if (iCount) 113 | { 114 | x += iCount; // skip these 115 | iCount = 0; 116 | } 117 | } 118 | } 119 | else 120 | { 121 | s = pDraw->pPixels; 122 | d = (uint16_t *)&pGIFBuf[(y * iCanvasWidth * 2) + (pDraw->iX * 2)]; 123 | // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) 124 | for (x=0; x\n"); 170 | return -1; 171 | } 172 | pData = ReadFile(argv[1], &iLen); 173 | if (pData == NULL) { 174 | printf("Something went wrong :(\n"); 175 | return -1; 176 | } 177 | printf("Successfully read file; size = %d bytes\n", iLen); 178 | spilcdSetTXBuffer(ucBuffer, 4096); 179 | // Both LCD hats can handle the max SPI speed of 62.5Mhz 180 | i = spilcdInit(&lcd, LCD_TYPE, FLAGS_NONE, 62500000, CS_PIN, DC_PIN, RESET_PIN, LED_PIN, -1,-1,-1); 181 | if (i == 0) 182 | { 183 | spilcdSetOrientation(&lcd, LCD_ORIENTATION_90); 184 | spilcdFill(&lcd, 0, DRAW_TO_LCD); 185 | if (pData[0] == 'B' && pData[1] == 'M') { 186 | spilcdDrawBMP(&lcd, pData, 0, 0, 0, -1, DRAW_TO_LCD); 187 | } else if (pData[0] == 0xff && pData[1] == 0xd8) { 188 | // JPEG 189 | } else if (pData[1] == 'P' && pData[2] == 'N') { 190 | // PNG 191 | } else if (pData[0] == 'G' && pData[1] == 'I') 192 | { // GIF 193 | int iDelay, rc, y, ty, dy, cx, cy; 194 | pGIFBuf = (uint8_t *)malloc(640 * 240); 195 | GIF_begin(&gif, GIF_PALETTE_RGB565_BE); 196 | rc = GIF_openRAM(&gif, pData, iLen, GIFDraw); 197 | if (rc) { 198 | printf("GIF dimensions: %d x %d\n", gif.iCanvasWidth, gif.iCanvasHeight); 199 | cx = (DISPLAY_WIDTH - gif.iCanvasWidth) /2; 200 | cy = (DISPLAY_HEIGHT - gif.iCanvasHeight) / 2; 201 | while (1) { 202 | while (GIF_playFrame(&gif, &iDelay, (void *)&gif)) { 203 | spilcdSetPosition(&lcd, cx+ gif.iX, cy + gif.iY, gif.iWidth, gif.iHeight, DRAW_TO_LCD); // only draw the delta frame size 204 | ty = 2048 / gif.iWidth; // max lines per 4k write 205 | //printf("number of lines = %d\n", ty); 206 | for (y=0; y ty) dy = ty; // last block? 209 | // Gather the data into a contiguous block 210 | // to write in one shot 211 | for (int i=0; i 4 | #include 5 | #include 6 | #include 7 | #ifdef __AVR__ 8 | #include 9 | #endif 10 | // 11 | // Group5 1-bit image compression library 12 | // Written by Larry Bank 13 | // Copyright (c) 2024 BitBank Software, Inc. 14 | // 15 | // Licensed under the Apache License, Version 2.0 (the "License"); 16 | // you may not use this file except in compliance with the License. 17 | // You may obtain a copy of the License at 18 | // http://www.apache.org/licenses/LICENSE-2.0 19 | // Unless required by applicable law or agreed to in writing, software 20 | // distributed under the License is distributed on an "AS IS" BASIS, 21 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22 | // See the License for the specific language governing permissions and 23 | // limitations under the License. 24 | //=========================================================================== 25 | // 26 | 27 | // The name "Group5" is derived from the CCITT Group4 standard 28 | // This code is based on a lot of the good ideas from CCITT T.6 29 | // for FAX image compression, but modified to work in a very 30 | // constrained environment. The Huffman tables for horizontal 31 | // mode have been replaced with a simple 2-bit flag followed by 32 | // short or long counts of a fixed length. The short codes are 33 | // always 3 bits (run lengths 0-7) and the long codes are the 34 | // number of bits needed to encode the width of the image. 35 | // For example, if a 320 pixel wide image is being compressed, 36 | // the longest horizontal run needed is 320, which requires 9 37 | // bits to encode. The 2 prefix bits have the following meaning: 38 | // 00 = short, short (3+3 bits) 39 | // 01 = short, long (3+N bits) 40 | // 10 = long, short (N+3 bits) 41 | // 11 = long, long (N+N bits) 42 | // The rest of the code works identically to Group4 2D FAX 43 | // 44 | // Caution - this is the maximum number of color changes per line 45 | // The default value is set low to work embedded systems with little RAM 46 | // for font compression, this is plenty since each line of a character should have 47 | // a maximum of 7 color changes 48 | // You can define this in your compiler macros to override the default vlaue 49 | // 50 | #ifndef MAX_IMAGE_FLIPS 51 | #ifdef __AVR__ 52 | #define MAX_IMAGE_FLIPS 32 53 | #else 54 | #define MAX_IMAGE_FLIPS 512 55 | #endif // __AVR__ 56 | #endif 57 | // Horizontal prefix bits 58 | enum { 59 | HORIZ_SHORT_SHORT=0, 60 | HORIZ_SHORT_LONG, 61 | HORIZ_LONG_SHORT, 62 | HORIZ_LONG_LONG 63 | }; 64 | 65 | // Return code for encoder and decoder 66 | enum { 67 | G5_SUCCESS = 0, 68 | G5_INVALID_PARAMETER, 69 | G5_DECODE_ERROR, 70 | G5_UNSUPPORTED_FEATURE, 71 | G5_ENCODE_COMPLETE, 72 | G5_DECODE_COMPLETE, 73 | G5_NOT_INITIALIZED, 74 | G5_DATA_OVERFLOW, 75 | G5_MAX_FLIPS_EXCEEDED 76 | }; 77 | // 78 | // Decoder state 79 | // 80 | typedef struct g5_dec_image_tag 81 | { 82 | int iWidth, iHeight; // image size 83 | int iError; 84 | int y; // last y value drawn 85 | int iVLCSize; 86 | int iHLen; // length of 'long' horizontal codes for this image 87 | int iPitch; // width in bytes of output buffer 88 | uint32_t u32Accum; // fractional scaling accumulator 89 | uint32_t ulBitOff, ulBits; // vlc decode variables 90 | uint8_t *pSrc, *pBuf; // starting & current buffer pointer 91 | int16_t *pCur, *pRef; // current state of current vs reference flips 92 | int16_t CurFlips[MAX_IMAGE_FLIPS]; 93 | int16_t RefFlips[MAX_IMAGE_FLIPS]; 94 | } G5DECIMAGE; 95 | 96 | // Due to unaligned memory causing an exception, we have to do these macros the slow way 97 | #ifdef __AVR__ 98 | // assume PROGMEM as the source of data 99 | inline uint32_t TIFFMOTOLONG(uint8_t *p) 100 | { 101 | uint32_t u32 = pgm_read_dword(p); 102 | return __builtin_bswap32(u32); 103 | } 104 | #else 105 | #define TIFFMOTOLONG(p) (((uint32_t)(*p)<<24UL) + ((uint32_t)(*(p+1))<<16UL) + ((uint32_t)(*(p+2))<<8UL) + (uint32_t)(*(p+3))) 106 | #endif // __AVR__ 107 | 108 | #define TOP_BIT 0x80000000 109 | #define MAX_VALUE 0xffffffff 110 | // Must be a 32-bit target processor 111 | #define REGISTER_WIDTH 32 112 | #define BIGUINT uint32_t 113 | 114 | // 115 | // G5 Encoder 116 | // 117 | 118 | typedef struct g5_buffered_bits 119 | { 120 | unsigned char *pBuf; // buffer pointer 121 | uint32_t ulBits; // buffered bits 122 | uint32_t ulBitOff; // current bit offset 123 | uint32_t ulDataSize; // available data 124 | } G5_BUFFERED_BITS; 125 | 126 | // 127 | // Encoder state 128 | // 129 | typedef struct g5_enc_image_tag 130 | { 131 | int iWidth, iHeight; // image size 132 | int iError; 133 | int y; // last y encoded 134 | int iOutSize; 135 | int iDataSize; // generated output size 136 | uint8_t *pOutBuf; 137 | int16_t *pCur, *pRef; // pointers to swap current and reference lines 138 | G5_BUFFERED_BITS bb; 139 | int16_t CurFlips[MAX_IMAGE_FLIPS]; 140 | int16_t RefFlips[MAX_IMAGE_FLIPS]; 141 | } G5ENCIMAGE; 142 | 143 | // 16-bit marker at the start of a BB_FONT file 144 | // (BitBank FontFile) 145 | #define BB_FONT_MARKER 0xBBFF 146 | #define BB_FONT_MARKER_SMALL 0xBBF2 147 | 148 | // 16-bit marker at the start of a BB_BITMAP file 149 | // (BitBank BitmapFile) 150 | #define BB_BITMAP_MARKER 0xBBBF 151 | 152 | // Font info per large character (glyph) 153 | typedef struct { 154 | uint16_t bitmapOffset; // Offset to compressed bitmap data for this glyph (starting from the end of the BB_GLYPH[] array) 155 | uint16_t width; // bitmap width in pixels 156 | uint16_t xAdvance; // total width in pixels (bitmap + padding) 157 | uint16_t height; // bitmap height in pixels 158 | int16_t xOffset; // left padding to upper left corner 159 | int16_t yOffset; // padding from baseline to upper left corner (usually negative) 160 | } BB_GLYPH; 161 | 162 | // Font info per small character (glyph) 163 | typedef struct bbg_small { 164 | uint16_t bitmapOffset; // Offset to compressed bitmap data for this glyph (starting from the end of the BB_GLYPH[] array) 165 | uint8_t width; // bitmap width in pixels 166 | uint8_t xAdvance; // total width in pixels (bitmap + padding) 167 | uint8_t height; // bitmap height in pixels 168 | int8_t xOffset; // left padding to upper left corner 169 | int8_t yOffset; // padding from baseline to upper left corner (usually negative) 170 | int8_t struct_padding; // pad the structure to 8 bytes to prevent alignment problems on different target compilers/settings 171 | } BB_GLYPH_SMALL; 172 | 173 | // This structure is stored at the beginning of a BB_FONT file 174 | typedef struct { 175 | uint16_t u16Marker; // 16-bit Marker defining a BB_FONT file 176 | uint16_t first; // first char (ASCII value) 177 | uint16_t last; // last char (ASCII value) 178 | uint16_t height; // total height of font 179 | uint32_t rotation; // store this as 32-bits to not have a struct packing problem 180 | BB_GLYPH glyphs[]; // Array of glyphs (one for each char) 181 | } BB_FONT; 182 | 183 | // This structure is stored at the beginning of a BB_FONT_SMALL file 184 | typedef struct { 185 | uint16_t u16Marker; // 16-bit Marker defining a BB_FONT_SMALL file 186 | uint16_t first; // first char (ASCII value) 187 | uint16_t last; // last char (ASCII value) 188 | uint16_t height; // total height of font 189 | uint32_t rotation; // store this as 32-bits to not have a struct packing problem 190 | BB_GLYPH_SMALL glyphs[]; // Array of glyphs (one for each char) 191 | } BB_FONT_SMALL; 192 | 193 | // This structure defines the start of a compressed bitmap file 194 | typedef struct { 195 | uint16_t u16Marker; // 16-bit marker defining a BB_BITMAP file 196 | uint16_t width; 197 | uint16_t height; 198 | uint16_t size; // compressed data size (not including this 8-byte header) 199 | } BB_BITMAP; 200 | 201 | #ifdef __cplusplus 202 | // 203 | // The G5 classes wrap portable C code which does the actual work 204 | // 205 | class G5ENCODER 206 | { 207 | public: 208 | int init(int iWidth, int iHeight, uint8_t *pOut, int iOutSize); 209 | int encodeLine(uint8_t *pPixels); 210 | int size(); 211 | 212 | private: 213 | G5ENCIMAGE _g5enc; 214 | }; 215 | class G5DECODER 216 | { 217 | public: 218 | int init(int iWidth, int iHeight, uint8_t *pData, int iDataSize); 219 | int decodeLine(uint8_t *pOut); 220 | 221 | private: 222 | G5DECIMAGE _g5dec; 223 | }; 224 | #endif // __cplusplus 225 | 226 | #endif // __GROUP5__ 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 BitBank Software, Inc. All rights reserved. 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "[]" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2020 BitBank Software, Inc. 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | 205 | -------------------------------------------------------------------------------- /src/g5enc.inl: -------------------------------------------------------------------------------- 1 | // 2 | // G5 Encoder 3 | // A 1-bpp image encoding library 4 | // 5 | // Written by Larry Bank 6 | // Copyright (c) 2024 BitBank Software, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | //=========================================================================== 18 | // 19 | 20 | #include "Group5.h" 21 | 22 | /* Number of consecutive 1 bits in a byte from MSB to LSB */ 23 | static uint8_t bitcount[256] = 24 | {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0-15 */ 25 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 16-31 */ 26 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 32-47 */ 27 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 48-63 */ 28 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 64-79 */ 29 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 80-95 */ 30 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 96-111 */ 31 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 112-127 */ 32 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 128-143 */ 33 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 144-159 */ 34 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 160-175 */ 35 | 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 176-191 */ 36 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 192-207 */ 37 | 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, /* 208-223 */ 38 | 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, /* 224-239 */ 39 | 4,4,4,4,4,4,4,4,5,5,5,5,6,6,7,8}; /* 240-255 */ 40 | 41 | /* Table of vertical codes for G5 encoding */ 42 | /* code followed by length, starting with v(-3) */ 43 | static const uint8_t vtable[14] = 44 | {3,7, /* V(-3) = 0000011 */ 45 | 3,6, /* V(-2) = 000011 */ 46 | 3,3, /* V(-1) = 011 */ 47 | 1,1, /* V(0) = 1 */ 48 | 2,3, /* V(1) = 010 */ 49 | 2,6, /* V(2) = 000010 */ 50 | 2,7}; /* V(3) = 0000010 */ 51 | 52 | 53 | static void G5ENCInsertCode(G5_BUFFERED_BITS *bb, BIGUINT ulCode, int iLen) 54 | { 55 | if ((bb->ulBitOff + iLen) > REGISTER_WIDTH) { // need to write data 56 | bb->ulBits |= (ulCode >> (bb->ulBitOff + iLen - REGISTER_WIDTH)); // partial bits on first word 57 | *(BIGUINT *)bb->pBuf = __builtin_bswap32(bb->ulBits); 58 | bb->pBuf += sizeof(BIGUINT); 59 | bb->ulBits = ulCode << ((REGISTER_WIDTH*2) - (bb->ulBitOff + iLen)); 60 | bb->ulBitOff += iLen - REGISTER_WIDTH; 61 | } else { 62 | bb->ulBits |= (ulCode << (REGISTER_WIDTH - bb->ulBitOff - iLen)); 63 | bb->ulBitOff += iLen; 64 | } 65 | } /* G5ENCInsertCode() */ 66 | // 67 | // Flush any buffered bits to the output 68 | // 69 | static void G5ENCFlushBits(G5_BUFFERED_BITS *bb) 70 | { 71 | while (bb->ulBitOff >= 8) 72 | { 73 | *bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8)); 74 | bb->ulBits <<= 8; 75 | bb->ulBitOff -= 8; 76 | } 77 | if (bb->ulBitOff) { // partial byte? 78 | *bb->pBuf++ = (unsigned char) (bb->ulBits >> (REGISTER_WIDTH - 8)); 79 | } 80 | bb->ulBitOff = 0; 81 | bb->ulBits = 0; 82 | } /* G5ENCFlushBits() */ 83 | // 84 | // Initialize the compressor 85 | // This must be called before adding data to the output 86 | // 87 | int g5_encode_init(G5ENCIMAGE *pImage, int iWidth, int iHeight, uint8_t *pOut, int iOutSize) 88 | { 89 | int iError = G5_SUCCESS; 90 | 91 | if (pImage == NULL || iHeight <= 0) 92 | return G5_INVALID_PARAMETER; 93 | pImage->iWidth = iWidth; // image size 94 | pImage->iHeight = iHeight; 95 | pImage->pCur = pImage->CurFlips; 96 | pImage->pRef = pImage->RefFlips; 97 | pImage->pOutBuf = pOut; // optional output buffer 98 | pImage->iOutSize = iOutSize; // output buffer pre-allocated size 99 | pImage->iDataSize = 0; // no data yet 100 | pImage->y = 0; 101 | for (int i=0; iRefFlips[i] = iWidth; 103 | pImage->CurFlips[i] = iWidth; 104 | } 105 | pImage->bb.pBuf = pImage->pOutBuf; 106 | pImage->bb.ulBits = 0; 107 | pImage->bb.ulBitOff = 0; 108 | pImage->iError = iError; 109 | return iError; 110 | } /* g5_encode_init() */ 111 | // 112 | // Internal function to convert uncompressed 1-bit per pixel data 113 | // into the run-end data needed to feed the G5 encoder 114 | // 115 | static int G5ENCEncodeLine(unsigned char *buf, int xsize, int16_t *pDest) 116 | { 117 | int iCount, xborder; 118 | uint8_t i, c; 119 | int8_t cBits; 120 | int iLen; 121 | int16_t x; 122 | int16_t *pLimit = pDest + (MAX_IMAGE_FLIPS-4); 123 | 124 | xborder = xsize; 125 | iCount = (xsize + 7) >> 3; /* Number of bytes per line */ 126 | cBits = 8; 127 | iLen = 0; /* Current run length */ 128 | x = 0; 129 | 130 | c = *buf++; /* Get the first byte to start */ 131 | iCount--; 132 | while (iCount >=0) { 133 | if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED; 134 | i = bitcount[c]; /* Get the number of consecutive bits */ 135 | iLen += i; /* Add this length to total run length */ 136 | c <<= i; 137 | cBits -= i; /* Minus the number in a byte */ 138 | if (cBits <= 0) 139 | { 140 | iLen += cBits; /* Adjust length */ 141 | cBits = 8; 142 | c = *buf++; /* Get another data byte */ 143 | iCount--; 144 | continue; /* Keep doing white until color change */ 145 | } 146 | c = ~c; /* flip color to count black pixels */ 147 | /* Store the white run length */ 148 | xborder -= iLen; 149 | if (xborder < 0) 150 | { 151 | iLen += xborder; /* Make sure run length is not past end */ 152 | break; 153 | } 154 | x += iLen; 155 | *pDest++ = x; 156 | iLen = 0; 157 | doblack: 158 | i = bitcount[c]; /* Get consecutive bits */ 159 | iLen += i; /* Add to total run length */ 160 | c <<= i; 161 | cBits -= i; 162 | if (cBits <= 0) 163 | { 164 | iLen += cBits; /* Adjust length */ 165 | cBits = 8; 166 | c = *buf++; /* Get another data byte */ 167 | c = ~c; /* Flip color to find black */ 168 | iCount--; 169 | if (iCount < 0) 170 | break; 171 | goto doblack; 172 | } 173 | /* Store the black run length */ 174 | c = ~c; /* Flip color again to find white pixels */ 175 | xborder -= iLen; 176 | if (xborder < 0) 177 | { 178 | iLen += xborder; /* Make sure run length is not past end */ 179 | break; 180 | } 181 | x += iLen; 182 | *pDest++ = x; 183 | iLen = 0; 184 | } /* while */ 185 | 186 | if (pDest >= pLimit) return G5_MAX_FLIPS_EXCEEDED; 187 | *pDest++ = xsize; 188 | *pDest++ = xsize; // Store a few more XSIZE to end the line 189 | *pDest++ = xsize; // so that the compressor doesn't go past 190 | *pDest++ = xsize; // the end of the line 191 | return G5_SUCCESS; 192 | } /* G5ENCEncodeLine() */ 193 | // 194 | // Compress a line of pixels and add it to the output 195 | // the input format is expected to be MSB (most significant bit) first 196 | // for example, pixel 0 is in byte 0 at bit 7 (0x80) 197 | // Returns G5ENC_SUCCESS for each line if all is well and G5ENC_IMAGE_COMPLETE 198 | // for the last line 199 | // 200 | int g5_encode_encodeLine(G5ENCIMAGE *pImage, uint8_t *pPixels) 201 | { 202 | int16_t a0, a0_c, b2, a1; 203 | int dx, run1, run2; 204 | int xsize, iErr, iHighWater; 205 | int iCur, iRef, iLen; 206 | int iHLen; // number of bits for long horizontal codes 207 | int16_t *CurFlips, *RefFlips; 208 | G5_BUFFERED_BITS bb; 209 | 210 | if (pImage == NULL || pPixels == NULL) 211 | return G5_INVALID_PARAMETER; 212 | iHighWater = pImage->iOutSize - 32; 213 | iHLen = 32 - __builtin_clz(pImage->iWidth); 214 | memcpy(&bb, &pImage->bb, sizeof(G5_BUFFERED_BITS)); // keep local copy 215 | CurFlips = pImage->pCur; 216 | RefFlips = pImage->pRef; 217 | xsize = pImage->iWidth; /* For performance reasons */ 218 | 219 | // Convert the incoming line of pixels into run-end data 220 | iErr = G5ENCEncodeLine(pPixels, pImage->iWidth, CurFlips); 221 | if (iErr != G5_SUCCESS) return iErr; // exceeded the maximum number of color changes 222 | /* Encode this line as G5 */ 223 | a0 = a0_c = 0; 224 | iCur = iRef = 0; 225 | while (a0 < xsize) { 226 | b2 = RefFlips[iRef+1]; 227 | a1 = CurFlips[iCur]; 228 | if (b2 < a1) { /* Is b2 to the left of a1? */ 229 | /* yes, do pass mode */ 230 | a0 = b2; 231 | iRef += 2; 232 | G5ENCInsertCode(&bb, 1, 4); /* Pass code = 0001 */ 233 | } else { /* Try vertical and horizontal mode */ 234 | dx = RefFlips[iRef] - a1; /* b1 - a1 */ 235 | if (dx > 3 || dx < -3) { /* Horizontal mode */ 236 | G5ENCInsertCode(&bb, 1, 3); /* Horizontal code = 001 */ 237 | run1 = CurFlips[iCur] - a0; 238 | run2 = CurFlips[iCur+1] - CurFlips[iCur]; 239 | if (run1 < 8) { 240 | if (run2 < 8) { // short, short 241 | G5ENCInsertCode(&bb, HORIZ_SHORT_SHORT, 2); /* short, short = 00 */ 242 | G5ENCInsertCode(&bb, run1, 3); 243 | G5ENCInsertCode(&bb, run2, 3); 244 | } else { // short, long 245 | G5ENCInsertCode(&bb, HORIZ_SHORT_LONG, 2); /* short, long = 01 */ 246 | G5ENCInsertCode(&bb, run1, 3); 247 | G5ENCInsertCode(&bb, run2, iHLen); 248 | } 249 | } else { // first run is long 250 | if (run2 < 8) { // long, short 251 | G5ENCInsertCode(&bb, HORIZ_LONG_SHORT, 2); /* long, short = 10 */ 252 | G5ENCInsertCode(&bb, run1, iHLen); 253 | G5ENCInsertCode(&bb, run2, 3); 254 | } else { // long, long 255 | G5ENCInsertCode(&bb, HORIZ_LONG_LONG, 2); /* long, long = 11 */ 256 | G5ENCInsertCode(&bb, run1, iHLen); 257 | G5ENCInsertCode(&bb, run2, iHLen); 258 | } 259 | } 260 | a0 = CurFlips[iCur+1]; /* a0 = a2 */ 261 | if (a0 != xsize) { 262 | iCur += 2; /* Skip two color flips */ 263 | while (RefFlips[iRef] != xsize && RefFlips[iRef] <= a0) { 264 | iRef += 2; 265 | } 266 | } 267 | } else { /* Vertical mode */ 268 | dx = (dx + 3) * 2; /* Convert to index table */ 269 | G5ENCInsertCode(&bb, vtable[dx], vtable[dx+1]); 270 | a0 = a1; 271 | a0_c = 1-a0_c; 272 | if (a0 != xsize) { 273 | if (iRef != 0) { 274 | iRef -= 2; 275 | } 276 | iRef++; /* Skip a color change in cur and ref */ 277 | iCur++; 278 | while (RefFlips[iRef] <= a0 && RefFlips[iRef] != xsize) { 279 | iRef += 2; 280 | } 281 | } 282 | } /* vertical mode */ 283 | } /* horiz/vert mode */ 284 | } /* while x < xsize */ 285 | iLen = (int)(bb.pBuf-pImage->pOutBuf); 286 | if (iLen >= iHighWater) { // not enough space 287 | pImage->iError = iErr = G5_DATA_OVERFLOW; // we don't have a better error 288 | return iErr; 289 | } 290 | if (pImage->y == pImage->iHeight-1) { // last line of image 291 | G5ENCFlushBits(&bb); // output the final buffered bits 292 | // wrap up final output 293 | pImage->iDataSize = 1 + (int)(bb.pBuf-pImage->pOutBuf); 294 | iErr = G5_ENCODE_COMPLETE; 295 | } 296 | pImage->pCur = RefFlips; // swap current and reference lines 297 | pImage->pRef = CurFlips; 298 | pImage->y++; 299 | memcpy(&pImage->bb, &bb, sizeof(bb)); 300 | return iErr; 301 | } /* g5_encode_encodeLine() */ 302 | // 303 | // Returns the number of bytes of G5 created by the encoder 304 | // 305 | int g5_encode_getOutSize(G5ENCIMAGE *pImage) 306 | { 307 | int iSize = 0; 308 | if (pImage != NULL) 309 | iSize = pImage->iDataSize; 310 | return iSize; 311 | } /* g5_encode_getOutSize() */ 312 | -------------------------------------------------------------------------------- /fontconvert/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | TrueType to Adafruit_GFX font converter. Derived from Peter Jakobs' 3 | Adafruit_ftGFX fork & makefont tool, and Paul Kourany's Adafruit_mfGFX. 4 | 5 | NOT AN ARDUINO SKETCH. This is a command-line tool for preprocessing 6 | fonts to be used with the Adafruit_GFX Arduino library. 7 | 8 | For UNIX-like systems. Outputs to stdout; redirect to header file, e.g.: 9 | ./fontconvert ~/Library/Fonts/FreeSans.ttf 18 > FreeSans18pt7b.h 10 | 11 | REQUIRES FREETYPE LIBRARY. www.freetype.org 12 | 13 | Currently this only extracts the printable 7-bit ASCII chars of a font. 14 | Will eventually extend with some int'l chars a la ftGFX, not there yet. 15 | Keep 7-bit fonts around as an option in that case, more compact. 16 | 17 | See notes at end for glyph nomenclature & other tidbits. 18 | */ 19 | #ifndef ARDUINO 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include FT_GLYPH_H 26 | #include FT_MODULE_H 27 | #include FT_TRUETYPE_DRIVER_H 28 | 29 | typedef struct { 30 | uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap 31 | uint16_t width; ///< Bitmap dimensions in pixels 32 | uint16_t height; ///< Bitmap dimensions in pixels 33 | uint16_t xAdvance; ///< Distance to advance cursor (x axis) 34 | int16_t xOffset; ///< X dist from cursor pos to UL corner 35 | int16_t yOffset; ///< Y dist from cursor pos to UL corner 36 | } GFXglyph; 37 | 38 | /// Data stored for FONT AS A WHOLE 39 | typedef struct { 40 | uint8_t *bitmap; ///< Glyph bitmaps, concatenated 41 | GFXglyph *glyph; ///< Glyph array 42 | uint8_t first; ///< ASCII extents (first char) 43 | uint8_t last; ///< ASCII extents (last char) 44 | int16_t yAdvance; ///< Newline distance (y axis) 45 | } GFXfont; 46 | 47 | #define DPI 141 // Approximate res. of Adafruit 2.8" TFT 48 | 49 | // Accumulate bits for output, with periodic hexadecimal byte write 50 | void enbit(uint8_t value) { 51 | static uint8_t row = 0, sum = 0, bit = 0x80, firstCall = 1; 52 | if (value) 53 | sum |= bit; // Set bit if needed 54 | if (!(bit >>= 1)) { // Advance to next bit, end of byte reached? 55 | if (!firstCall) { // Format output table nicely 56 | if (++row >= 12) { // Last entry on line? 57 | printf(",\n "); // Newline format output 58 | row = 0; // Reset row counter 59 | } else { // Not end of line 60 | printf(", "); // Simple comma delim 61 | } 62 | } 63 | printf("0x%02X", sum); // Write byte value 64 | sum = 0; // Clear for next byte 65 | bit = 0x80; // Reset bit counter 66 | firstCall = 0; // Formatting flag 67 | } 68 | } 69 | // 70 | // Lookup table to convert codepage 1252 to Unicode 71 | // 72 | const uint16_t uc1252Table[256] = { 73 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0-15 not used (some can be mapped to printable characters if needed) 74 | 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 16-31 not used 75 | 32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47, 76 | 48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63, 77 | 64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79, 78 | 80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95, 79 | 96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111, 80 | 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, 81 | // Now starts the remapped Unicode characters 82 | 0x20ac, 32, 0x201a, 0x192, 0x201e, 0x2026, 0x2020, 0x2021, 0x2c6, 0x2030,0x160,0x2039,0x152,32,0x17d,32, // 0x80-0x8f 83 | 32, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x2dc, 0x2122, 0x161, 0x2031, 0x153, 32, 0x17e, 0x178, // 0x90-0x9f 84 | 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf, 85 | 0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf, 86 | 0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf, 87 | 0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf, 88 | 0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef, 89 | 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff 90 | }; 91 | 92 | int main(int argc, char *argv[]) { 93 | int i, j, err, size, first = ' ', last = '~', bitmapOffset = 0, x, y, byte; 94 | char *fontName, c, *ptr; 95 | FT_Library library; 96 | FT_Face face; 97 | FT_Glyph glyph; 98 | FT_Bitmap *bitmap; 99 | FT_BitmapGlyphRec *g; 100 | GFXglyph *table; 101 | uint8_t bit; 102 | 103 | // Parse command line. Valid syntaxes are: 104 | // fontconvert [filename] [size] 105 | // fontconvert [filename] [size] [last char] 106 | // fontconvert [filename] [size] [first char] [last char] 107 | // Unless overridden, default first and last chars are 108 | // ' ' (space) and '~', respectively 109 | 110 | if (argc < 3) { 111 | fprintf(stderr, "Usage: %s fontfile size [first] [last]\n", argv[0]); 112 | return 1; 113 | } 114 | 115 | size = atoi(argv[2]); 116 | 117 | if (argc == 4) { 118 | last = atoi(argv[3]); 119 | } else if (argc == 5) { 120 | first = atoi(argv[3]); 121 | last = atoi(argv[4]); 122 | } 123 | 124 | if (last < first) { 125 | i = first; 126 | first = last; 127 | last = i; 128 | } 129 | 130 | ptr = strrchr(argv[1], '/'); // Find last slash in filename 131 | if (ptr) 132 | ptr++; // First character of filename (path stripped) 133 | else 134 | ptr = argv[1]; // No path; font in local dir. 135 | 136 | // Allocate space for font name and glyph table 137 | if ((!(fontName = malloc(strlen(ptr) + 20))) || 138 | (!(table = (GFXglyph *)malloc((last - first + 1) * sizeof(GFXglyph))))) { 139 | fprintf(stderr, "Malloc error\n"); 140 | return 1; 141 | } 142 | 143 | // Derive font table names from filename. Period (filename 144 | // extension) is truncated and replaced with the font size & bits. 145 | strcpy(fontName, ptr); 146 | ptr = strrchr(fontName, '.'); // Find last period (file ext) 147 | if (!ptr) 148 | ptr = &fontName[strlen(fontName)]; // If none, append 149 | // Insert font size and 7/8 bit. fontName was alloc'd w/extra 150 | // space to allow this, we're not sprintfing into Forbidden Zone. 151 | sprintf(ptr, "%dpt%db", size, (last > 127) ? 8 : 7); 152 | // Space and punctuation chars in name replaced w/ underscores. 153 | for (i = 0; (c = fontName[i]); i++) { 154 | if (isspace(c) || ispunct(c)) 155 | fontName[i] = '_'; 156 | } 157 | 158 | // Init FreeType lib, load font 159 | if ((err = FT_Init_FreeType(&library))) { 160 | fprintf(stderr, "FreeType init error: %d", err); 161 | return err; 162 | } 163 | 164 | // Use TrueType engine version 35, without subpixel rendering. 165 | // This improves clarity of fonts since this library does not 166 | // support rendering multiple levels of gray in a glyph. 167 | // See https://github.com/adafruit/Adafruit-GFX-Library/issues/103 168 | FT_UInt interpreter_version = TT_INTERPRETER_VERSION_35; 169 | FT_Property_Set(library, "truetype", "interpreter-version", 170 | &interpreter_version); 171 | 172 | if ((err = FT_New_Face(library, argv[1], 0, &face))) { 173 | fprintf(stderr, "Font load error: %d", err); 174 | FT_Done_FreeType(library); 175 | return err; 176 | } 177 | 178 | // << 6 because '26dot6' fixed-point format 179 | FT_Set_Char_Size(face, size << 6, 0, DPI, 0); 180 | 181 | // Currently all symbols from 'first' to 'last' are processed. 182 | // Fonts may contain WAY more glyphs than that, but this code 183 | // will need to handle encoding stuff to deal with extracting 184 | // the right symbols, and that's not done yet. 185 | // fprintf(stderr, "%ld glyphs\n", face->num_glyphs); 186 | 187 | printf("const uint8_t %sBitmaps[] PROGMEM = {\n ", fontName); 188 | 189 | // Process glyphs and output huge bitmap data array 190 | for (i = first, j = 0; i <= last; i++, j++) { 191 | int iChar = uc1252Table[i]; // convert CP1252 to Unicode 192 | // MONO renderer provides clean image with perfect crop 193 | // (no wasted pixels) via bitmap struct. 194 | if ((err = FT_Load_Char(face, iChar, FT_LOAD_TARGET_MONO))) { 195 | fprintf(stderr, "Error %d loading char '%c'\n", err, iChar); 196 | continue; 197 | } 198 | 199 | if ((err = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_MONO))) { 200 | fprintf(stderr, "Error %d rendering char '%c'\n", err, iChar); 201 | continue; 202 | } 203 | 204 | if ((err = FT_Get_Glyph(face->glyph, &glyph))) { 205 | fprintf(stderr, "Error %d getting glyph '%c'\n", err, iChar); 206 | continue; 207 | } 208 | 209 | bitmap = &face->glyph->bitmap; 210 | g = (FT_BitmapGlyphRec *)glyph; 211 | 212 | // Minimal font and per-glyph information is stored to 213 | // reduce flash space requirements. Glyph bitmaps are 214 | // fully bit-packed; no per-scanline pad, though end of 215 | // each character may be padded to next byte boundary 216 | // when needed. 16-bit offset means 64K max for bitmaps, 217 | // code currently doesn't check for overflow. (Doesn't 218 | // check that size & offsets are within bounds either for 219 | // that matter...please convert fonts responsibly.) 220 | table[j].bitmapOffset = bitmapOffset; 221 | table[j].width = bitmap->width; 222 | table[j].height = bitmap->rows; 223 | table[j].xAdvance = face->glyph->advance.x >> 6; 224 | table[j].xOffset = g->left; 225 | table[j].yOffset = 1 - g->top; 226 | 227 | for (y = 0; y < bitmap->rows; y++) { 228 | for (x = 0; x < bitmap->width; x++) { 229 | byte = x / 8; 230 | bit = 0x80 >> (x & 7); 231 | enbit(bitmap->buffer[y * bitmap->pitch + byte] & bit); 232 | } 233 | } 234 | 235 | // Pad end of char bitmap to next byte boundary if needed 236 | int n = (bitmap->width * bitmap->rows) & 7; 237 | if (n) { // Pixel count not an even multiple of 8? 238 | n = 8 - n; // # bits to next multiple 239 | while (n--) 240 | enbit(0); 241 | } 242 | bitmapOffset += (bitmap->width * bitmap->rows + 7) / 8; 243 | 244 | FT_Done_Glyph(glyph); 245 | } 246 | 247 | printf(" };\n\n"); // End bitmap array 248 | 249 | // Output glyph attributes table (one per character) 250 | printf("const GFXglyph %sGlyphs[] PROGMEM = {\n", fontName); 251 | for (i = first, j = 0; i <= last; i++, j++) { 252 | printf(" { %5d, %3d, %3d, %3d, %4d, %4d }", table[j].bitmapOffset, 253 | table[j].width, table[j].height, table[j].xAdvance, table[j].xOffset, 254 | table[j].yOffset); 255 | if (i < last) { 256 | printf(", // 0x%02X", i); 257 | if ((i >= ' ') && (i <= '~')) { 258 | printf(" '%c'", i); 259 | } 260 | putchar('\n'); 261 | } 262 | } 263 | printf(" }; // 0x%02X", last); 264 | if ((last >= ' ') && (last <= '~')) 265 | printf(" '%c'", last); 266 | printf("\n\n"); 267 | 268 | // Output font structure 269 | printf("const GFXfont %s PROGMEM = {\n", fontName); 270 | printf(" (uint8_t *)%sBitmaps,\n", fontName); 271 | printf(" (GFXglyph *)%sGlyphs,\n", fontName); 272 | if (face->size->metrics.height == 0) { 273 | // No face height info, assume fixed width and get from a glyph. 274 | printf(" 0x%02X, 0x%02X, %d };\n\n", first, last, table[0].height); 275 | } else { 276 | printf(" 0x%02X, 0x%02X, %ld };\n\n", first, last, 277 | face->size->metrics.height >> 6); 278 | } 279 | printf("// Approx. %d bytes\n", bitmapOffset + (last - first + 1) * 7 + 7); 280 | // Size estimate is based on AVR struct and pointer sizes; 281 | // actual size may vary. 282 | 283 | FT_Done_FreeType(library); 284 | 285 | return 0; 286 | } 287 | 288 | /* ------------------------------------------------------------------------- 289 | 290 | Character metrics are slightly different from classic GFX & ftGFX. 291 | In classic GFX: cursor position is the upper-left pixel of each 5x7 292 | character; lower extent of most glyphs (except those w/descenders) 293 | is +6 pixels in Y direction. 294 | W/new GFX fonts: cursor position is on baseline, where baseline is 295 | 'inclusive' (containing the bottom-most row of pixels in most symbols, 296 | except those with descenders; ftGFX is one pixel lower). 297 | 298 | Cursor Y will be moved automatically when switching between classic 299 | and new fonts. If you switch fonts, any print() calls will continue 300 | along the same baseline. 301 | 302 | ...........#####.. -- yOffset 303 | ..........######.. 304 | ..........######.. 305 | .........#######.. 306 | ........#########. 307 | * = Cursor pos. ........#########. 308 | .......##########. 309 | ......#####..####. 310 | ......#####..####. 311 | *.#.. .....#####...####. 312 | .#.#. ....############## 313 | #...# ...############### 314 | #...# ...############### 315 | ##### ..#####......##### 316 | #...# .#####.......##### 317 | ====== #...# ====== #*###.........#### ======= Baseline 318 | || xOffset 319 | 320 | glyph->xOffset and yOffset are pixel offsets, in GFX coordinate space 321 | (+Y is down), from the cursor position to the top-left pixel of the 322 | glyph bitmap. i.e. yOffset is typically negative, xOffset is typically 323 | zero but a few glyphs will have other values (even negative xOffsets 324 | sometimes, totally normal). glyph->xAdvance is the distance to move 325 | the cursor on the X axis after drawing the corresponding symbol. 326 | 327 | There's also some changes with regard to 'background' color and new GFX 328 | fonts (classic fonts unchanged). See Adafruit_GFX.cpp for explanation. 329 | */ 330 | 331 | #endif /* !ARDUINO */ 332 | -------------------------------------------------------------------------------- /src/g5dec.inl: -------------------------------------------------------------------------------- 1 | // 2 | // Group5 3 | // A 1-bpp image decoder 4 | // 5 | // Written by Larry Bank 6 | // Copyright (c) 2024 BitBank Software, Inc. 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // http://www.apache.org/licenses/LICENSE-2.0 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | //=========================================================================== 18 | // 19 | 20 | #include "Group5.h" 21 | 22 | /* 23 | The code tree that follows has: bit_length, decode routine 24 | These codes are for Group 4 (MMR) decoding 25 | 26 | 01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2 27 | 02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash 28 | */ 29 | 30 | static const uint8_t code_table[128] = 31 | {0x90, 0, 0x40, 0, /* trash, uncompr mode - codes 0 and 1 */ 32 | 3, 7, /* V(-3) pos = 2 */ 33 | 0x13, 7, /* V(3) pos = 3 */ 34 | 2, 6, 2, 6, /* V(-2) pos = 4,5 */ 35 | 0x12, 6, 0x12, 6, /* V(2) pos = 6,7 */ 36 | 0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, /* pass pos = 8->F */ 37 | 0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, 38 | 0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, /* horiz pos = 10->1F */ 39 | 0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, 40 | 0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, 41 | 0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, 42 | /* V(-1) pos = 20->2F */ 43 | 1, 3, 1, 3, 1, 3, 1, 3, 44 | 1, 3, 1, 3, 1, 3, 1, 3, 45 | 1, 3, 1, 3, 1, 3, 1, 3, 46 | 1, 3, 1, 3, 1, 3, 1, 3, 47 | 0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, /* V(1) pos = 30->3F */ 48 | 0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, 49 | 0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, 50 | 0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3}; 51 | 52 | static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize) 53 | { 54 | if (pImage == NULL || iWidth < 1 || iHeight < 1 || pData == NULL || iDataSize < 1) 55 | return G5_INVALID_PARAMETER; 56 | 57 | pImage->iVLCSize = iDataSize; 58 | pImage->pSrc = pData; 59 | pImage->ulBitOff = 0; 60 | pImage->y = 0; 61 | pImage->ulBits = TIFFMOTOLONG(pData); // preload the first 32 bits of data 62 | pImage->iWidth = iWidth; 63 | pImage->iHeight = iHeight; 64 | return G5_SUCCESS; 65 | 66 | } /* g5_decode_init() */ 67 | 68 | static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut) 69 | { 70 | int x, len, run; 71 | uint8_t lBit, rBit, *p; 72 | int iStart = 0, xright = pPage->iWidth; 73 | uint8_t *pDest; 74 | 75 | iStart = 0; 76 | 77 | pDest = pOut; 78 | len = (xright+7)>>3; // number of bytes to generate 79 | for (x=0; x= xright || run == 0) 88 | break; 89 | if ((x + run) > 0) { /* If the run is visible, draw it */ 90 | if (x < 0) { 91 | run += x; /* draw only visible part of run */ 92 | x = 0; 93 | } 94 | if ((x + run) > xright) { /* Don't let it go off right edge */ 95 | run = xright - x; 96 | } 97 | /* Draw this run */ 98 | lBit = 0xff << (8 - (x & 7)); 99 | rBit = 0xff >> ((x + run) & 7); 100 | len = ((x+run)>>3) - (x >> 3); 101 | p = &pDest[x >> 3]; 102 | if (len == 0) { 103 | lBit |= rBit; 104 | *p &= lBit; 105 | } else { 106 | *p++ &= lBit; 107 | while (len > 1) { 108 | *p++ = 0; 109 | len--; 110 | } 111 | *p = rBit; 112 | } 113 | } // visible run 114 | } /* while drawing line */ 115 | } /* G5DrawLine() */ 116 | // 117 | // Initialize internal structures to decode the image 118 | // 119 | static void Decode_Begin(G5DECIMAGE *pPage) 120 | { 121 | int i, xsize; 122 | int16_t *CurFlips, *RefFlips; 123 | 124 | xsize = pPage->iWidth; 125 | 126 | RefFlips = pPage->RefFlips; 127 | CurFlips = pPage->CurFlips; 128 | 129 | /* Seed the current and reference line with XSIZE for V(0) codes */ 130 | for (i=0; i XSIZE 136 | 3-16-94 */ 137 | CurFlips[i] = RefFlips[i] = 0x7fff; 138 | CurFlips[i+1] = RefFlips[i+1] = 0x7fff; 139 | 140 | pPage->pCur = CurFlips; 141 | pPage->pRef = RefFlips; 142 | pPage->pBuf = pPage->pSrc; 143 | pPage->ulBits = TIFFMOTOLONG(pPage->pSrc); // load 32 bits to start 144 | pPage->ulBitOff = 0; 145 | // Calculate the number of bits needed for a long horizontal code 146 | #ifdef __AVR__ 147 | pPage->iHLen = 16 - __builtin_clz(pPage->iWidth); 148 | #else 149 | pPage->iHLen = 32 - __builtin_clz(pPage->iWidth); 150 | #endif 151 | } /* Decode_Begin() */ 152 | // 153 | // Decode a single line of G5 data (private function) 154 | // 155 | static int DecodeLine(G5DECIMAGE *pPage) 156 | { 157 | signed int a0, a0_p, b1; 158 | int16_t *pCur, *pRef, *RefFlips, *CurFlips; 159 | int xsize, tot_run=0, tot_run1 = 0; 160 | int32_t sCode; 161 | uint32_t lBits; 162 | uint32_t ulBits, ulBitOff; 163 | uint8_t *pBuf/*, *pBufEnd*/; 164 | uint32_t u32HMask, u32HLen; // horizontal code mask and length 165 | 166 | pCur = CurFlips = pPage->pCur; 167 | pRef = RefFlips = pPage->pRef; 168 | ulBits = pPage->ulBits; 169 | ulBitOff = pPage->ulBitOff; 170 | pBuf = pPage->pBuf; 171 | // pBufEnd = &pPage->pSrc[pPage->iVLCSize]; 172 | u32HLen = pPage->iHLen; 173 | u32HMask = (1 << u32HLen) - 1; 174 | a0 = -1; 175 | xsize = pPage->iWidth; 176 | 177 | while (a0 < xsize) { /* Decode this line */ 178 | if (ulBitOff > (REGISTER_WIDTH-8)) { // need at least 7 unused bits 179 | pBuf += (ulBitOff >> 3); 180 | ulBitOff &= 7; 181 | ulBits = TIFFMOTOLONG(pBuf); 182 | } 183 | if ((int32_t)(ulBits << ulBitOff) < 0) { /* V(0) code is the most frequent case (1 bit) */ 184 | a0 = *pRef++; 185 | ulBitOff++; // length = 1 bit 186 | *pCur++ = a0; 187 | } else { /* Slower method for the less frequence codes */ 188 | lBits = (ulBits >> ((REGISTER_WIDTH - 8) - ulBitOff)) & 0xfe; /* Only the first 7 bits are useful */ 189 | sCode = code_table[lBits]; /* Get the code type as an 8-bit value */ 190 | ulBitOff += code_table[lBits+1]; /* Get the code length */ 191 | switch (sCode) { 192 | case 1: /* V(-1) */ 193 | case 2: /* V(-2) */ 194 | case 3: /* V(-3) */ 195 | a0 = *pRef - sCode; /* A0 = B1 - x */ 196 | *pCur++ = a0; 197 | if (pRef == RefFlips) { 198 | pRef += 2; 199 | } 200 | pRef--; 201 | while (a0 >= *pRef) { 202 | pRef += 2; 203 | } 204 | break; 205 | 206 | case 0x11: /* V(1) */ 207 | case 0x12: /* V(2) */ 208 | case 0x13: /* V(3) */ 209 | a0 = *pRef++; /* A0 = B1 */ 210 | b1 = a0; 211 | a0 += sCode & 7; /* A0 = B1 + x */ 212 | if (b1 != xsize && a0 < xsize) { 213 | while (a0 >= *pRef) { 214 | pRef += 2; 215 | } 216 | } 217 | if (a0 > xsize) { 218 | a0 = xsize; 219 | } 220 | *pCur++ = a0; 221 | break; 222 | 223 | case 0x20: /* Horizontal codes */ 224 | if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits 225 | pBuf += (ulBitOff >> 3); 226 | ulBitOff &= 7; 227 | ulBits = TIFFMOTOLONG(pBuf); 228 | } 229 | a0_p = a0; 230 | if (a0 < 0) { 231 | a0_p = 0; 232 | } 233 | lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3; // get 2-bit prefix for code type 234 | // There are 4 possible horizontal cases: short/short, short/long, long/short, long/long 235 | // These are encoded in a 2-bit prefix code, followed by 3 bits for short or N bits for long code 236 | // N is the log base 2 of the image width (e.g. 320 pixels requires 9 bits) 237 | ulBitOff += 2; 238 | switch (lBits) { 239 | case HORIZ_SHORT_SHORT: 240 | tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length 241 | ulBitOff += 3; 242 | tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length 243 | ulBitOff += 3; 244 | break; 245 | case HORIZ_SHORT_LONG: 246 | tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length 247 | ulBitOff += 3; 248 | tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length 249 | ulBitOff += u32HLen; 250 | break; 251 | case HORIZ_LONG_SHORT: 252 | tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length 253 | ulBitOff += u32HLen; 254 | tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7; // get 3-bit short length 255 | ulBitOff += 3; 256 | break; 257 | case HORIZ_LONG_LONG: 258 | tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length 259 | ulBitOff += u32HLen; 260 | if (ulBitOff > (REGISTER_WIDTH-16)) { // need at least 16 unused bits 261 | pBuf += (ulBitOff >> 3); 262 | ulBitOff &= 7; 263 | ulBits = TIFFMOTOLONG(pBuf); 264 | } 265 | tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask; // get long length 266 | ulBitOff += u32HLen; 267 | break; 268 | } // switch on lBits 269 | a0 = a0_p + tot_run; 270 | *pCur++ = a0; 271 | a0 += tot_run1; 272 | if (a0 < xsize) { 273 | while (a0 >= *pRef) { 274 | pRef += 2; 275 | } 276 | } 277 | *pCur++ = a0; 278 | break; 279 | 280 | case 0x30: /* Pass code */ 281 | pRef++; /* A0 = B2, iRef+=2 */ 282 | a0 = *pRef++; 283 | break; 284 | 285 | default: /* ERROR */ 286 | pPage->iError = G5_DECODE_ERROR; 287 | goto pilreadg5z; 288 | } /* switch */ 289 | } /* Slow climb */ 290 | } 291 | /*--- Convert flips data into run lengths ---*/ 292 | *pCur++ = xsize; /* Terminate the line properly */ 293 | *pCur++ = xsize; 294 | pilreadg5z: 295 | // Save the current VLC decoder state 296 | pPage->ulBits = ulBits; 297 | pPage->ulBitOff = ulBitOff; 298 | pPage->pBuf = pBuf; 299 | return pPage->iError; 300 | } /* DecodeLine() */ 301 | // 302 | // Decompress the VLC data 303 | // 304 | static int g5_decode_line(G5DECIMAGE *pPage, uint8_t *pOut) 305 | { 306 | int rc; 307 | uint8_t *pBufEnd; 308 | int16_t *t1; 309 | 310 | if (pPage == NULL || pOut == NULL) 311 | return G5_INVALID_PARAMETER; 312 | if (pPage->y >= pPage->iHeight) 313 | return G5_DECODE_COMPLETE; 314 | 315 | pPage->iError = G5_SUCCESS; 316 | 317 | if (pPage->y == 0) { // first time through 318 | Decode_Begin(pPage); 319 | } 320 | pBufEnd = &pPage->pSrc[pPage->iVLCSize]; 321 | 322 | if (pPage->pBuf >= pBufEnd) { // read past the end, error 323 | pPage->iError = G5_DECODE_ERROR; 324 | return G5_DECODE_ERROR; 325 | } 326 | rc = DecodeLine(pPage); 327 | if (rc == G5_SUCCESS) { 328 | // Draw the current line 329 | G5DrawLine(pPage, pPage->pCur, pOut); 330 | /*--- Swap current and reference lines ---*/ 331 | t1 = pPage->pRef; 332 | pPage->pRef = pPage->pCur; 333 | pPage->pCur = t1; 334 | pPage->y++; 335 | if (pPage->y >= pPage->iHeight) { 336 | pPage->iError = G5_DECODE_COMPLETE; 337 | } 338 | } else { 339 | pPage->iError = rc; 340 | } 341 | return pPage->iError; 342 | } /* Decode() */ 343 | 344 | -------------------------------------------------------------------------------- /src/linux_io.inl: -------------------------------------------------------------------------------- 1 | // 2 | // bb_spi_lcd Linux specific functions 3 | // 4 | #ifndef __BB_LINUX_IO__ 5 | #define __BB_LINUX_IO__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #ifndef __MEM_ONLY__ 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #ifndef OUTPUT 22 | #define false 0 23 | #define true 1 24 | #define PROGMEM 25 | #define memcpy_P memcpy 26 | #define OUTPUT 1 27 | #define INPUT 0 28 | #define INPUT_PULLUP 2 29 | #define HIGH 1 30 | #define LOW 0 31 | #define pgm_read_byte(a) (*(uint8_t*)(a)) 32 | #endif // !OUTPUT 33 | #ifndef CONSUMER 34 | #define CONSUMER "Consumer" 35 | #endif 36 | //uint8_t *pDMA, ucTXBuf[4096]; 37 | //static uint8_t transfer_is_done = true; 38 | extern volatile int bSetPosition; 39 | static int iGPIOChip; 40 | struct gpiod_chip *chip = NULL; 41 | #ifdef GPIOD_API 42 | struct gpiod_line *lines[64]; 43 | #else 44 | struct gpiod_line_request *lines[64]; 45 | #endif 46 | #endif // !__MEM_ONLY__ 47 | 48 | extern uint8_t u8BW, u8WR, u8RD, u8DC, u8CS, u8CMD; 49 | static int spi_fd; // SPI handle 50 | static uint8_t gpio_bit_zero; // bit zero of parallel GPIO pins 51 | volatile uint32_t *gpio; 52 | volatile uint32_t *set_reg, *clr_reg, *sel_reg; 53 | #define GPIO_BLOCK_SIZE 4*1024 54 | static uint32_t u32Speed; 55 | // N.B. the default buffer size is 4K 56 | #define RPI_DMA_SIZE 4096 57 | // wrapper/adapter functions to make the code work on Linux 58 | int digitalRead(int iPin) 59 | { 60 | #ifdef __MEM_ONLY__ 61 | (void)iPin; 62 | return 0; 63 | #else 64 | if (lines[iPin] == 0) return 0; 65 | #ifdef GPIOD_API // 1.x (old) API 66 | return gpiod_line_get_value(lines[iPin]); 67 | #else // 2.x (new) 68 | return gpiod_line_request_get_value(lines[iPin], iPin) == GPIOD_LINE_VALUE_ACTIVE; 69 | #endif 70 | #endif // __MEM_ONLY__ 71 | } /* digitalRead() */ 72 | 73 | void digitalWrite(int iPin, int iState) 74 | { 75 | #ifdef __MEM_ONLY__ 76 | (void)iPin; (void)iState; 77 | #else 78 | if (lines[iPin] == 0) return; 79 | #ifdef GPIOD_API // old 1.6 API 80 | gpiod_line_set_value(lines[iPin], iState); 81 | #else // new 2.x API 82 | gpiod_line_request_set_value(lines[iPin], iPin, (iState) ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE); 83 | #endif 84 | #endif // __MEM_ONLY__ 85 | } /* digitalWrite() */ 86 | 87 | void pinMode(int iPin, int iMode) 88 | { 89 | #ifdef __MEM_ONLY__ 90 | (void)iPin; (void)iMode; 91 | #else 92 | #ifdef GPIOD_API // old 1.6 API 93 | if (chip == NULL) { 94 | char szTemp[32]; 95 | snprintf(szTemp, sizeof(szTemp), "gpiochip%d", iGPIOChip); 96 | chip = gpiod_chip_open_by_name(szTemp); 97 | } 98 | lines[iPin] = gpiod_chip_get_line(chip, iPin); 99 | if (iMode == OUTPUT) { 100 | gpiod_line_request_output(lines[iPin], CONSUMER, 0); 101 | } else if (iMode == INPUT_PULLUP) { 102 | gpiod_line_request_input_flags(lines[iPin], CONSUMER, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP); 103 | } else { // plain input 104 | gpiod_line_request_input(lines[iPin], CONSUMER); 105 | } 106 | #else // new 2.x API 107 | struct gpiod_line_settings *settings; 108 | struct gpiod_line_config *line_cfg; 109 | struct gpiod_request_config *req_cfg; 110 | char szTemp[32]; 111 | snprintf(szTemp, sizeof(szTemp), "/dev/gpiochip%d", iGPIOChip); 112 | //printf("opening %s\n", szTemp); 113 | chip = gpiod_chip_open(szTemp); 114 | if (!chip) { 115 | printf("chip open failed\n"); 116 | return; 117 | } 118 | settings = gpiod_line_settings_new(); 119 | if (!settings) { 120 | printf("line_settings_new failed\n"); 121 | return; 122 | } 123 | gpiod_line_settings_set_direction(settings, (iMode == OUTPUT) ? GPIOD_LINE_DIRECTION_OUTPUT : GPIOD_LINE_DIRECTION_INPUT); 124 | line_cfg = gpiod_line_config_new(); 125 | if (!line_cfg) return; 126 | gpiod_line_config_add_line_settings(line_cfg, (const unsigned int *)&iPin, 1, settings); 127 | req_cfg = gpiod_request_config_new(); 128 | gpiod_request_config_set_consumer(req_cfg, CONSUMER); 129 | lines[iPin] = gpiod_chip_request_lines(chip, req_cfg, line_cfg); 130 | gpiod_request_config_free(req_cfg); 131 | gpiod_line_config_free(line_cfg); 132 | gpiod_line_settings_free(settings); 133 | gpiod_chip_close(chip); 134 | #endif 135 | #endif // __MEM_ONLY__ 136 | } /* pinMode() */ 137 | 138 | static void delay(int iMS) 139 | { 140 | #ifdef __MEM_ONLY__ 141 | (void)iMS; 142 | #else 143 | usleep(iMS * 1000); 144 | #endif 145 | } /* delay() */ 146 | 147 | static void delayMicroseconds(int iMS) 148 | { 149 | #ifdef __MEM_ONLY__ 150 | (void)iMS; 151 | #else 152 | usleep(iMS); 153 | #endif 154 | } /* delayMicroseconds() */ 155 | 156 | void linux_spi_write(uint8_t *pBuf, int iLen, uint32_t iSPISpeed) 157 | { 158 | #ifdef __MEM_ONLY__ 159 | (void)pBuf; (void)iLen; (void)iSPISpeed; 160 | #else 161 | struct spi_ioc_transfer spi; 162 | memset(&spi, 0, sizeof(spi)); 163 | while (iLen) { // max 64k transfers (default is 4k) 164 | int j = iLen; 165 | if (j > RPI_DMA_SIZE) j = RPI_DMA_SIZE; 166 | spi.tx_buf = (unsigned long)pBuf; 167 | spi.len = j; 168 | spi.speed_hz =iSPISpeed; 169 | //spi.cs_change = 1; 170 | spi.bits_per_word = 8; 171 | ioctl(spi_fd, SPI_IOC_MESSAGE(1), &spi); 172 | iLen -= j; 173 | pBuf += j; 174 | } 175 | #endif 176 | } /* linux_spi_write() */ 177 | 178 | void linux_spi_init(int iMISOPin, int iMOSIPin, int iCLKPin) 179 | { 180 | #ifdef __MEM_ONLY__ 181 | (void)iMISOPin; (void)iMOSIPin; (void)iCLKPin; 182 | #else 183 | iGPIOChip = iMISOPin; 184 | char szTemp[32]; 185 | snprintf(szTemp, sizeof(szTemp), "/dev/spidev%d.%d", iMOSIPin, iCLKPin); 186 | spi_fd = open(szTemp, O_RDWR); 187 | if (spi_fd <= 0) { 188 | printf("Error opening %s\n", szTemp); 189 | } 190 | #endif 191 | } /* linux_spi_init() */ 192 | 193 | /** 194 | * Wait N CPU cycles (ARM CPU only) 195 | * On the RPI Zero 2W, 1 microsecond ˜= 463 wait loops 196 | */ 197 | static void wait_cycles(unsigned int n) 198 | { 199 | if(n) while(n--) { asm volatile("nop"); } 200 | } 201 | // 202 | // Write parallel data to the Raspberry Pi (1/2/3/4) GPIO registers 203 | // 204 | void linux_parallel_write(uint8_t *pData, int len, int iMode) 205 | { 206 | #ifdef __MEM_ONLY__ 207 | (void)pData; (void)len; (void)iMode; 208 | #else 209 | const uint32_t DATA_BIT_0 = gpio_bit_zero; 210 | uint32_t c, e; 211 | const uint32_t u32WR = 1 << u8WR; 212 | const uint32_t xor_mask = (0xff << DATA_BIT_0); 213 | const uint32_t xor_mask2 = (xor_mask | u32WR); 214 | 215 | *clr_reg = (1 << u8CS); // activate CS 216 | if (iMode == 0) /*MODE_DATA*/ { // DC high 217 | *set_reg = (1 << u8DC); 218 | } else { 219 | *clr_reg = (1 << u8DC); // DC low 220 | } 221 | //wait_cycles(u32Speed); 222 | 223 | for (int i=0; i' 189 | {470, 8, 17, 11, 2, -16}, // 0x3F '?' 190 | {487, 17, 16, 21, 2, -15}, // 0x40 '@' 191 | {521, 17, 16, 17, 0, -15}, // 0x41 'A' 192 | {555, 12, 16, 15, 1, -15}, // 0x42 'B' 193 | {579, 15, 16, 16, 1, -15}, // 0x43 'C' 194 | {609, 16, 16, 17, 0, -15}, // 0x44 'D' 195 | {641, 14, 16, 15, 0, -15}, // 0x45 'E' 196 | {669, 14, 16, 14, 0, -15}, // 0x46 'F' 197 | {697, 16, 16, 17, 1, -15}, // 0x47 'G' 198 | {729, 16, 16, 17, 0, -15}, // 0x48 'H' 199 | {761, 6, 16, 8, 1, -15}, // 0x49 'I' 200 | {773, 8, 16, 9, 0, -15}, // 0x4A 'J' 201 | {789, 16, 16, 17, 1, -15}, // 0x4B 'K' 202 | {821, 15, 16, 15, 0, -15}, // 0x4C 'L' 203 | {851, 19, 16, 21, 1, -15}, // 0x4D 'M' 204 | {889, 16, 16, 17, 1, -15}, // 0x4E 'N' 205 | {921, 15, 16, 17, 1, -15}, // 0x4F 'O' 206 | {951, 12, 16, 14, 0, -15}, // 0x50 'P' 207 | {975, 16, 20, 17, 1, -15}, // 0x51 'Q' 208 | {1015, 15, 16, 16, 0, -15}, // 0x52 'R' 209 | {1045, 11, 16, 13, 0, -15}, // 0x53 'S' 210 | {1067, 15, 16, 15, 0, -15}, // 0x54 'T' 211 | {1097, 16, 16, 17, 1, -15}, // 0x55 'U' 212 | {1129, 17, 16, 17, 0, -15}, // 0x56 'V' 213 | {1163, 22, 16, 23, 0, -15}, // 0x57 'W' 214 | {1207, 17, 16, 17, 0, -15}, // 0x58 'X' 215 | {1241, 16, 16, 17, 0, -15}, // 0x59 'Y' 216 | {1273, 14, 16, 15, 1, -15}, // 0x5A 'Z' 217 | {1301, 5, 20, 8, 2, -15}, // 0x5B '[' 218 | {1314, 7, 17, 7, 0, -16}, // 0x5C '\' 219 | {1329, 5, 20, 8, 1, -15}, // 0x5D ']' 220 | {1342, 10, 9, 11, 1, -15}, // 0x5E '^' 221 | {1354, 12, 1, 12, 0, 3}, // 0x5F '_' 222 | {1356, 5, 4, 6, 0, -15}, // 0x60 '`' 223 | {1359, 10, 11, 10, 1, -10}, // 0x61 'a' 224 | {1373, 10, 17, 12, 1, -16}, // 0x62 'b' 225 | {1395, 8, 11, 11, 1, -10}, // 0x63 'c' 226 | {1406, 10, 17, 12, 1, -16}, // 0x64 'd' 227 | {1428, 10, 11, 11, 1, -10}, // 0x65 'e' 228 | {1442, 9, 17, 9, 0, -16}, // 0x66 'f' 229 | {1462, 12, 16, 11, 0, -10}, // 0x67 'g' 230 | {1486, 11, 17, 12, 0, -16}, // 0x68 'h' 231 | {1510, 5, 16, 7, 0, -15}, // 0x69 'i' 232 | {1520, 6, 21, 8, 0, -15}, // 0x6A 'j' 233 | {1536, 11, 17, 12, 1, -16}, // 0x6B 'k' 234 | {1560, 5, 17, 6, 0, -16}, // 0x6C 'l' 235 | {1571, 18, 11, 19, 0, -10}, // 0x6D 'm' 236 | {1596, 11, 11, 12, 0, -10}, // 0x6E 'n' 237 | {1612, 10, 11, 12, 1, -10}, // 0x6F 'o' 238 | {1626, 11, 16, 12, 0, -10}, // 0x70 'p' 239 | {1648, 10, 16, 12, 1, -10}, // 0x71 'q' 240 | {1668, 8, 11, 8, 0, -10}, // 0x72 'r' 241 | {1679, 7, 11, 9, 1, -10}, // 0x73 's' 242 | {1689, 6, 13, 7, 1, -12}, // 0x74 't' 243 | {1699, 10, 11, 12, 1, -10}, // 0x75 'u' 244 | {1713, 11, 11, 11, 0, -10}, // 0x76 'v' 245 | {1729, 16, 11, 16, 0, -10}, // 0x77 'w' 246 | {1751, 11, 11, 12, 0, -10}, // 0x78 'x' 247 | {1767, 11, 16, 11, 0, -10}, // 0x79 'y' 248 | {1789, 10, 11, 10, 0, -10}, // 0x7A 'z' 249 | {1803, 5, 21, 12, 2, -16}, // 0x7B '{' 250 | {1817, 1, 17, 5, 2, -16}, // 0x7C '|' 251 | {1820, 5, 21, 12, 5, -15}, // 0x7D '}' 252 | {1834, 12, 3, 12, 0, -6}}; // 0x7E '~' 253 | 254 | const GFXfont FreeSerif12pt7b PROGMEM = {(uint8_t *)FreeSerif12pt7bBitmaps, 255 | (GFXglyph *)FreeSerif12pt7bGlyphs, 256 | 0x20, 0x7E, 29}; 257 | 258 | // Approx. 2511 bytes 259 | --------------------------------------------------------------------------------