From 6a9b7199ecd457b49a9e51abcdea46e56bce4a85 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 4 Feb 2023 16:44:29 -0600 Subject: [PATCH 01/16] Initial gif proof of concept --- py/circuitpy_defns.mk | 7 + shared-bindings/displayio/OnDiskGif.c | 191 ++++ shared-bindings/displayio/OnDiskGif.h | 45 + shared-bindings/displayio/__init__.c | 2 + .../displayio/AnimatedGIF_circuitpy.h | 178 ++++ shared-module/displayio/OnDiskGif.c | 195 ++++ shared-module/displayio/OnDiskGif.h | 54 + shared-module/displayio/gif.c | 993 ++++++++++++++++++ 8 files changed, 1665 insertions(+) create mode 100644 shared-bindings/displayio/OnDiskGif.c create mode 100644 shared-bindings/displayio/OnDiskGif.h create mode 100644 shared-module/displayio/AnimatedGIF_circuitpy.h create mode 100644 shared-module/displayio/OnDiskGif.c create mode 100644 shared-module/displayio/OnDiskGif.h create mode 100644 shared-module/displayio/gif.c diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index be33d80a9991f..6c8a513d8f300 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -579,6 +579,8 @@ SRC_SHARED_MODULE_ALL = \ displayio/Group.c \ displayio/I2CDisplay.c \ displayio/OnDiskBitmap.c \ + displayio/OnDiskGif.c \ + displayio/gif.c \ displayio/Palette.c \ displayio/Shape.c \ displayio/TileGrid.c \ @@ -697,6 +699,11 @@ SRC_MOD += $(addprefix lib/protomatter/src/, \ $(BUILD)/lib/protomatter/src/core.o: CFLAGS += -include "shared-module/rgbmatrix/allocator.h" -DCIRCUITPY -Wno-missing-braces -Wno-missing-prototypes endif +#SRC_MOD += $(addprefix lib/AnimatedGIF/, \ + #gif.c \ +#) +#$(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -Wno-missing-braces -Wno-missing-prototypes + ifeq ($(CIRCUITPY_ZLIB),1) SRC_MOD += $(addprefix lib/uzlib/, \ tinflate.c \ diff --git a/shared-bindings/displayio/OnDiskGif.c b/shared-bindings/displayio/OnDiskGif.c new file mode 100644 index 0000000000000..eeade372cb9c7 --- /dev/null +++ b/shared-bindings/displayio/OnDiskGif.c @@ -0,0 +1,191 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/displayio/OnDiskGif.h" + +#include + +#include "py/runtime.h" +#include "py/objproperty.h" +#include "supervisor/shared/translate/translate.h" +#include "shared-bindings/displayio/OnDiskGif.h" + +//| class OnDiskBitmap: +//| """Loads values straight from disk. This minimizes memory use but can lead to +//| much slower pixel load times. These load times may result in frame tearing where only part of +//| the image is visible. +//| +//| It's easiest to use on a board with a built in display such as the `Hallowing M0 Express +//| `_. +//| +//| .. code-block:: Python +//| +//| import board +//| import displayio +//| import time +//| import pulseio +//| +//| board.DISPLAY.brightness = 0 +//| splash = displayio.Group() +//| board.DISPLAY.show(splash) +//| +//| odb = displayio.OnDiskBitmap('/sample.bmp') +//| face = displayio.TileGrid(odb, pixel_shader=odb.pixel_shader) +//| splash.append(face) +//| # Wait for the image to load. +//| board.DISPLAY.refresh(target_frames_per_second=60) +//| +//| # Fade up the backlight +//| for i in range(100): +//| board.DISPLAY.brightness = 0.01 * i +//| time.sleep(0.05) +//| +//| # Wait forever +//| while True: +//| pass""" +//| +//| def __init__(self, file: Union[str, typing.BinaryIO]) -> None: +//| """Create an OnDiskBitmap object with the given file. +//| +//| :param file file: The name of the bitmap file. For backwards compatibility, a file opened in binary mode may also be passed. +//| +//| Older versions of CircuitPython required a file opened in binary +//| mode. CircuitPython 7.0 modified OnDiskBitmap so that it takes a +//| filename instead, and opens the file internally. A future version +//| of CircuitPython will remove the ability to pass in an opened file. +//| """ +//| ... +STATIC mp_obj_t displayio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + mp_obj_t arg = all_args[0]; + + if (mp_obj_is_str(arg)) { + arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb)); + } + + if (!mp_obj_is_type(arg, &mp_type_fileio)) { + mp_raise_TypeError(translate("file must be a file opened in byte mode")); + } + + displayio_ondiskgif_t *self = m_new_obj(displayio_ondiskgif_t); + self->base.type = &displayio_ondiskgif_type; + common_hal_displayio_ondiskgif_construct(self, MP_OBJ_TO_PTR(arg)); + + return MP_OBJ_FROM_PTR(self); +} + +//| width: int +//| """Width of the bitmap. (read only)""" +STATIC mp_obj_t displayio_ondiskgif_obj_get_width(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_width(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_width_obj, displayio_ondiskgif_obj_get_width); + +MP_PROPERTY_GETTER(displayio_ondiskgif_width_obj, + (mp_obj_t)&displayio_ondiskgif_get_width_obj); + +//| height: int +//| """Height of the bitmap. (read only)""" +STATIC mp_obj_t displayio_ondiskgif_obj_get_height(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_height(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_height_obj, displayio_ondiskgif_obj_get_height); + +MP_PROPERTY_GETTER(displayio_ondiskgif_height_obj, + (mp_obj_t)&displayio_ondiskgif_get_height_obj); + +//| pixel_shader: Union[ColorConverter, Palette] +//| """The image's pixel_shader. The type depends on the underlying +//| bitmap's structure. The pixel shader can be modified (e.g., to set the +//| transparent pixel or, for palette shaded images, to update the palette.)""" +//| +STATIC mp_obj_t displayio_ondiskgif_obj_get_pixel_shader(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_displayio_ondiskgif_get_pixel_shader(self); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_pixel_shader_obj, displayio_ondiskgif_obj_get_pixel_shader); + +const mp_obj_property_t displayio_ondiskgif_pixel_shader_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&displayio_ondiskgif_get_pixel_shader_obj, + (mp_obj_t)MP_ROM_NONE, + (mp_obj_t)MP_ROM_NONE}, +}; + +//| bitmap: Bitmap +//| """The image's bitmap. The type depends on the underlying +//| bitmap's structure. The pixel shader can be modified (e.g., to set the +//| transparent pixel or, for palette shaded images, to update the palette.)""" +//| +STATIC mp_obj_t displayio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_displayio_ondiskgif_get_bitmap(self); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_bitmap_obj, displayio_ondiskgif_obj_get_bitmap); + +const mp_obj_property_t displayio_ondiskgif_bitmap_obj = { + .base.type = &mp_type_property, + .proxy = {(mp_obj_t)&displayio_ondiskgif_get_bitmap_obj, + (mp_obj_t)MP_ROM_NONE, + (mp_obj_t)MP_ROM_NONE}, +}; + + +//| play_frame: None +//| """Play next frame. (read only)""" +//| +STATIC mp_obj_t displayio_ondiskgif_obj_play_frame(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_play_frame(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_play_frame_obj, displayio_ondiskgif_obj_play_frame); + + +STATIC const mp_rom_map_elem_t displayio_ondiskgif_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&displayio_ondiskgif_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixel_shader), MP_ROM_PTR(&displayio_ondiskgif_pixel_shader_obj) }, + { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&displayio_ondiskgif_bitmap_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&displayio_ondiskgif_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_play_frame), MP_ROM_PTR(&displayio_ondiskgif_play_frame_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(displayio_ondiskgif_locals_dict, displayio_ondiskgif_locals_dict_table); + +const mp_obj_type_t displayio_ondiskgif_type = { + { &mp_type_type }, + .name = MP_QSTR_OnDiskGif, + .make_new = displayio_ondiskgif_make_new, + .locals_dict = (mp_obj_dict_t *)&displayio_ondiskgif_locals_dict, +}; diff --git a/shared-bindings/displayio/OnDiskGif.h b/shared-bindings/displayio/OnDiskGif.h new file mode 100644 index 0000000000000..988bc8581bd9d --- /dev/null +++ b/shared-bindings/displayio/OnDiskGif.h @@ -0,0 +1,45 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H +#define MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H + +#include "shared-module/displayio/OnDiskGif.h" +#include "extmod/vfs_fat.h" + +extern const mp_obj_type_t displayio_ondiskgif_type; + +void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_file_obj_t *file); + +uint32_t common_hal_displayio_ondiskgif_get_pixel(displayio_ondiskgif_t *bitmap, + int16_t x, int16_t y); + +uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self); +mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t *self); +mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self); +uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self); +uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self); +#endif // MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H diff --git a/shared-bindings/displayio/__init__.c b/shared-bindings/displayio/__init__.c index 3fcd1d082b2fa..4f2905e2e2576 100644 --- a/shared-bindings/displayio/__init__.c +++ b/shared-bindings/displayio/__init__.c @@ -39,6 +39,7 @@ #include "shared-bindings/displayio/Group.h" #include "shared-bindings/displayio/I2CDisplay.h" #include "shared-bindings/displayio/OnDiskBitmap.h" +#include "shared-bindings/displayio/OnDiskGif.h" #include "shared-bindings/displayio/Palette.h" #if CIRCUITPY_PARALLELDISPLAY #include "shared-bindings/paralleldisplay/ParallelBus.h" @@ -85,6 +86,7 @@ STATIC const mp_rom_map_elem_t displayio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_EPaperDisplay), MP_ROM_PTR(&displayio_epaperdisplay_type) }, { MP_ROM_QSTR(MP_QSTR_Group), MP_ROM_PTR(&displayio_group_type) }, { MP_ROM_QSTR(MP_QSTR_OnDiskBitmap), MP_ROM_PTR(&displayio_ondiskbitmap_type) }, + { MP_ROM_QSTR(MP_QSTR_OnDiskGif), MP_ROM_PTR(&displayio_ondiskgif_type) }, { MP_ROM_QSTR(MP_QSTR_Palette), MP_ROM_PTR(&displayio_palette_type) }, { MP_ROM_QSTR(MP_QSTR_Shape), MP_ROM_PTR(&displayio_shape_type) }, { MP_ROM_QSTR(MP_QSTR_TileGrid), MP_ROM_PTR(&displayio_tilegrid_type) }, diff --git a/shared-module/displayio/AnimatedGIF_circuitpy.h b/shared-module/displayio/AnimatedGIF_circuitpy.h new file mode 100644 index 0000000000000..f81108695c012 --- /dev/null +++ b/shared-module/displayio/AnimatedGIF_circuitpy.h @@ -0,0 +1,178 @@ +// Copyright 2020 BitBank Software, Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// =========================================================================== + +#ifndef __ANIMATEDGIF__ +#define __ANIMATEDGIF__ +#include +#include +#include +#include +// +// GIF Animator +// Written by Larry Bank +// Copyright (c) 2020 BitBank Software, Inc. +// bitbank@pobox.com +// +// Designed to decode images up to 480x320 +// using less than 22K of RAM +// + +/* GIF Defines and variables */ +#define MAX_CHUNK_SIZE 255 +#define LZW_BUF_SIZE (6 * MAX_CHUNK_SIZE) +#define LZW_HIGHWATER (4 * MAX_CHUNK_SIZE) +#define MAX_WIDTH 320 +#define FILE_BUF_SIZE 4096 + +#define PIXEL_FIRST 0 +#define PIXEL_LAST 4096 +#define LINK_UNUSED 5911 // 0x1717 to use memset +#define LINK_END 5912 +#define MAX_HASH 5003 +#define MAXMAXCODE 4096 + +enum { + GIF_PALETTE_RGB565_LE = 0, // little endian (default) + GIF_PALETTE_RGB565_BE, // big endian + GIF_PALETTE_RGB888 // original 24-bpp entries +}; +// for compatibility with older code +#define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE +#define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE +// +// Draw callback pixel type +// RAW = 8-bit palettized pixels requiring transparent pixel handling +// COOKED = 16 or 24-bpp fully rendered pixels ready for display +// +enum { + GIF_DRAW_RAW = 0, + GIF_DRAW_COOKED +}; + +enum { + GIF_SUCCESS = 0, + GIF_DECODE_ERROR, + GIF_TOO_WIDE, + GIF_INVALID_PARAMETER, + GIF_UNSUPPORTED_FEATURE, + GIF_FILE_NOT_OPEN, + GIF_EARLY_EOF, + GIF_EMPTY_FRAME, + GIF_BAD_FILE, + GIF_ERROR_MEMORY +}; + +typedef struct gif_file_tag +{ + int32_t iPos; // current file position + int32_t iSize; // file size + uint8_t *pData; // memory file pointer + void *fHandle; // class pointer to File/SdFat or whatever you want +} GIFFILE; + +typedef struct gif_info_tag +{ + int32_t iFrameCount; // total frames in file + int32_t iDuration; // duration of animation in milliseconds + int32_t iMaxDelay; // maximum frame delay + int32_t iMinDelay; // minimum frame delay +} GIFINFO; + +typedef struct gif_draw_tag +{ + int iX, iY; // Corner offset of this frame on the canvas + int y; // current line being drawn (0 = top line of image) + int iWidth, iHeight; // size of this frame + void *pUser; // user supplied pointer + uint8_t *pPixels; // 8-bit source pixels for this line + uint16_t *pPalette; // little or big-endian RGB565 palette entries (default) + uint8_t *pPalette24; // RGB888 palette (optional) + uint8_t ucTransparent; // transparent color + uint8_t ucHasTransparency; // flag indicating the transparent color is in use + uint8_t ucDisposalMethod; // frame disposal method + uint8_t ucBackground; // background color + uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used +} GIFDRAW; + +// Callback function prototypes +typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); +typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition); +typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw); +typedef void * (GIF_OPEN_CALLBACK)(const char *szFilename, int32_t *pFileSize); +typedef void (GIF_CLOSE_CALLBACK)(void *pHandle); +typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize); +typedef void (GIF_FREE_CALLBACK)(void *buffer); +// +// our private structure to hold a GIF image decode state +// +typedef struct gif_image_tag +{ + int iWidth, iHeight, iCanvasWidth, iCanvasHeight; + int iX, iY; // GIF corner offset + int iBpp; + int iError; // last error + int iFrameDelay; // delay in milliseconds for this frame + int iRepeatCount; // NETSCAPE animation repeat count. 0=forever + int iXCount, iYCount; // decoding position in image (countdown values) + int iLZWOff; // current LZW data offset + int iLZWSize; // current quantity of data in the LZW buffer + int iCommentPos; // file offset of start of comment data + short sCommentLen; // length of comment + GIF_READ_CALLBACK *pfnRead; + GIF_SEEK_CALLBACK *pfnSeek; + GIF_DRAW_CALLBACK *pfnDraw; + GIF_OPEN_CALLBACK *pfnOpen; + GIF_CLOSE_CALLBACK *pfnClose; + GIFFILE GIFFile; + void *pUser; + unsigned char *pFrameBuffer; + unsigned char *pPixels, *pOldPixels; + unsigned char ucLineBuf[MAX_WIDTH]; // current line + unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack + unsigned short pPalette[384]; // can hold RGB565 or RGB888 - set in begin() + unsigned short pLocalPalette[384]; // color palettes for GIF images + unsigned char ucLZW[LZW_BUF_SIZE]; // holds 6 chunks (6x255) of GIF LZW data packed together + unsigned short usGIFTable[4096]; + unsigned char ucGIFPixels[8192]; + unsigned char bEndOfFrame; + unsigned char ucGIFBits, ucBackground, ucTransparent, ucCodeStart, ucMap, bUseLocalPalette; + unsigned char ucPaletteType; // RGB565 or RGB888 + unsigned char ucDrawType; // RAW or COOKED +} GIFIMAGE; + +// C interface +int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); +int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw); +void GIF_close(GIFIMAGE *pGIF); +void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType); +void GIF_reset(GIFIMAGE *pGIF); +int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser); +int GIF_getCanvasWidth(GIFIMAGE *pGIF); +int GIF_getCanvasHeight(GIFIMAGE *pGIF); +int GIF_getComment(GIFIMAGE *pGIF, char *destBuffer); +int GIF_getInfo(GIFIMAGE *pGIF, GIFINFO *pInfo); +int GIF_getLastError(GIFIMAGE *pGIF); +int GIF_getLoopCount(GIFIMAGE *pGIF); +int GIF_init(GIFIMAGE *pGIF); +void GIF_setDrawCallback(GIFIMAGE *pGIF, GIF_DRAW_CALLBACK *pfnDraw); +void GIF_scaleHalf(uint16_t *pCurrent, uint16_t *pPrev, int iWidth, int bBigEndian); + +// Due to unaligned memory causing an exception, we have to do these macros the slow way +#define INTELSHORT(p) ((*p) + (*(p + 1) << 8)) +#define INTELLONG(p) ((*p) + (*(p + 1) << 8) + (*(p + 2) << 16) + (*(p + 3) << 24)) +#define MOTOSHORT(p) (((*(p)) << 8) + (*(p + 1))) +#define MOTOLONG(p) (((*p) << 24) + ((*(p + 1)) << 16) + ((*(p + 2)) << 8) + (*(p + 3))) + +// Must be a 32-bit target processor +#define REGISTER_WIDTH 32 + +#endif // __ANIMATEDGIF__ diff --git a/shared-module/displayio/OnDiskGif.c b/shared-module/displayio/OnDiskGif.c new file mode 100644 index 0000000000000..88146d354eb72 --- /dev/null +++ b/shared-module/displayio/OnDiskGif.c @@ -0,0 +1,195 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/displayio/OnDiskGif.h" +#include "shared-bindings/displayio/ColorConverter.h" +#include "shared-bindings/displayio/Palette.h" +#include "shared-bindings/displayio/Bitmap.h" +#include "shared-module/displayio/ColorConverter.h" +#include "shared-module/displayio/Palette.h" + +#include + +#include "py/mperrno.h" +#include "py/runtime.h" + +static int32_t GIFReadFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { + // mp_printf(&mp_plat_print, "GifReadFile len %d ", iLen); + uint32_t iBytesRead; + iBytesRead = iLen; + pyb_file_obj_t *f = pFile->fHandle; + // Note: If you read a file all the way to the last byte, seek() stops working + if ((pFile->iSize - pFile->iPos) < iLen) { + iBytesRead = pFile->iSize - pFile->iPos - 1; // <-- ugly work-around + } + if (iBytesRead <= 0) { + return 0; + } + UINT bytes_read; + if (f_read(&f->fp, pBuf, iBytesRead, &bytes_read) != FR_OK) { + mp_raise_OSError(MP_EIO); + } + pFile->iPos = f->fp.fptr; + // mp_printf(&mp_plat_print, " now at %d\n", pFile->iPos); + + return bytes_read; +} /* GIFReadFile() */ + +static int32_t GIFSeekFile(GIFFILE *pFile, int32_t iPosition) { + // mp_printf(&mp_plat_print, "GifSeekFile %d ", iPosition); + pyb_file_obj_t *f = pFile->fHandle; + + f_lseek(&f->fp, iPosition); + pFile->iPos = f->fp.fptr; + // mp_printf(&mp_plat_print, " now at %d\n", pFile->iPos); + return pFile->iPos; +} /* GIFSeekFile() */ + +static void GIFDraw(GIFDRAW *pDraw) { + // Called for every scan line of the image as it decodes + // The pixels delivered are the 8-bit native GIF output + // The palette is either RGB565 or the original 24-bit RGB values + // depending on the pixel type selected with gif.begin() + + displayio_bitmap_t *bitmap = (displayio_bitmap_t *)pDraw->pUser; + + // mp_printf(&mp_plat_print, "GD: y%d iX%d iY%d iW%d iH%d Trans%d hasT%d bk%d pUser %x bmp%x\n", + // pDraw->y, pDraw->iX, pDraw->iY, pDraw->iWidth, pDraw->iHeight, pDraw->ucTransparent, pDraw->ucHasTransparency, pDraw->ucBackground, pDraw->pUser, bitmap->data); + +/* + int iX, iY; // Corner offset of this frame on the canvas + int y; // current line being drawn (0 = top line of image) + int iWidth, iHeight; // size of this frame + void *pUser; // user supplied pointer + uint8_t *pPixels; // 8-bit source pixels for this line + uint16_t *pPalette; // little or big-endian RGB565 palette entries (default) + uint8_t *pPalette24; // RGB888 palette (optional) + uint8_t ucTransparent; // transparent color + uint8_t ucHasTransparency; // flag indicating the transparent color is in use + uint8_t ucDisposalMethod; // frame disposal method + uint8_t ucBackground; // background color + uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used +*/ + + // For all other lines, just push the pixels to the display + // (uint8_t *)pDraw->pPixels, pDraw->iWidth*2); + + + + int32_t row_start = pDraw->y * bitmap->stride; + uint32_t *row = bitmap->data + row_start; + uint8_t *d = (uint8_t *)row; + // mp_printf(&mp_plat_print, "rs %d strd %d:\n", row_start, bitmap->stride); + + uint8_t *p = (uint8_t *)pDraw->pPixels; + for (int w = 0; w < pDraw->iWidth * 2; w++) { + // mp_printf(&mp_plat_print, "%x:", *p); + *d++ = *p; + p++; + } + // mp_printf(&mp_plat_print, "\n"); +} + +void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_file_obj_t *file) { + // mp_printf(&mp_plat_print, "Begin OnDiskGif\n"); + self->file = file; + + GIF_begin(&self->gif, GIF_PALETTE_RGB565_LE); + + self->gif.iError = GIF_SUCCESS; + self->gif.pfnRead = GIFReadFile; + self->gif.pfnSeek = GIFSeekFile; + self->gif.pfnDraw = GIFDraw; + self->gif.pfnClose = NULL; + self->gif.pfnOpen = NULL; + self->gif.GIFFile.fHandle = self->file; + + f_rewind(&self->file->fp); + self->gif.GIFFile.iSize = (int32_t)f_size(&self->file->fp); + + int result = GIF_init(&self->gif); + if (result != 1) { + mp_arg_error_invalid(MP_QSTR_file); + } + + self->frame = m_malloc(self->gif.iCanvasWidth * self->gif.iCanvasHeight * sizeof(uint16_t), false); // MUST FREE LATER? + self->gif.pFrameBuffer = (uint8_t *)self->frame; + self->gif.ucDrawType = GIF_DRAW_COOKED; + + displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t); + bitmap->base.type = &displayio_bitmap_type; + common_hal_displayio_bitmap_construct(bitmap, self->gif.iCanvasWidth, self->gif.iCanvasHeight, 16); + self->bitmap = bitmap; + + // mp_printf(&mp_plat_print, "GIF_init returned %d %x\n", result, bitmap->data); +} + + +uint32_t common_hal_displayio_ondiskgif_get_pixel(displayio_ondiskgif_t *self, + int16_t x, int16_t y) { + if (x < 0 || x >= self->gif.iCanvasWidth || y < 0 || y >= self->gif.iCanvasHeight) { + return 0; + } + + return 0; +} + +uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self) { + GIFINFO info; + GIF_getInfo(&self->gif, &info); + mp_printf(&mp_plat_print, "dur %d fc %d max %d min %d\n", info.iDuration, info.iFrameCount, info.iMaxDelay, info.iMinDelay); + + return (uint16_t)self->gif.iCanvasHeight; +} + +uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self) { + return (uint16_t)self->gif.iCanvasWidth; +} + +mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t *self) { + return MP_OBJ_FROM_PTR(self->pixel_shader_base); +} + +mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self) { + return MP_OBJ_FROM_PTR(self->bitmap); +} + +uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self) { + int result = GIF_playFrame(&self->gif, 0, self->bitmap); + + if (result >= 0) { + displayio_area_t dirty_area = { + .x1 = 0, + .y1 = 0, + .x2 = self->bitmap->width, + .y2 = self->bitmap->height, + }; + + displayio_bitmap_set_dirty_area(self->bitmap, &dirty_area); + } + + return result; +} diff --git a/shared-module/displayio/OnDiskGif.h b/shared-module/displayio/OnDiskGif.h new file mode 100644 index 0000000000000..008635bc66ac3 --- /dev/null +++ b/shared-module/displayio/OnDiskGif.h @@ -0,0 +1,54 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef MICROPY_INCLUDED_SHARED_MODULE_DISPLAYIO_ONDISKGIF_H +#define MICROPY_INCLUDED_SHARED_MODULE_DISPLAYIO_ONDISKGIF_H + +#include +#include + +#include "py/obj.h" + +// #include "lib/AnimatedGIF/AnimatedGIF_circuitpy.h" +#include "AnimatedGIF_circuitpy.h" +#include "Bitmap.h" + +#include "extmod/vfs_fat.h" + +typedef struct { + mp_obj_base_t base; + GIFIMAGE gif; + pyb_file_obj_t *file; + uint32_t *frame; + displayio_bitmap_t *bitmap; + union { + mp_obj_base_t *pixel_shader_base; + struct displayio_palette *palette; + struct displayio_colorconverter *colorconverter; + }; +} displayio_ondiskgif_t; + +#endif // MICROPY_INCLUDED_SHARED_MODULE_DISPLAYIO_ONDISKGIF_H diff --git a/shared-module/displayio/gif.c b/shared-module/displayio/gif.c new file mode 100644 index 0000000000000..a4ad31608da41 --- /dev/null +++ b/shared-module/displayio/gif.c @@ -0,0 +1,993 @@ +// +// GIF Animator +// written by Larry Bank +// bitbank@pobox.com +// Arduino port started 7/5/2020 +// Original GIF code written 20+ years ago :) +// The goal of this code is to decode images up to 480x320 +// using no more than 22K of RAM (if sent directly to an LCD display) +// +// Copyright 2020 BitBank Software, Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// =========================================================================== +#define __LINUX__ +#include "AnimatedGIF_circuitpy.h" + +#include "py/mperrno.h" +#include "py/runtime.h" + + +#ifdef HAL_ESP32_HAL_H_ +#define memcpy_P memcpy +#endif + +static const unsigned char cGIFBits[9] = {1,4,4,4,8,8,8,8,8}; // convert odd bpp values to ones we can handle + +// forward references +static int GIFInit(GIFIMAGE *pGIF); +static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly); +static int GIFGetMoreData(GIFIMAGE *pPage); +static void GIFMakePels(GIFIMAGE *pPage, unsigned int code); +static int DecodeLZW(GIFIMAGE *pImage, int iOptions); +static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); +static int32_t seekMem(GIFFILE *pFile, int32_t iPosition); +int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo); +#if defined(PICO_BUILD) || defined(__LINUX__) || defined(__MCUXPRESSO) +static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); +static int32_t seekFile(GIFFILE *pFile, int32_t iPosition); +static void closeFile(void *handle); + +// C API +int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) { + pGIF->iError = GIF_SUCCESS; + pGIF->pfnRead = readMem; + pGIF->pfnSeek = seekMem; + pGIF->pfnDraw = pfnDraw; + pGIF->pfnOpen = NULL; + pGIF->pfnClose = NULL; + pGIF->GIFFile.iSize = iDataSize; + pGIF->GIFFile.pData = pData; + return GIFInit(pGIF); +} /* GIF_openRAM() */ + +#ifdef __LINUX__ +int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw) { + pGIF->iError = GIF_SUCCESS; + pGIF->pfnRead = readFile; + pGIF->pfnSeek = seekFile; + pGIF->pfnDraw = pfnDraw; + pGIF->pfnOpen = NULL; + pGIF->pfnClose = closeFile; + pGIF->GIFFile.fHandle = fopen(szFilename, "r+b"); + if (pGIF->GIFFile.fHandle == NULL) { + return 0; + } + fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_END); + pGIF->GIFFile.iSize = (int)ftell((FILE *)pGIF->GIFFile.fHandle); + fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_SET); + return GIFInit(pGIF); +} /* GIF_openFile() */ +#endif + +void GIF_close(GIFIMAGE *pGIF) { + if (pGIF->pfnClose) { + (*pGIF->pfnClose)(pGIF->GIFFile.fHandle); + } +} /* GIF_close() */ + +void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType) { + memset(pGIF, 0, sizeof(GIFIMAGE)); + pGIF->ucPaletteType = ucPaletteType; +} /* GIF_begin() */ + +void GIF_reset(GIFIMAGE *pGIF) { + (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); +} /* GIF_reset() */ + +// +// Return value: +// 1 = good decode, more frames exist +// 0 = good decode, no more frames +// -1 = error +// +int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser) { + int rc; + + if (delayMilliseconds) { + *delayMilliseconds = 0; // clear any old valid + } + if (pGIF->GIFFile.iPos >= pGIF->GIFFile.iSize - 1) { // no more data exists + (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek to start + } + if (GIFParseInfo(pGIF, 0)) { + pGIF->pUser = pUser; + if (pGIF->iError == GIF_EMPTY_FRAME) { // don't try to decode it + return 0; + } + rc = DecodeLZW(pGIF, 0); + if (rc != 0) { // problem + return 0; + } + } else { + return 0; // error parsing the frame info, we may be at the end of the file + } + // Return 1 for more frames or 0 if this was the last frame + if (delayMilliseconds) { // if not NULL, return the frame delay time + *delayMilliseconds = pGIF->iFrameDelay; + } + return pGIF->GIFFile.iPos < pGIF->GIFFile.iSize - 1; +} /* GIF_playFrame() */ + +int GIF_getCanvasWidth(GIFIMAGE *pGIF) { + return pGIF->iCanvasWidth; +} /* GIF_getCanvasWidth() */ + +int GIF_getCanvasHeight(GIFIMAGE *pGIF) { + return pGIF->iCanvasHeight; +} /* GIF_getCanvasHeight() */ + +int GIF_getLoopCount(GIFIMAGE *pGIF) { + return pGIF->iRepeatCount; +} /* GIF_getLoopCount() */ + +int GIF_getComment(GIFIMAGE *pGIF, char *pDest) { + int32_t iOldPos; + + iOldPos = pGIF->GIFFile.iPos; // keep old position + (*pGIF->pfnSeek)(&pGIF->GIFFile, pGIF->iCommentPos); + (*pGIF->pfnRead)(&pGIF->GIFFile, (uint8_t *)pDest, pGIF->sCommentLen); + (*pGIF->pfnSeek)(&pGIF->GIFFile, iOldPos); + pDest[pGIF->sCommentLen] = 0; // zero terminate the string + return (int)pGIF->sCommentLen; + +} /* GIF_getComment() */ + +int GIF_getLastError(GIFIMAGE *pGIF) { + return pGIF->iError; +} /* GIF_getLastError() */ + +int GIF_init(GIFIMAGE *pGIF) { + return GIFInit(pGIF); +} + +#endif // !__cplusplus +// +// Helper functions for memory based images +// +static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { + int32_t iBytesRead; + + iBytesRead = iLen; + if ((pFile->iSize - pFile->iPos) < iLen) { + iBytesRead = pFile->iSize - pFile->iPos; + } + if (iBytesRead <= 0) { + return 0; + } + memmove(pBuf, &pFile->pData[pFile->iPos], iBytesRead); + pFile->iPos += iBytesRead; + return iBytesRead; +} /* readMem() */ + +/* +static int32_t readFLASH(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + + iBytesRead = iLen; + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos; + if (iBytesRead <= 0) + return 0; + memcpy_P(pBuf, &pFile->pData[pFile->iPos], iBytesRead); + pFile->iPos += iBytesRead; + return iBytesRead; +} *//* readFLASH() */ + +static int32_t seekMem(GIFFILE *pFile, int32_t iPosition) { + if (iPosition < 0) { + iPosition = 0; + } else if (iPosition >= pFile->iSize) { + iPosition = pFile->iSize - 1; + } + pFile->iPos = iPosition; + return iPosition; +} /* seekMem() */ + +#if defined(__LINUX__) || defined(__MCUXPRESSO) +static void closeFile(void *handle) { + fclose((FILE *)handle); +} /* closeFile() */ + +static int32_t seekFile(GIFFILE *pFile, int32_t iPosition) { + if (iPosition < 0) { + iPosition = 0; + } else if (iPosition >= pFile->iSize) { + iPosition = pFile->iSize - 1; + } + pFile->iPos = iPosition; + fseek((FILE *)pFile->fHandle, iPosition, SEEK_SET); + return iPosition; +} /* seekMem() */ + +static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { + int32_t iBytesRead; + + iBytesRead = iLen; + if ((pFile->iSize - pFile->iPos) < iLen) { + iBytesRead = pFile->iSize - pFile->iPos; + } + if (iBytesRead <= 0) { + return 0; + } + iBytesRead = (int)fread(pBuf, 1, iBytesRead, (FILE *)pFile->fHandle); + pFile->iPos += iBytesRead; + return iBytesRead; +} /* readFile() */ + +#endif // __LINUX__ +// +// The following functions are written in plain C and have no +// 3rd party dependencies, not even the C runtime library +// +// +// Initialize a GIF file and callback access from a file on SD or memory +// returns 1 for success, 0 for failure +// Fills in the canvas size of the GIFIMAGE structure +// +static int GIFInit(GIFIMAGE *pGIF) { + pGIF->GIFFile.iPos = 0; // start at beginning of file + if (!GIFParseInfo(pGIF, 1)) { // gather info for the first frame + return 0; // something went wrong; not a GIF file? + } + (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek back to start of the file + if (pGIF->iCanvasWidth > MAX_WIDTH) { // need to allocate more space + pGIF->iError = GIF_TOO_WIDE; + return 0; + } + return 1; +} /* GIFInit() */ + +// +// Parse the GIF header, gather the size and palette info +// If called with bInfoOnly set to true, it will test for a valid file +// and return the canvas size only +// Returns 1 for success, 0 for failure +// +static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { + int i, j, iColorTableBits; + int iBytesRead; + unsigned char c, *p; + int32_t iOffset = 0; + int32_t iStartPos = pPage->GIFFile.iPos; // starting file position + int iReadSize; + + pPage->bUseLocalPalette = 0; // assume no local palette + pPage->bEndOfFrame = 0; // we're just getting started + pPage->iFrameDelay = 0; // may not have a gfx extension block + pPage->iRepeatCount = -1; // assume NETSCAPE loop count is not specified + iReadSize = (bInfoOnly) ? 12 : MAX_CHUNK_SIZE; + // If you try to read past the EOF, the SD lib will return garbage data + if (iStartPos + iReadSize > pPage->GIFFile.iSize) { + iReadSize = (pPage->GIFFile.iSize - iStartPos - 1); + } + p = pPage->ucFileBuf; + iBytesRead = (*pPage->pfnRead)(&pPage->GIFFile, pPage->ucFileBuf, iReadSize); // 255 is plenty for now + + if (iBytesRead != iReadSize) { // we're at the end of the file + pPage->iError = GIF_EARLY_EOF; + return 0; + } + if (iStartPos == 0) { // start of the file + // canvas size + if (memcmp(p, "GIF89", 5) != 0 && memcmp(p, "GIF87", 5) != 0) { // not a GIF file + pPage->iError = GIF_BAD_FILE; + return 0; + } + pPage->iCanvasWidth = pPage->iWidth = INTELSHORT(&p[6]); + pPage->iCanvasHeight = pPage->iHeight = INTELSHORT(&p[8]); + pPage->iBpp = ((p[10] & 0x70) >> 4) + 1; + if (bInfoOnly) { + return 1; // we've got the info we needed, leave + } + iColorTableBits = (p[10] & 7) + 1; // Log2(size) of the color table + pPage->ucBackground = p[11]; // background color + pPage->ucGIFBits = 0; + iOffset = 13; + if (p[10] & 0x80) { // global color table? + // by default, convert to byte-reversed RGB565 for immediate use + // Read enough additional data for the color table + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], 3 * (1 << iColorTableBits)); + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { + for (i = 0; i < (1 << iColorTableBits); i++) + { + uint16_t usRGB565; + usRGB565 = ((p[iOffset] >> 3) << 11); // R + usRGB565 |= ((p[iOffset + 1] >> 2) << 5); // G + usRGB565 |= (p[iOffset + 2] >> 3); // B + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) { + pPage->pPalette[i] = usRGB565; + } else { + pPage->pPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first + } + iOffset += 3; + } + } else { // just copy it as-is + memcpy(pPage->pPalette, &p[iOffset], (1 << iColorTableBits) * 3); + iOffset += (1 << iColorTableBits) * 3; + } + } + } + while (p[iOffset] != ',' && p[iOffset] != ';') { /* Wait for image separator */ + if (p[iOffset] == '!') { /* Extension block */ + iOffset++; + switch (p[iOffset++]) /* Block type */ + { + case 0xf9: /* Graphic extension */ + if (p[iOffset] == 4) { // correct length + pPage->ucGIFBits = p[iOffset + 1]; // packed fields + pPage->iFrameDelay = (INTELSHORT(&p[iOffset + 2])) * 10; // delay in ms + if (pPage->iFrameDelay <= 1) { // 0-1 is going to make it run at 60fps; use 100 (10fps) as a reasonable substitute + pPage->iFrameDelay = 100; + } + if (pPage->ucGIFBits & 1) { // transparent color is used + pPage->ucTransparent = p[iOffset + 4]; // transparent color index + } + iOffset += 6; + } + // else // error + break; + case 0xff: /* App extension */ + c = 1; + while (c) { /* Skip all data sub-blocks */ + c = p[iOffset++]; /* Block length */ + if ((iBytesRead - iOffset) < (c + 32)) { // need to read more data first + memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead - iOffset)); // move existing data down + iBytesRead -= iOffset; + iStartPos += iOffset; + iOffset = 0; + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c + 32); + } + if (c == 11) { // fixed block length + // Netscape app block contains the repeat count + if (memcmp(&p[iOffset], "NETSCAPE2.0", 11) == 0) { + if (p[iOffset + 11] == 3 && p[iOffset + 12] == 1) { // loop count + pPage->iRepeatCount = INTELSHORT(&p[iOffset + 13]); + } + } + } + iOffset += (int)c; /* Skip to next sub-block */ + } + break; + case 0x01: /* Text extension */ + c = 1; + j = 0; + while (c) { /* Skip all data sub-blocks */ + c = p[iOffset++]; /* Block length */ + if (j == 0) { // use only first block + j = c; + if (j > 127) { // max comment length = 127 + j = 127; + } + // memcpy(pPage->szInfo1, &p[iOffset], j); + // pPage->szInfo1[j] = '\0'; + j = 1; + } + iOffset += (int)c; /* Skip this sub-block */ + } + break; + case 0xfe: /* Comment */ + c = 1; + while (c) { /* Skip all data sub-blocks */ + c = p[iOffset++]; /* Block length */ + if ((iBytesRead - iOffset) < (c + 32)) { // need to read more data first + memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead - iOffset)); // move existing data down + iBytesRead -= iOffset; + iStartPos += iOffset; + iOffset = 0; + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c + 32); + } + if (pPage->iCommentPos == 0) { // Save first block info + pPage->iCommentPos = iStartPos + iOffset; + pPage->sCommentLen = c; + } + iOffset += (int)c; /* Skip this sub-block */ + } + break; + default: + /* Bad header info */ + pPage->iError = GIF_DECODE_ERROR; + return 0; + } /* switch */ + } else { // invalid byte, stop decoding + if (pPage->GIFFile.iSize - iStartPos < 32) { // non-image bytes at end of file? + pPage->iError = GIF_EMPTY_FRAME; + } else { + /* Bad header info */ + pPage->iError = GIF_DECODE_ERROR; + } + return 0; + } + } /* while */ + if (p[iOffset] == ';') { // end of file, quit and return a correct error code + pPage->iError = GIF_EMPTY_FRAME; + return 1; + } + + if (p[iOffset] == ',') { + iOffset++; + } + // This particular frame's size and position on the main frame (if animated) + pPage->iX = INTELSHORT(&p[iOffset]); + pPage->iY = INTELSHORT(&p[iOffset + 2]); + pPage->iWidth = INTELSHORT(&p[iOffset + 4]); + pPage->iHeight = INTELSHORT(&p[iOffset + 6]); + iOffset += 8; + + /* Image descriptor + 7 6 5 4 3 2 1 0 M=0 - use global color map, ignore pixel + M I 0 0 0 pixel M=1 - local color map follows, use pixel + I=0 - Image in sequential order + I=1 - Image in interlaced order + pixel+1 = # bits per pixel for this image + */ + pPage->ucMap = p[iOffset++]; + if (pPage->ucMap & 0x80) { // local color table? + // by default, convert to byte-reversed RGB565 for immediate use + j = (1 << ((pPage->ucMap & 7) + 1)); + // Read enough additional data for the color table + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], j *3); + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { + for (i = 0; i < j; i++) + { + uint16_t usRGB565; + usRGB565 = ((p[iOffset] >> 3) << 11); // R + usRGB565 |= ((p[iOffset + 1] >> 2) << 5); // G + usRGB565 |= (p[iOffset + 2] >> 3); // B + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) { + pPage->pLocalPalette[i] = usRGB565; + } else { + pPage->pLocalPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first + } + iOffset += 3; + } + } else { // just copy it as-is + memcpy(pPage->pLocalPalette, &p[iOffset], j * 3); + iOffset += j * 3; + } + pPage->bUseLocalPalette = 1; + } + pPage->ucCodeStart = p[iOffset++]; /* initial code size */ + /* Since GIF can be 1-8 bpp, we only allow 1,4,8 */ + pPage->iBpp = cGIFBits[pPage->ucCodeStart]; + // we are re-using the same buffer turning GIF file data + // into "pure" LZW + pPage->iLZWSize = 0; // we're starting with no LZW data yet + c = 1; // get chunk length + while (c && iOffset < iBytesRead) { +// Serial.printf("iOffset=%d, iBytesRead=%d\n", iOffset, iBytesRead); + c = p[iOffset++]; // get chunk length +// Serial.printf("Chunk size = %d\n", c); + if (c <= (iBytesRead - iOffset)) { + memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], c); + pPage->iLZWSize += c; + iOffset += c; + } else { // partial chunk in our buffer + int iPartialLen = (iBytesRead - iOffset); + memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], iPartialLen); + pPage->iLZWSize += iPartialLen; + iOffset += iPartialLen; + (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c - iPartialLen); + pPage->iLZWSize += (c - iPartialLen); + } + if (c == 0) { + pPage->bEndOfFrame = 1; // signal not to read beyond the end of the frame + } + } +// seeking on an SD card is VERY VERY SLOW, so use the data we've already read by de-chunking it +// in this case, there's too much data, so we have to seek backwards a bit + if (iOffset < iBytesRead) { +// Serial.printf("Need to seek back %d bytes\n", iBytesRead - iOffset); + (*pPage->pfnSeek)(&pPage->GIFFile, iStartPos + iOffset); // position file to new spot + } + return 1; // we are now at the start of the chunk data +} /* GIFParseInfo() */ +// +// Gather info about an animated GIF file +// +int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { + int iOff, iNumFrames; + int iDelay, iMaxDelay, iMinDelay, iTotalDelay; + int iReadAmount; + int iDataAvailable = 0; + int iDataRemaining = 0; + uint32_t lFileOff = 0; + int bDone = 0; + int bExt; + uint8_t c, *cBuf; + + iMaxDelay = iTotalDelay = 0; + iMinDelay = 10000; + iNumFrames = 1; + iDataRemaining = pPage->GIFFile.iSize; + cBuf = (uint8_t *)pPage->ucFileBuf; + (*pPage->pfnSeek)(&pPage->GIFFile, 0); + iDataAvailable = (*pPage->pfnRead)(&pPage->GIFFile, cBuf, FILE_BUF_SIZE); + iDataRemaining -= iDataAvailable; + lFileOff += iDataAvailable; + iOff = 10; + c = cBuf[iOff]; // get info bits + iOff += 3; /* Skip flags, background color & aspect ratio */ + if (c & 0x80) { /* Deal with global color table */ + c &= 7; /* Get the number of colors defined */ + iOff += (2 << c) * 3; /* skip color table */ + } + while (!bDone) { // && iNumFrames < MAX_FRAMES) + bExt = 1; /* skip extension blocks */ + while (bExt && iOff < iDataAvailable) { + if ((iDataAvailable - iOff) < 258) { // need to read more data first + memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } + switch (cBuf[iOff]) + { + case 0x3b: /* End of file */ + /* we were fooled into thinking there were more pages */ + iNumFrames--; + goto gifpagesz; + // F9 = Graphic Control Extension (fixed length of 4 bytes) + // FE = Comment Extension + // FF = Application Extension + // 01 = Plain Text Extension + case 0x21: /* Extension block */ + if (cBuf[iOff + 1] == 0xf9 && cBuf[iOff + 2] == 4) { // Graphic Control Extension + // cBuf[iOff+3]; // page disposition flags + iDelay = cBuf[iOff + 4]; // delay low byte + iDelay |= ((uint16_t)(cBuf[iOff + 5]) << 8); // delay high byte + if (iDelay < 2) { // too fast, provide a default + iDelay = 2; + } + iDelay *= 10; // turn JIFFIES into milliseconds + iTotalDelay += iDelay; + if (iDelay > iMaxDelay) { + iMaxDelay = iDelay; + } else if (iDelay < iMinDelay) { + iMinDelay = iDelay; + } + // (cBuf[iOff+6]; // transparent color index + } + iOff += 2; /* skip to length */ + iOff += (int)cBuf[iOff]; /* Skip the data block */ + iOff++; + // block terminator or optional sub blocks + c = cBuf[iOff++]; /* Skip any sub-blocks */ + while (c) { + iOff += (int)c; + c = cBuf[iOff++]; + if ((iDataAvailable - iOff) < (c + 258)) { // need to read more data first + memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } + } + if (c != 0) { // problem, we went past the end + iNumFrames--; // possible corrupt data; stop + goto gifpagesz; + } + break; + case 0x2c: /* Start of image data */ + bExt = 0; /* Stop doing extension blocks */ + break; + default: + /* Corrupt data, stop here */ + iNumFrames--; + goto gifpagesz; + } // switch + } // while + if (iOff >= iDataAvailable) { // problem + iNumFrames--; // possible corrupt data; stop + goto gifpagesz; + } + /* Start of image data */ + c = cBuf[iOff + 9]; /* Get the flags byte */ + iOff += 10; /* Skip image position and size */ + if (c & 0x80) { /* Local color table */ + c &= 7; + iOff += (2 << c) * 3; + } + iOff++; /* Skip LZW code size byte */ + if ((iDataAvailable - iOff) < (c + 258)) { // need to read more data first + if (iOff < iDataAvailable) { + memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + } else { // already points beyond end + iOff -= iDataAvailable; + iDataAvailable = 0; + } + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } + c = cBuf[iOff++]; + while (c) { /* While there are more data blocks */ + if (iOff > (3 * FILE_BUF_SIZE / 4) && iDataRemaining > 0) { /* Near end of buffer, re-align */ + memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + iReadAmount = (FILE_BUF_SIZE - iDataAvailable); + if (iReadAmount > iDataRemaining) { + iReadAmount = iDataRemaining; + } + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } + iOff += (int)c; /* Skip this data block */ +// if ((int)lFileOff + iOff > pPage->GIFFile.iSize) // past end of file, stop +// { +// iNumFrames--; // don't count this page +// break; // last page is corrupted, don't use it +// } + c = cBuf[iOff++]; /* Get length of next */ + } + /* End of image data, check for more pages... */ + if (cBuf[iOff] == 0x3b || (iDataRemaining == 0 && (iDataAvailable - iOff) < 32)) { + bDone = 1; /* End of file has been reached */ + } else { /* More pages to scan */ + iNumFrames++; + // read new page data starting at this offset + if (pPage->GIFFile.iSize > FILE_BUF_SIZE && iDataRemaining > 0) { // since we didn't read the whole file in one shot + memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + iReadAmount = (FILE_BUF_SIZE - iDataAvailable); + if (iReadAmount > iDataRemaining) { + iReadAmount = iDataRemaining; + } + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } + } + } /* while !bDone */ +gifpagesz: + pInfo->iFrameCount = iNumFrames; + pInfo->iMaxDelay = iMaxDelay; + pInfo->iMinDelay = iMinDelay; + pInfo->iDuration = iTotalDelay; + return 1; +} /* GIF_getInfo() */ + +// +// Unpack more chunk data for decoding +// returns 1 to signify more data available for this image +// 0 indicates there is no more data +// +static int GIFGetMoreData(GIFIMAGE *pPage) { + int iDelta = (pPage->iLZWSize - pPage->iLZWOff); + unsigned char c = 1; + // move any existing data down + if (pPage->bEndOfFrame || iDelta >= (LZW_BUF_SIZE - MAX_CHUNK_SIZE) || iDelta <= 0) { + return 1; // frame is finished or buffer is already full; no need to read more data + } + if (pPage->iLZWOff != 0) { +// NB: memcpy() fails on some systems because the src and dest ptrs overlap +// so copy the bytes in a simple loop to avoid problems + for (int i = 0; i < pPage->iLZWSize - pPage->iLZWOff; i++) { + pPage->ucLZW[i] = pPage->ucLZW[i + pPage->iLZWOff]; + } + pPage->iLZWSize -= pPage->iLZWOff; + pPage->iLZWOff = 0; + } + while (c && pPage->GIFFile.iPos < pPage->GIFFile.iSize && pPage->iLZWSize < (LZW_BUF_SIZE - MAX_CHUNK_SIZE)) { + (*pPage->pfnRead)(&pPage->GIFFile, &c, 1); // current length + (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c); + pPage->iLZWSize += c; + } + if (c == 0) { // end of frame + pPage->bEndOfFrame = 1; + } + return c != 0 && pPage->GIFFile.iPos < pPage->GIFFile.iSize; // more data available? +} /* GIFGetMoreData() */ +// +// Handle transparent pixels and disposal method +// Used only when a frame buffer is allocated +// +static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { + uint8_t *d, *s; + int x, iPitch = pPage->iCanvasWidth; + + s = pDraw->pPixels; + d = &pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer + if (pDraw->ucDisposalMethod == 2) { // restore to background color + memset(d, pDraw->ucBackground, pDraw->iWidth); + } + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t c, ucTransparent = pDraw->ucTransparent; + for (x = 0; x < pDraw->iWidth; x++) + { + c = *s++; + if (c != ucTransparent) { + *d = c; + } + d++; + } + } else { + memcpy(d, s, pDraw->iWidth); // just overwrite the old pixels + } +} /* DrawNewPixels() */ +// +// Convert current line of pixels through the palette +// to either RGB565 or RGB888 output +// Used only when a frame buffer has been allocated +// +static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { + uint8_t *d, *s; + int x; + + s = &pPage->pFrameBuffer[(pPage->iCanvasWidth * (pDraw->iY + pDraw->y)) + pDraw->iX]; + d = &pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; // point past bottom of frame buffer + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { + uint16_t *pPal, *pu16; + pPal = (uint16_t *)pDraw->pPalette; + pu16 = (uint16_t *)d; + for (x = 0; x < pPage->iWidth; x++) + { + *pu16++ = pPal[*s++]; // convert to RGB565 pixels + } + } else { + uint8_t *pPal; + int pixel; + pPal = (uint8_t *)pDraw->pPalette; + for (x = 0; x < pPage->iWidth; x++) + { + pixel = *s++; + *d++ = pPal[(pixel * 3) + 0]; // convert to RGB888 pixels + *d++ = pPal[(pixel * 3) + 1]; + *d++ = pPal[(pixel * 3) + 2]; + } + } +} /* ConvertNewPixels() */ + +// +// GIFMakePels +// +static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { + int iPixCount; + unsigned short *giftabs; + unsigned char *buf, *s, *pEnd, *gifpels; + unsigned char ucNeedMore = 0; + /* Copy this string of sequential pixels to output buffer */ + // iPixCount = 0; + s = pPage->ucFileBuf + FILE_BUF_SIZE; /* Pixels will come out in reversed order */ + buf = pPage->ucLineBuf + (pPage->iWidth - pPage->iXCount); + giftabs = pPage->usGIFTable; + gifpels = &pPage->ucGIFPixels[PIXEL_LAST]; + while (code < LINK_UNUSED) { + if (s == pPage->ucFileBuf) { /* Houston, we have a problem */ + return; /* Exit with error */ + } + *(--s) = gifpels[code]; + code = giftabs[code]; + } + iPixCount = (int)(intptr_t)(pPage->ucFileBuf + FILE_BUF_SIZE - s); + + while (iPixCount && pPage->iYCount > 0) { + if (pPage->iXCount > iPixCount) { /* Pixels fit completely on the line */ + // memcpy(buf, s, iPixCount); + // buf += iPixCount; + pEnd = buf + iPixCount; + while (buf < pEnd) { + *buf++ = *s++; + } + pPage->iXCount -= iPixCount; + // iPixCount = 0; + if (ucNeedMore) { + GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines + } + return; + } else { /* Pixels cross into next line */ + GIFDRAW gd; + pEnd = buf + pPage->iXCount; + while (buf < pEnd) { + *buf++ = *s++; + } + iPixCount -= pPage->iXCount; + pPage->iXCount = pPage->iWidth; /* Reset pixel count */ + // Prepare GIDRAW structure for callback + gd.iX = pPage->iX; + gd.iY = pPage->iY; + gd.iWidth = pPage->iWidth; + gd.iHeight = pPage->iHeight; + gd.pPixels = pPage->ucLineBuf; + gd.pPalette = (pPage->bUseLocalPalette) ? pPage->pLocalPalette : pPage->pPalette; + gd.pPalette24 = (uint8_t *)gd.pPalette; // just cast the pointer for RGB888 + gd.ucIsGlobalPalette = pPage->bUseLocalPalette == 1?0:1; + gd.y = pPage->iHeight - pPage->iYCount; + // Ugly logic to handle the interlaced line position, but it + // saves having to have another set of state variables + if (pPage->ucMap & 0x40) { // interlaced? + int height = pPage->iHeight - 1; + if (gd.y > height / 2) { + gd.y = gd.y * 2 - (height | 1); + } else if (gd.y > height / 4) { + gd.y = gd.y * 4 - ((height & ~1) | 2); + } else if (gd.y > height / 8) { + gd.y = gd.y * 8 - ((height & ~3) | 4); + } else { + gd.y = gd.y * 8; + } + } + gd.ucDisposalMethod = (pPage->ucGIFBits & 0x1c) >> 2; + gd.ucTransparent = pPage->ucTransparent; + gd.ucHasTransparency = pPage->ucGIFBits & 1; + gd.ucBackground = pPage->ucBackground; + gd.pUser = pPage->pUser; + if (pPage->pFrameBuffer) { // update the frame buffer + DrawNewPixels(pPage, &gd); + if (pPage->ucDrawType == GIF_DRAW_COOKED) { + ConvertNewPixels(pPage, &gd); // prepare for output + gd.pPixels = &pPage->pFrameBuffer[pPage->iCanvasWidth * pPage->iCanvasHeight]; + } + } + (*pPage->pfnDraw)(&gd); // callback to handle this line + pPage->iYCount--; + buf = pPage->ucLineBuf; + if ((pPage->iYCount & 3) == 0) { // since we support only small images... + ucNeedMore = 1; + } + } + } /* while */ + if (ucNeedMore) { + GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines + } + return; +} /* GIFMakePels() */ +// +// Macro to extract a variable length code +// +#define GET_CODE if (bitnum > (REGISTER_WIDTH - codesize)) { pImage->iLZWOff += (bitnum >> 3); \ + bitnum &= 7; ulBits = INTELLONG(&p[pImage->iLZWOff]); } \ + code = (unsigned short)(ulBits >> bitnum); /* Read a 32-bit chunk */ \ + code &= sMask; bitnum += codesize; + +// +// Decode LZW into an image +// +static int DecodeLZW(GIFIMAGE *pImage, int iOptions) { + int i, bitnum; + unsigned short oldcode, codesize, nextcode, nextlim; + unsigned short *giftabs, cc, eoi; + signed short sMask; + unsigned char *gifpels, *p; + // int iStripSize; + // unsigned char **index; + uint32_t ulBits; + unsigned short code; + (void)iOptions; // not used for now + // if output can be used for string table, do it faster + // if (bGIF && (OutPage->cBitsperpixel == 8 && ((OutPage->iWidth & 3) == 0))) + // return PILFastLZW(InPage, OutPage, bGIF, iOptions); + p = pImage->ucLZW; // un-chunked LZW data + sMask = 0xffff << (pImage->ucCodeStart + 1); + sMask = 0xffff - sMask; + cc = (sMask >> 1) + 1; /* Clear code */ + eoi = cc + 1; + giftabs = pImage->usGIFTable; + gifpels = pImage->ucGIFPixels; + pImage->iYCount = pImage->iHeight; // count down the lines + pImage->iXCount = pImage->iWidth; + bitnum = 0; + pImage->iLZWOff = 0; // Offset into compressed data + GIFGetMoreData(pImage); // Read some data to start + + // Initialize code table + // this part only needs to be initialized once + for (i = 0; i < cc; i++) + { + gifpels[PIXEL_FIRST + i] = gifpels[PIXEL_LAST + i] = (unsigned short)i; + giftabs[i] = LINK_END; + } +init_codetable: + codesize = pImage->ucCodeStart + 1; + sMask = 0xffff << (pImage->ucCodeStart + 1); + sMask = 0xffff - sMask; + nextcode = cc + 2; + nextlim = (unsigned short)((1 << codesize)); + // This part of the table needs to be reset multiple times + memset(&giftabs[cc], LINK_UNUSED, (4096 - cc) * sizeof(short)); + ulBits = INTELLONG(&p[pImage->iLZWOff]); // start by reading 4 bytes of LZW data + GET_CODE + if (code == cc) { // we just reset the dictionary, so get another code + GET_CODE + } + oldcode = code; + GIFMakePels(pImage, code); // first code is output as the first pixel + // Main decode loop + while (code != eoi && pImage->iYCount > 0) { // && y < pImage->iHeight+1) /* Loop through all lines of the image (or strip) */ + GET_CODE + if (code == cc) { /* Clear code?, and not first code */ + goto init_codetable; + } + if (code != eoi) { + if (nextcode < nextlim) { // for deferred cc case, don't let it overwrite the last entry (fff) + giftabs[nextcode] = oldcode; + gifpels[PIXEL_FIRST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; + if (giftabs[code] == LINK_UNUSED) { /* Old code */ + gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; + } else { + gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + code]; + } + } + nextcode++; + if (nextcode >= nextlim && codesize < 12) { + codesize++; + nextlim <<= 1; + sMask = (sMask << 1) | 1; + } + GIFMakePels(pImage, code); + oldcode = code; + } + } /* while not end of LZW code stream */ + return 0; +// gif_forced_error: +// free(pImage->pPixels); +// pImage->pPixels = NULL; +// return -1; +} /* DecodeLZW() */ + +void GIF_setDrawCallback(GIFIMAGE *pGIF, GIF_DRAW_CALLBACK *pfnDraw) { + pGIF->pfnDraw = pfnDraw; +} /* GIF_setDrawCallback() */ +// +// Scale 2 scanlines down by 50% with pixel averaging +// writes new values over previous line +// expects RGB565 little endian pixels as input +// +void GIF_scaleHalf(uint16_t *pCurrent, uint16_t *pPrev, int iWidth, int bBigEndian) { + int x; + uint16_t *d = pPrev; + uint32_t gSum, rbSum, pix0,pix1,pix2,pix3; + const uint32_t RBMask = 0xf81f, GMask = 0x7e0; + + for (x = 0; x < iWidth; x += 2) + { + pix0 = pCurrent[0]; + pix1 = pCurrent[1]; + pix2 = pPrev[0]; + pix3 = pPrev[1]; + pCurrent += 2; + pPrev += 2; + gSum = (pix0 & GMask) + (pix1 & GMask) + (pix2 & GMask) + (pix3 & GMask); + gSum = ((gSum + 0x40) >> 2) & GMask; // for rounding towards 1 + rbSum = (pix0 & RBMask) + (pix1 & RBMask) + (pix2 & RBMask) + (pix3 & RBMask); + rbSum = ((rbSum + 0x1002) >> 2) & RBMask; + if (bBigEndian) { + *d++ = __builtin_bswap16((uint16_t)(gSum + rbSum)); + } else { + *d++ = (uint16_t)(gSum + rbSum); // store finished pixel + } + } // for x +} /* GIF_scaleHalf() */ From 2b26242c66beb5f9f265514a326f738489abfe45 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 4 Feb 2023 18:38:07 -0600 Subject: [PATCH 02/16] need gif.c in shared-bindings to build --- shared-bindings/displayio/gif.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 shared-bindings/displayio/gif.c diff --git a/shared-bindings/displayio/gif.c b/shared-bindings/displayio/gif.c new file mode 100644 index 0000000000000..e69de29bb2d1d From dbe518680a001a8e675e0743717fe45107fea621 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 5 Feb 2023 10:54:04 -0600 Subject: [PATCH 03/16] Memory savings and more properties --- shared-bindings/displayio/OnDiskGif.c | 82 ++++++++++++++++------ shared-bindings/displayio/OnDiskGif.h | 4 ++ shared-module/displayio/OnDiskGif.c | 97 +++++++++++++-------------- shared-module/displayio/OnDiskGif.h | 10 ++- 4 files changed, 114 insertions(+), 79 deletions(-) diff --git a/shared-bindings/displayio/OnDiskGif.c b/shared-bindings/displayio/OnDiskGif.c index eeade372cb9c7..66f2f9f5c35bd 100644 --- a/shared-bindings/displayio/OnDiskGif.c +++ b/shared-bindings/displayio/OnDiskGif.c @@ -123,25 +123,6 @@ MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_height_obj, displayio_ondiskgi MP_PROPERTY_GETTER(displayio_ondiskgif_height_obj, (mp_obj_t)&displayio_ondiskgif_get_height_obj); -//| pixel_shader: Union[ColorConverter, Palette] -//| """The image's pixel_shader. The type depends on the underlying -//| bitmap's structure. The pixel shader can be modified (e.g., to set the -//| transparent pixel or, for palette shaded images, to update the palette.)""" -//| -STATIC mp_obj_t displayio_ondiskgif_obj_get_pixel_shader(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return common_hal_displayio_ondiskgif_get_pixel_shader(self); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_pixel_shader_obj, displayio_ondiskgif_obj_get_pixel_shader); - -const mp_obj_property_t displayio_ondiskgif_pixel_shader_obj = { - .base.type = &mp_type_property, - .proxy = {(mp_obj_t)&displayio_ondiskgif_get_pixel_shader_obj, - (mp_obj_t)MP_ROM_NONE, - (mp_obj_t)MP_ROM_NONE}, -}; - //| bitmap: Bitmap //| """The image's bitmap. The type depends on the underlying //| bitmap's structure. The pixel shader can be modified (e.g., to set the @@ -154,12 +135,16 @@ STATIC mp_obj_t displayio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_bitmap_obj, displayio_ondiskgif_obj_get_bitmap); +MP_PROPERTY_GETTER(displayio_ondiskgif_bitmap_obj, + (mp_obj_t)&displayio_ondiskgif_get_bitmap_obj); + +/* const mp_obj_property_t displayio_ondiskgif_bitmap_obj = { .base.type = &mp_type_property, .proxy = {(mp_obj_t)&displayio_ondiskgif_get_bitmap_obj, (mp_obj_t)MP_ROM_NONE, (mp_obj_t)MP_ROM_NONE}, -}; +};*/ //| play_frame: None @@ -173,13 +158,68 @@ STATIC mp_obj_t displayio_ondiskgif_obj_play_frame(mp_obj_t self_in) { MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_play_frame_obj, displayio_ondiskgif_obj_play_frame); +//| duration: int +//| """Height of the bitmap. (read only)""" +STATIC mp_obj_t displayio_ondiskgif_obj_get_duration(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_duration(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_duration_obj, displayio_ondiskgif_obj_get_duration); + +MP_PROPERTY_GETTER(displayio_ondiskgif_duration_obj, + (mp_obj_t)&displayio_ondiskgif_get_duration_obj); + +//| frame_count: int +//| """Height of the bitmap. (read only)""" +STATIC mp_obj_t displayio_ondiskgif_obj_get_frame_count(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_frame_count(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_frame_count_obj, displayio_ondiskgif_obj_get_frame_count); + +MP_PROPERTY_GETTER(displayio_ondiskgif_frame_count_obj, + (mp_obj_t)&displayio_ondiskgif_get_frame_count_obj); + +//| min_delay: int +//| """Height of the bitmap. (read only)""" +STATIC mp_obj_t displayio_ondiskgif_obj_get_min_delay(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_min_delay(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_min_delay_obj, displayio_ondiskgif_obj_get_min_delay); + +MP_PROPERTY_GETTER(displayio_ondiskgif_min_delay_obj, + (mp_obj_t)&displayio_ondiskgif_get_min_delay_obj); + +//| max_delay: int +//| """Height of the bitmap. (read only)""" +//| +STATIC mp_obj_t displayio_ondiskgif_obj_get_max_delay(mp_obj_t self_in) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_max_delay(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_max_delay_obj, displayio_ondiskgif_obj_get_max_delay); + +MP_PROPERTY_GETTER(displayio_ondiskgif_max_delay_obj, + (mp_obj_t)&displayio_ondiskgif_get_max_delay_obj); STATIC const mp_rom_map_elem_t displayio_ondiskgif_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&displayio_ondiskgif_height_obj) }, - { MP_ROM_QSTR(MP_QSTR_pixel_shader), MP_ROM_PTR(&displayio_ondiskgif_pixel_shader_obj) }, { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&displayio_ondiskgif_bitmap_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&displayio_ondiskgif_width_obj) }, { MP_ROM_QSTR(MP_QSTR_play_frame), MP_ROM_PTR(&displayio_ondiskgif_play_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_duration), MP_ROM_PTR(&displayio_ondiskgif_duration_obj) }, + { MP_ROM_QSTR(MP_QSTR_frame_count), MP_ROM_PTR(&displayio_ondiskgif_frame_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_min_delay), MP_ROM_PTR(&displayio_ondiskgif_min_delay_obj) }, + { MP_ROM_QSTR(MP_QSTR_max_delay), MP_ROM_PTR(&displayio_ondiskgif_max_delay_obj) }, }; STATIC MP_DEFINE_CONST_DICT(displayio_ondiskgif_locals_dict, displayio_ondiskgif_locals_dict_table); diff --git a/shared-bindings/displayio/OnDiskGif.h b/shared-bindings/displayio/OnDiskGif.h index 988bc8581bd9d..5996930124446 100644 --- a/shared-bindings/displayio/OnDiskGif.h +++ b/shared-bindings/displayio/OnDiskGif.h @@ -42,4 +42,8 @@ mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t * mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self); uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self); uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self); +int32_t common_hal_displayio_ondiskgif_get_duration(displayio_ondiskgif_t *self); +int32_t common_hal_displayio_ondiskgif_get_frame_count(displayio_ondiskgif_t *self); +int32_t common_hal_displayio_ondiskgif_get_min_delay(displayio_ondiskgif_t *self); +int32_t common_hal_displayio_ondiskgif_get_max_delay(displayio_ondiskgif_t *self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H diff --git a/shared-module/displayio/OnDiskGif.c b/shared-module/displayio/OnDiskGif.c index 88146d354eb72..97caced1eb441 100644 --- a/shared-module/displayio/OnDiskGif.c +++ b/shared-module/displayio/OnDiskGif.c @@ -76,41 +76,33 @@ static void GIFDraw(GIFDRAW *pDraw) { displayio_bitmap_t *bitmap = (displayio_bitmap_t *)pDraw->pUser; - // mp_printf(&mp_plat_print, "GD: y%d iX%d iY%d iW%d iH%d Trans%d hasT%d bk%d pUser %x bmp%x\n", - // pDraw->y, pDraw->iX, pDraw->iY, pDraw->iWidth, pDraw->iHeight, pDraw->ucTransparent, pDraw->ucHasTransparency, pDraw->ucBackground, pDraw->pUser, bitmap->data); + uint8_t *s; + uint16_t *d; -/* - int iX, iY; // Corner offset of this frame on the canvas - int y; // current line being drawn (0 = top line of image) - int iWidth, iHeight; // size of this frame - void *pUser; // user supplied pointer - uint8_t *pPixels; // 8-bit source pixels for this line - uint16_t *pPalette; // little or big-endian RGB565 palette entries (default) - uint8_t *pPalette24; // RGB888 palette (optional) - uint8_t ucTransparent; // transparent color - uint8_t ucHasTransparency; // flag indicating the transparent color is in use - uint8_t ucDisposalMethod; // frame disposal method - uint8_t ucBackground; // background color - uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used -*/ - - // For all other lines, just push the pixels to the display - // (uint8_t *)pDraw->pPixels, pDraw->iWidth*2); + int32_t row_start = pDraw->y * bitmap->stride; + uint32_t *row = bitmap->data + row_start; + s = pDraw->pPixels; + d = (uint16_t *)row; + uint16_t *pPal; + pPal = (uint16_t *)pDraw->pPalette; + if (pDraw->ucDisposalMethod == 2) { // restore to background color + memset(d, pDraw->ucBackground, pDraw->iWidth); + } - int32_t row_start = pDraw->y * bitmap->stride; - uint32_t *row = bitmap->data + row_start; - uint8_t *d = (uint8_t *)row; - // mp_printf(&mp_plat_print, "rs %d strd %d:\n", row_start, bitmap->stride); - - uint8_t *p = (uint8_t *)pDraw->pPixels; - for (int w = 0; w < pDraw->iWidth * 2; w++) { - // mp_printf(&mp_plat_print, "%x:", *p); - *d++ = *p; - p++; + // We always check for transpancy even if the gif does not have it + // as we also convert the color to the palette here + // Could separate it but would not sure it would be much a speed up + uint8_t c, ucTransparent = pDraw->ucTransparent; + for (int x = 0; x < pDraw->iWidth; x++) + { + c = *s++; + if (c != ucTransparent) { + *d = pPal[c]; + } + d++; } - // mp_printf(&mp_plat_print, "\n"); } void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_file_obj_t *file) { @@ -135,33 +127,22 @@ void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_f mp_arg_error_invalid(MP_QSTR_file); } - self->frame = m_malloc(self->gif.iCanvasWidth * self->gif.iCanvasHeight * sizeof(uint16_t), false); // MUST FREE LATER? - self->gif.pFrameBuffer = (uint8_t *)self->frame; - self->gif.ucDrawType = GIF_DRAW_COOKED; - displayio_bitmap_t *bitmap = m_new_obj(displayio_bitmap_t); bitmap->base.type = &displayio_bitmap_type; common_hal_displayio_bitmap_construct(bitmap, self->gif.iCanvasWidth, self->gif.iCanvasHeight, 16); self->bitmap = bitmap; - // mp_printf(&mp_plat_print, "GIF_init returned %d %x\n", result, bitmap->data); -} - - -uint32_t common_hal_displayio_ondiskgif_get_pixel(displayio_ondiskgif_t *self, - int16_t x, int16_t y) { - if (x < 0 || x >= self->gif.iCanvasWidth || y < 0 || y >= self->gif.iCanvasHeight) { - return 0; - } + GIFINFO info; + GIF_getInfo(&self->gif, &info); + self->duration = info.iDuration; + self->frame_count = info.iFrameCount; + self->min_delay = info.iMinDelay; + self->max_delay = info.iMaxDelay; - return 0; + // mp_printf(&mp_plat_print, "GIF_init returned %d %x\n", result, bitmap->data); } uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self) { - GIFINFO info; - GIF_getInfo(&self->gif, &info); - mp_printf(&mp_plat_print, "dur %d fc %d max %d min %d\n", info.iDuration, info.iFrameCount, info.iMaxDelay, info.iMinDelay); - return (uint16_t)self->gif.iCanvasHeight; } @@ -169,14 +150,26 @@ uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self) { return (uint16_t)self->gif.iCanvasWidth; } -mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t *self) { - return MP_OBJ_FROM_PTR(self->pixel_shader_base); -} - mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self) { return MP_OBJ_FROM_PTR(self->bitmap); } +int32_t common_hal_displayio_ondiskgif_get_duration(displayio_ondiskgif_t *self) { + return self->duration; +} + +int32_t common_hal_displayio_ondiskgif_get_frame_count(displayio_ondiskgif_t *self) { + return self->frame_count; +} + +int32_t common_hal_displayio_ondiskgif_get_min_delay(displayio_ondiskgif_t *self) { + return self->min_delay; +} + +int32_t common_hal_displayio_ondiskgif_get_max_delay(displayio_ondiskgif_t *self) { + return self->max_delay; +} + uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self) { int result = GIF_playFrame(&self->gif, 0, self->bitmap); diff --git a/shared-module/displayio/OnDiskGif.h b/shared-module/displayio/OnDiskGif.h index 008635bc66ac3..2184f78dbeacc 100644 --- a/shared-module/displayio/OnDiskGif.h +++ b/shared-module/displayio/OnDiskGif.h @@ -42,13 +42,11 @@ typedef struct { mp_obj_base_t base; GIFIMAGE gif; pyb_file_obj_t *file; - uint32_t *frame; displayio_bitmap_t *bitmap; - union { - mp_obj_base_t *pixel_shader_base; - struct displayio_palette *palette; - struct displayio_colorconverter *colorconverter; - }; + int32_t duration; + int32_t frame_count; + int32_t min_delay; + int32_t max_delay; } displayio_ondiskgif_t; #endif // MICROPY_INCLUDED_SHARED_MODULE_DISPLAYIO_ONDISKGIF_H From 19eba4163080fe4ee19be4e5ae1d5998e8bf4077 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Wed, 8 Feb 2023 22:58:44 -0600 Subject: [PATCH 04/16] Doc fixes and return next frame delay --- shared-bindings/displayio/OnDiskGif.c | 77 ++++++++++----------------- shared-bindings/displayio/OnDiskGif.h | 2 +- shared-module/displayio/OnDiskGif.c | 11 ++-- 3 files changed, 35 insertions(+), 55 deletions(-) diff --git a/shared-bindings/displayio/OnDiskGif.c b/shared-bindings/displayio/OnDiskGif.c index 66f2f9f5c35bd..51651a93e7ab7 100644 --- a/shared-bindings/displayio/OnDiskGif.c +++ b/shared-bindings/displayio/OnDiskGif.c @@ -33,13 +33,9 @@ #include "supervisor/shared/translate/translate.h" #include "shared-bindings/displayio/OnDiskGif.h" -//| class OnDiskBitmap: +//| class OnDiskGif: //| """Loads values straight from disk. This minimizes memory use but can lead to -//| much slower pixel load times. These load times may result in frame tearing where only part of -//| the image is visible. -//| -//| It's easiest to use on a board with a built in display such as the `Hallowing M0 Express -//| `_. +//| much slower pixel load times //| //| .. code-block:: Python //| @@ -48,34 +44,25 @@ //| import time //| import pulseio //| -//| board.DISPLAY.brightness = 0 //| splash = displayio.Group() //| board.DISPLAY.show(splash) //| -//| odb = displayio.OnDiskBitmap('/sample.bmp') -//| face = displayio.TileGrid(odb, pixel_shader=odb.pixel_shader) +//| odg = displayio.OnDiskBitmap('/sample.gif') +//| odg.play_frame() # Load the first frame +//| face = displayio.TileGrid(odg, pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565)) //| splash.append(face) -//| # Wait for the image to load. -//| board.DISPLAY.refresh(target_frames_per_second=60) -//| -//| # Fade up the backlight -//| for i in range(100): -//| board.DISPLAY.brightness = 0.01 * i -//| time.sleep(0.05) +//| board.DISPLAY.refresh() //| //| # Wait forever //| while True: -//| pass""" +//| gif.play_frame() +//| time.sleep(0.1)""" //| -//| def __init__(self, file: Union[str, typing.BinaryIO]) -> None: -//| """Create an OnDiskBitmap object with the given file. +//| def __init__(self, file: str) -> None: +//| """Create an OnDiskGif object with the given file. //| -//| :param file file: The name of the bitmap file. For backwards compatibility, a file opened in binary mode may also be passed. +//| :param file file: The name of the GIF file. //| -//| Older versions of CircuitPython required a file opened in binary -//| mode. CircuitPython 7.0 modified OnDiskBitmap so that it takes a -//| filename instead, and opens the file internally. A future version -//| of CircuitPython will remove the ability to pass in an opened file. //| """ //| ... STATIC mp_obj_t displayio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -98,7 +85,7 @@ STATIC mp_obj_t displayio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n } //| width: int -//| """Width of the bitmap. (read only)""" +//| """Width of the gif. (read only)""" STATIC mp_obj_t displayio_ondiskgif_obj_get_width(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); @@ -111,7 +98,7 @@ MP_PROPERTY_GETTER(displayio_ondiskgif_width_obj, (mp_obj_t)&displayio_ondiskgif_get_width_obj); //| height: int -//| """Height of the bitmap. (read only)""" +//| """Height of the gif. (read only)""" STATIC mp_obj_t displayio_ondiskgif_obj_get_height(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); @@ -124,10 +111,7 @@ MP_PROPERTY_GETTER(displayio_ondiskgif_height_obj, (mp_obj_t)&displayio_ondiskgif_get_height_obj); //| bitmap: Bitmap -//| """The image's bitmap. The type depends on the underlying -//| bitmap's structure. The pixel shader can be modified (e.g., to set the -//| transparent pixel or, for palette shaded images, to update the palette.)""" -//| +//| """The bitmap used to hold the current frame.""" STATIC mp_obj_t displayio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); return common_hal_displayio_ondiskgif_get_bitmap(self); @@ -138,28 +122,23 @@ MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_bitmap_obj, displayio_ondiskgi MP_PROPERTY_GETTER(displayio_ondiskgif_bitmap_obj, (mp_obj_t)&displayio_ondiskgif_get_bitmap_obj); -/* -const mp_obj_property_t displayio_ondiskgif_bitmap_obj = { - .base.type = &mp_type_property, - .proxy = {(mp_obj_t)&displayio_ondiskgif_get_bitmap_obj, - (mp_obj_t)MP_ROM_NONE, - (mp_obj_t)MP_ROM_NONE}, -};*/ - +//| play_frame: int +//| """Play next frame. Returns expected delay until the next frame.""" +STATIC mp_obj_t displayio_ondiskgif_obj_play_frame(size_t n_args, const mp_obj_t *args) { + displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(args[0]); + bool setDirty = mp_const_true; -//| play_frame: None -//| """Play next frame. (read only)""" -//| -STATIC mp_obj_t displayio_ondiskgif_obj_play_frame(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + if (n_args == 1) { + setDirty = mp_obj_is_true(args[1]); + } - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_play_frame(self)); + return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_play_frame(self, setDirty)); } -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_play_frame_obj, displayio_ondiskgif_obj_play_frame); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(displayio_ondiskgif_play_frame_obj, 1, 2, displayio_ondiskgif_obj_play_frame); //| duration: int -//| """Height of the bitmap. (read only)""" +//| """Returns the total duration of the GIF in milliseconds. (read only)""" STATIC mp_obj_t displayio_ondiskgif_obj_get_duration(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); @@ -172,7 +151,7 @@ MP_PROPERTY_GETTER(displayio_ondiskgif_duration_obj, (mp_obj_t)&displayio_ondiskgif_get_duration_obj); //| frame_count: int -//| """Height of the bitmap. (read only)""" +//| """Returns the number of frames in the GIF. (read only)""" STATIC mp_obj_t displayio_ondiskgif_obj_get_frame_count(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); @@ -185,7 +164,7 @@ MP_PROPERTY_GETTER(displayio_ondiskgif_frame_count_obj, (mp_obj_t)&displayio_ondiskgif_get_frame_count_obj); //| min_delay: int -//| """Height of the bitmap. (read only)""" +//| """The minimum delay found between frames. (read only)""" STATIC mp_obj_t displayio_ondiskgif_obj_get_min_delay(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); @@ -198,7 +177,7 @@ MP_PROPERTY_GETTER(displayio_ondiskgif_min_delay_obj, (mp_obj_t)&displayio_ondiskgif_get_min_delay_obj); //| max_delay: int -//| """Height of the bitmap. (read only)""" +//| """The maximum delay found between frames. (read only)""" //| STATIC mp_obj_t displayio_ondiskgif_obj_get_max_delay(mp_obj_t self_in) { displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); diff --git a/shared-bindings/displayio/OnDiskGif.h b/shared-bindings/displayio/OnDiskGif.h index 5996930124446..6e5041c012c8a 100644 --- a/shared-bindings/displayio/OnDiskGif.h +++ b/shared-bindings/displayio/OnDiskGif.h @@ -41,7 +41,7 @@ uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self); mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t *self); mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self); uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self); -uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self); +uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self, bool setDirty); int32_t common_hal_displayio_ondiskgif_get_duration(displayio_ondiskgif_t *self); int32_t common_hal_displayio_ondiskgif_get_frame_count(displayio_ondiskgif_t *self); int32_t common_hal_displayio_ondiskgif_get_min_delay(displayio_ondiskgif_t *self); diff --git a/shared-module/displayio/OnDiskGif.c b/shared-module/displayio/OnDiskGif.c index 97caced1eb441..22a97020f075a 100644 --- a/shared-module/displayio/OnDiskGif.c +++ b/shared-module/displayio/OnDiskGif.c @@ -109,7 +109,7 @@ void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_f // mp_printf(&mp_plat_print, "Begin OnDiskGif\n"); self->file = file; - GIF_begin(&self->gif, GIF_PALETTE_RGB565_LE); + GIF_begin(&self->gif, GIF_PALETTE_RGB565_BE); self->gif.iError = GIF_SUCCESS; self->gif.pfnRead = GIFReadFile; @@ -170,10 +170,11 @@ int32_t common_hal_displayio_ondiskgif_get_max_delay(displayio_ondiskgif_t *self return self->max_delay; } -uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self) { - int result = GIF_playFrame(&self->gif, 0, self->bitmap); +uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self, bool setDirty) { + int nextDelay = 0; + int result = GIF_playFrame(&self->gif, &nextDelay, self->bitmap); - if (result >= 0) { + if ((result >= 0) && (setDirty)) { displayio_area_t dirty_area = { .x1 = 0, .y1 = 0, @@ -184,5 +185,5 @@ uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self) { displayio_bitmap_set_dirty_area(self->bitmap, &dirty_area); } - return result; + return nextDelay; } From da9b6fb0e20554135c40fc08af0c4894a366601b Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Fri, 10 Feb 2023 18:31:59 -0600 Subject: [PATCH 05/16] Moving library to /lib --- lib/AnimatedGIF/AnimatedGIF.cpp | 236 ++++++ lib/AnimatedGIF/AnimatedGIF.h | 216 +++++ .../AnimatedGIF}/AnimatedGIF_circuitpy.h | 3 + lib/AnimatedGIF/README.md | 5 + .../displayio => lib/AnimatedGIF}/gif.c | 768 ++++++++++-------- py/circuitpy_defns.mk | 9 +- shared-bindings/displayio/gif.c | 0 shared-module/displayio/OnDiskGif.h | 3 +- 8 files changed, 874 insertions(+), 366 deletions(-) create mode 100644 lib/AnimatedGIF/AnimatedGIF.cpp create mode 100644 lib/AnimatedGIF/AnimatedGIF.h rename {shared-module/displayio => lib/AnimatedGIF}/AnimatedGIF_circuitpy.h (99%) create mode 100644 lib/AnimatedGIF/README.md rename {shared-module/displayio => lib/AnimatedGIF}/gif.c (60%) delete mode 100644 shared-bindings/displayio/gif.c diff --git a/lib/AnimatedGIF/AnimatedGIF.cpp b/lib/AnimatedGIF/AnimatedGIF.cpp new file mode 100644 index 0000000000000..ad79b212b8a70 --- /dev/null +++ b/lib/AnimatedGIF/AnimatedGIF.cpp @@ -0,0 +1,236 @@ +// +// GIF Animator +// written by Larry Bank +// bitbank@pobox.com +// Arduino port started 7/5/2020 +// Original GIF code written 20+ years ago :) +// The goal of this code is to decode images up to 480x320 +// using no more than 22K of RAM (if sent directly to an LCD display) +// +// Copyright 2020 BitBank Software, Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//=========================================================================== +#include "AnimatedGIF.h" + +// Here is all of the actual code... +#include "gif.inl" + +// +// Memory initialization +// +int AnimatedGIF::open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) +{ + _gif.iError = GIF_SUCCESS; + _gif.pfnRead = readMem; + _gif.pfnSeek = seekMem; + _gif.pfnDraw = pfnDraw; + _gif.pfnOpen = NULL; + _gif.pfnClose = NULL; + _gif.GIFFile.iSize = iDataSize; + _gif.GIFFile.pData = pData; + return GIFInit(&_gif); +} /* open() */ + +int AnimatedGIF::openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) +{ + _gif.iError = GIF_SUCCESS; + _gif.pfnRead = readFLASH; + _gif.pfnSeek = seekMem; + _gif.pfnDraw = pfnDraw; + _gif.pfnOpen = NULL; + _gif.pfnClose = NULL; + _gif.GIFFile.iSize = iDataSize; + _gif.GIFFile.pData = pData; + return GIFInit(&_gif); +} /* openFLASH() */ + +// +// Returns the first comment block found (if any) +// +int AnimatedGIF::getComment(char *pDest) +{ +int32_t iOldPos; + + iOldPos = _gif.GIFFile.iPos; // keep old position + (*_gif.pfnSeek)(&_gif.GIFFile, _gif.iCommentPos); + (*_gif.pfnRead)(&_gif.GIFFile, (uint8_t *)pDest, _gif.sCommentLen); + (*_gif.pfnSeek)(&_gif.GIFFile, iOldPos); + pDest[_gif.sCommentLen] = 0; // zero terminate the string + return (int)_gif.sCommentLen; +} /* getComment() */ + +// +// Allocate a block of memory to hold the entire canvas (as 8-bpp) +// +int AnimatedGIF::allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc) +{ + if (_gif.iCanvasWidth > 0 && _gif.iCanvasHeight > 0 && _gif.pFrameBuffer == NULL) + { + // Allocate a little extra space for the current line + // as RGB565 or RGB888 + int iCanvasSize = _gif.iCanvasWidth * (_gif.iCanvasHeight+3); + _gif.pFrameBuffer = (unsigned char *)(*pfnAlloc)(iCanvasSize); + if (_gif.pFrameBuffer == NULL) + return GIF_ERROR_MEMORY; + return GIF_SUCCESS; + } + return GIF_INVALID_PARAMETER; +} /* allocFrameBuf() */ +// +// Set the DRAW callback behavior to RAW (default) +// or COOKED (requires allocating a frame buffer) +// +int AnimatedGIF::setDrawType(int iType) +{ + if (iType != GIF_DRAW_RAW && iType != GIF_DRAW_COOKED) + return GIF_INVALID_PARAMETER; // invalid drawing mode + _gif.ucDrawType = (uint8_t)iType; + return GIF_SUCCESS; +} /* setDrawType() */ +// +// Release the memory used by the frame buffer +// +int AnimatedGIF::freeFrameBuf(GIF_FREE_CALLBACK *pfnFree) +{ + if (_gif.pFrameBuffer) + { + (*pfnFree)(_gif.pFrameBuffer); + _gif.pFrameBuffer = NULL; + return GIF_SUCCESS; + } + return GIF_INVALID_PARAMETER; +} /* freeFrameBuf() */ +// +// Return a pointer to the frame buffer (if it was allocated) +// +uint8_t * AnimatedGIF::getFrameBuf() +{ + return _gif.pFrameBuffer; +} /* getFrameBuf() */ + +int AnimatedGIF::getCanvasWidth() +{ + return _gif.iCanvasWidth; +} /* getCanvasWidth() */ + +int AnimatedGIF::getCanvasHeight() +{ + return _gif.iCanvasHeight; +} /* getCanvasHeight() */ + +int AnimatedGIF::getLoopCount() +{ + return _gif.iRepeatCount; +} /* getLoopCount() */ + +int AnimatedGIF::getInfo(GIFINFO *pInfo) +{ + return GIF_getInfo(&_gif, pInfo); +} /* getInfo() */ + +int AnimatedGIF::getLastError() +{ + return _gif.iError; +} /* getLastError() */ + +// +// File (SD/MMC) based initialization +// +int AnimatedGIF::open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw) +{ + _gif.iError = GIF_SUCCESS; + _gif.pfnRead = pfnRead; + _gif.pfnSeek = pfnSeek; + _gif.pfnDraw = pfnDraw; + _gif.pfnOpen = pfnOpen; + _gif.pfnClose = pfnClose; + _gif.GIFFile.fHandle = (*pfnOpen)(szFilename, &_gif.GIFFile.iSize); + if (_gif.GIFFile.fHandle == NULL) { + _gif.iError = GIF_FILE_NOT_OPEN; + return 0; + } + return GIFInit(&_gif); + +} /* open() */ + +void AnimatedGIF::close() +{ + if (_gif.pfnClose) + (*_gif.pfnClose)(_gif.GIFFile.fHandle); +} /* close() */ + +void AnimatedGIF::reset() +{ + (*_gif.pfnSeek)(&_gif.GIFFile, 0); +} /* reset() */ + +void AnimatedGIF::begin(unsigned char ucPaletteType) +{ + memset(&_gif, 0, sizeof(_gif)); + if (ucPaletteType != GIF_PALETTE_RGB565_LE && ucPaletteType != GIF_PALETTE_RGB565_BE && ucPaletteType != GIF_PALETTE_RGB888) + _gif.iError = GIF_INVALID_PARAMETER; + _gif.ucPaletteType = ucPaletteType; + _gif.ucDrawType = GIF_DRAW_RAW; // assume RAW pixel handling + _gif.pFrameBuffer = NULL; +} /* begin() */ +// +// Play a single frame +// returns: +// 1 = good result and more frames exist +// 0 = no more frames exist, a frame may or may not have been played: use getLastError() and look for GIF_SUCCESS to know if a frame was played +// -1 = error +int AnimatedGIF::playFrame(bool bSync, int *delayMilliseconds, void *pUser) +{ +int rc; +#if !defined( __MACH__ ) && !defined( __LINUX__ ) +long lTime = millis(); +#endif + + if (_gif.GIFFile.iPos >= _gif.GIFFile.iSize-1) // no more data exists + { + (*_gif.pfnSeek)(&_gif.GIFFile, 0); // seek to start + } + if (GIFParseInfo(&_gif, 0)) + { + _gif.pUser = pUser; + if (_gif.iError == GIF_EMPTY_FRAME) // don't try to decode it + return 0; + rc = DecodeLZW(&_gif, 0); + if (rc != 0) // problem + return -1; + } + else + { + // The file is "malformed" in that there is a bunch of non-image data after + // the last frame. Return as if all is well, though if needed getLastError() + // can be used to see if a frame was actually processed: + // GIF_SUCCESS -> frame processed, GIF_EMPTY_FRAME -> no frame processed + if (_gif.iError == GIF_EMPTY_FRAME) + { + if (delayMilliseconds) + *delayMilliseconds = 0; + return 0; + } + return -1; // error parsing the frame info, we may be at the end of the file + } + // Return 1 for more frames or 0 if this was the last frame + if (bSync) + { +#if !defined( __MACH__ ) && !defined( __LINUX__ ) + lTime = millis() - lTime; + if (lTime < _gif.iFrameDelay) // need to pause a bit + delay(_gif.iFrameDelay - lTime); +#endif // __LINUX__ + } + if (delayMilliseconds) // if not NULL, return the frame delay time + *delayMilliseconds = _gif.iFrameDelay; + return (_gif.GIFFile.iPos < _gif.GIFFile.iSize-10); +} /* playFrame() */ diff --git a/lib/AnimatedGIF/AnimatedGIF.h b/lib/AnimatedGIF/AnimatedGIF.h new file mode 100644 index 0000000000000..e1868bb6cd5a6 --- /dev/null +++ b/lib/AnimatedGIF/AnimatedGIF.h @@ -0,0 +1,216 @@ +// Copyright 2020 BitBank Software, Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//=========================================================================== + +#ifndef __ANIMATEDGIF__ +#define __ANIMATEDGIF__ +#if defined( PICO_BUILD ) || defined( __MACH__ ) || defined( __LINUX__ ) || defined( __MCUXPRESSO ) +#include +#include +#include +#include +#define memcpy_P memcpy +#define PROGMEM +#else +#include +#endif +// +// GIF Animator +// Written by Larry Bank +// Copyright (c) 2020 BitBank Software, Inc. +// bitbank@pobox.com +// +// Designed to decode images up to 480x320 +// using less than 22K of RAM +// + +/* GIF Defines and variables */ +#define MAX_CHUNK_SIZE 255 +#define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE) +#define LZW_HIGHWATER (4*MAX_CHUNK_SIZE) +#ifdef __LINUX__ +#define MAX_WIDTH 2048 +#else +#define MAX_WIDTH 320 +#endif // __LINUX__ +#define FILE_BUF_SIZE 4096 + +#define PIXEL_FIRST 0 +#define PIXEL_LAST 4096 +#define LINK_UNUSED 5911 // 0x1717 to use memset +#define LINK_END 5912 +#define MAX_HASH 5003 +#define MAXMAXCODE 4096 + +enum { + GIF_PALETTE_RGB565_LE = 0, // little endian (default) + GIF_PALETTE_RGB565_BE, // big endian + GIF_PALETTE_RGB888 // original 24-bpp entries +}; +// for compatibility with older code +#define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE +#define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE +// +// Draw callback pixel type +// RAW = 8-bit palettized pixels requiring transparent pixel handling +// COOKED = 16 or 24-bpp fully rendered pixels ready for display +// +enum { + GIF_DRAW_RAW = 0, + GIF_DRAW_COOKED +}; + +enum { + GIF_SUCCESS = 0, + GIF_DECODE_ERROR, + GIF_TOO_WIDE, + GIF_INVALID_PARAMETER, + GIF_UNSUPPORTED_FEATURE, + GIF_FILE_NOT_OPEN, + GIF_EARLY_EOF, + GIF_EMPTY_FRAME, + GIF_BAD_FILE, + GIF_ERROR_MEMORY +}; + +typedef struct gif_file_tag +{ + int32_t iPos; // current file position + int32_t iSize; // file size + uint8_t *pData; // memory file pointer + void * fHandle; // class pointer to File/SdFat or whatever you want +} GIFFILE; + +typedef struct gif_info_tag +{ + int32_t iFrameCount; // total frames in file + int32_t iDuration; // duration of animation in milliseconds + int32_t iMaxDelay; // maximum frame delay + int32_t iMinDelay; // minimum frame delay +} GIFINFO; + +typedef struct gif_draw_tag +{ + int iX, iY; // Corner offset of this frame on the canvas + int y; // current line being drawn (0 = top line of image) + int iWidth, iHeight; // size of this frame + void *pUser; // user supplied pointer + uint8_t *pPixels; // 8-bit source pixels for this line + uint16_t *pPalette; // little or big-endian RGB565 palette entries (default) + uint8_t *pPalette24; // RGB888 palette (optional) + uint8_t ucTransparent; // transparent color + uint8_t ucHasTransparency; // flag indicating the transparent color is in use + uint8_t ucDisposalMethod; // frame disposal method + uint8_t ucBackground; // background color + uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used +} GIFDRAW; + +// Callback function prototypes +typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); +typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition); +typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw); +typedef void * (GIF_OPEN_CALLBACK)(const char *szFilename, int32_t *pFileSize); +typedef void (GIF_CLOSE_CALLBACK)(void *pHandle); +typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize); +typedef void (GIF_FREE_CALLBACK)(void *buffer); +// +// our private structure to hold a GIF image decode state +// +typedef struct gif_image_tag +{ + int iWidth, iHeight, iCanvasWidth, iCanvasHeight; + int iX, iY; // GIF corner offset + int iBpp; + int iError; // last error + int iFrameDelay; // delay in milliseconds for this frame + int iRepeatCount; // NETSCAPE animation repeat count. 0=forever + int iXCount, iYCount; // decoding position in image (countdown values) + int iLZWOff; // current LZW data offset + int iLZWSize; // current quantity of data in the LZW buffer + int iCommentPos; // file offset of start of comment data + short sCommentLen; // length of comment + GIF_READ_CALLBACK *pfnRead; + GIF_SEEK_CALLBACK *pfnSeek; + GIF_DRAW_CALLBACK *pfnDraw; + GIF_OPEN_CALLBACK *pfnOpen; + GIF_CLOSE_CALLBACK *pfnClose; + GIFFILE GIFFile; + void *pUser; + unsigned char *pFrameBuffer; + unsigned char *pPixels, *pOldPixels; + unsigned char ucLineBuf[MAX_WIDTH]; // current line + unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack + unsigned short pPalette[384]; // can hold RGB565 or RGB888 - set in begin() + unsigned short pLocalPalette[384]; // color palettes for GIF images + unsigned char ucLZW[LZW_BUF_SIZE]; // holds 6 chunks (6x255) of GIF LZW data packed together + unsigned short usGIFTable[4096]; + unsigned char ucGIFPixels[8192]; + unsigned char bEndOfFrame; + unsigned char ucGIFBits, ucBackground, ucTransparent, ucCodeStart, ucMap, bUseLocalPalette; + unsigned char ucPaletteType; // RGB565 or RGB888 + unsigned char ucDrawType; // RAW or COOKED +} GIFIMAGE; + +#ifdef __cplusplus +// +// The GIF class wraps portable C code which does the actual work +// +class AnimatedGIF +{ + public: + int open(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); + int openFLASH(uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); + int open(const char *szFilename, GIF_OPEN_CALLBACK *pfnOpen, GIF_CLOSE_CALLBACK *pfnClose, GIF_READ_CALLBACK *pfnRead, GIF_SEEK_CALLBACK *pfnSeek, GIF_DRAW_CALLBACK *pfnDraw); + void close(); + void reset(); + void begin(unsigned char ucPaletteType = GIF_PALETTE_RGB565_LE); + void begin(int iEndian, unsigned char ucPaletteType) { begin(ucPaletteType); }; + int playFrame(bool bSync, int *delayMilliseconds, void *pUser = NULL); + int getCanvasWidth(); + int allocFrameBuf(GIF_ALLOC_CALLBACK *pfnAlloc); + int setDrawType(int iType); + int freeFrameBuf(GIF_FREE_CALLBACK *pfnFree); + uint8_t *getFrameBuf(); + int getCanvasHeight(); + int getLoopCount(); + int getInfo(GIFINFO *pInfo); + int getLastError(); + int getComment(char *destBuffer); + + private: + GIFIMAGE _gif; +}; +#else +// C interface + int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw); + int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw); + void GIF_close(GIFIMAGE *pGIF); + void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType); + void GIF_reset(GIFIMAGE *pGIF); + int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser); + int GIF_getCanvasWidth(GIFIMAGE *pGIF); + int GIF_getCanvasHeight(GIFIMAGE *pGIF); + int GIF_getComment(GIFIMAGE *pGIF, char *destBuffer); + int GIF_getInfo(GIFIMAGE *pGIF, GIFINFO *pInfo); + int GIF_getLastError(GIFIMAGE *pGIF); + int GIF_getLoopCount(GIFIMAGE *pGIF); +#endif // __cplusplus + +// Due to unaligned memory causing an exception, we have to do these macros the slow way +#define INTELSHORT(p) ((*p) + (*(p+1)<<8)) +#define INTELLONG(p) ((*p) + (*(p+1)<<8) + (*(p+2)<<16) + (*(p+3)<<24)) +#define MOTOSHORT(p) (((*(p))<<8) + (*(p+1))) +#define MOTOLONG(p) (((*p)<<24) + ((*(p+1))<<16) + ((*(p+2))<<8) + (*(p+3))) + +// Must be a 32-bit target processor +#define REGISTER_WIDTH 32 + +#endif // __ANIMATEDGIF__ diff --git a/shared-module/displayio/AnimatedGIF_circuitpy.h b/lib/AnimatedGIF/AnimatedGIF_circuitpy.h similarity index 99% rename from shared-module/displayio/AnimatedGIF_circuitpy.h rename to lib/AnimatedGIF/AnimatedGIF_circuitpy.h index f81108695c012..e401935a95040 100644 --- a/shared-module/displayio/AnimatedGIF_circuitpy.h +++ b/lib/AnimatedGIF/AnimatedGIF_circuitpy.h @@ -9,6 +9,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // =========================================================================== +// +// Modified 2023 by Mark Komus to work for CircuitPython +// #ifndef __ANIMATEDGIF__ #define __ANIMATEDGIF__ diff --git a/lib/AnimatedGIF/README.md b/lib/AnimatedGIF/README.md new file mode 100644 index 0000000000000..25b8cd59b79bf --- /dev/null +++ b/lib/AnimatedGIF/README.md @@ -0,0 +1,5 @@ +This library is from the AnimatedGIF Arduino GIF decoder by Larry Bank. +Released under the Apache License 2.0 +[AnimatedGIF](https://github.com/bitbank2/AnimatedGIF) + +It has been modified for use in CircuitPython by Mark Komus. diff --git a/shared-module/displayio/gif.c b/lib/AnimatedGIF/gif.c similarity index 60% rename from shared-module/displayio/gif.c rename to lib/AnimatedGIF/gif.c index a4ad31608da41..00a013756226d 100644 --- a/shared-module/displayio/gif.c +++ b/lib/AnimatedGIF/gif.c @@ -17,14 +17,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// =========================================================================== -#define __LINUX__ +//=========================================================================== +// +// Modified 2023 by Mark Komus to work for CircuitPython +// #include "AnimatedGIF_circuitpy.h" -#include "py/mperrno.h" -#include "py/runtime.h" - - #ifdef HAL_ESP32_HAL_H_ #define memcpy_P memcpy #endif @@ -40,13 +38,16 @@ static int DecodeLZW(GIFIMAGE *pImage, int iOptions); static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); static int32_t seekMem(GIFFILE *pFile, int32_t iPosition); int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo); -#if defined(PICO_BUILD) || defined(__LINUX__) || defined(__MCUXPRESSO) + +#if defined ( __LINUX__ ) || defined( __MCUXPRESSO ) static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen); static int32_t seekFile(GIFFILE *pFile, int32_t iPosition); static void closeFile(void *handle); +#endif // C API -int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) { +int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw) +{ pGIF->iError = GIF_SUCCESS; pGIF->pfnRead = readMem; pGIF->pfnSeek = seekMem; @@ -59,7 +60,8 @@ int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK } /* GIF_openRAM() */ #ifdef __LINUX__ -int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw) { +int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw) +{ pGIF->iError = GIF_SUCCESS; pGIF->pfnRead = readFile; pGIF->pfnSeek = seekFile; @@ -67,9 +69,8 @@ int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnD pGIF->pfnOpen = NULL; pGIF->pfnClose = closeFile; pGIF->GIFFile.fHandle = fopen(szFilename, "r+b"); - if (pGIF->GIFFile.fHandle == NULL) { - return 0; - } + if (pGIF->GIFFile.fHandle == NULL) + return 0; fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_END); pGIF->GIFFile.iSize = (int)ftell((FILE *)pGIF->GIFFile.fHandle); fseek((FILE *)pGIF->GIFFile.fHandle, 0, SEEK_SET); @@ -77,18 +78,20 @@ int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnD } /* GIF_openFile() */ #endif -void GIF_close(GIFIMAGE *pGIF) { - if (pGIF->pfnClose) { +void GIF_close(GIFIMAGE *pGIF) +{ + if (pGIF->pfnClose) (*pGIF->pfnClose)(pGIF->GIFFile.fHandle); - } } /* GIF_close() */ -void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType) { +void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType) +{ memset(pGIF, 0, sizeof(GIFIMAGE)); pGIF->ucPaletteType = ucPaletteType; } /* GIF_begin() */ -void GIF_reset(GIFIMAGE *pGIF) { +void GIF_reset(GIFIMAGE *pGIF) +{ (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); } /* GIF_reset() */ @@ -98,48 +101,53 @@ void GIF_reset(GIFIMAGE *pGIF) { // 0 = good decode, no more frames // -1 = error // -int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser) { - int rc; +int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser) +{ +int rc; - if (delayMilliseconds) { - *delayMilliseconds = 0; // clear any old valid - } - if (pGIF->GIFFile.iPos >= pGIF->GIFFile.iSize - 1) { // no more data exists + if (delayMilliseconds) + *delayMilliseconds = 0; // clear any old valid + if (pGIF->GIFFile.iPos >= pGIF->GIFFile.iSize-1) // no more data exists + { (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek to start } - if (GIFParseInfo(pGIF, 0)) { + if (GIFParseInfo(pGIF, 0)) + { pGIF->pUser = pUser; - if (pGIF->iError == GIF_EMPTY_FRAME) { // don't try to decode it + if (pGIF->iError == GIF_EMPTY_FRAME) // don't try to decode it return 0; - } rc = DecodeLZW(pGIF, 0); - if (rc != 0) { // problem + if (rc != 0) // problem return 0; - } - } else { + } + else + { return 0; // error parsing the frame info, we may be at the end of the file } // Return 1 for more frames or 0 if this was the last frame - if (delayMilliseconds) { // if not NULL, return the frame delay time + if (delayMilliseconds) // if not NULL, return the frame delay time *delayMilliseconds = pGIF->iFrameDelay; - } - return pGIF->GIFFile.iPos < pGIF->GIFFile.iSize - 1; + return (pGIF->GIFFile.iPos < pGIF->GIFFile.iSize-1); } /* GIF_playFrame() */ -int GIF_getCanvasWidth(GIFIMAGE *pGIF) { +int GIF_getCanvasWidth(GIFIMAGE *pGIF) +{ return pGIF->iCanvasWidth; } /* GIF_getCanvasWidth() */ -int GIF_getCanvasHeight(GIFIMAGE *pGIF) { +int GIF_getCanvasHeight(GIFIMAGE *pGIF) +{ return pGIF->iCanvasHeight; } /* GIF_getCanvasHeight() */ -int GIF_getLoopCount(GIFIMAGE *pGIF) { +int GIF_getLoopCount(GIFIMAGE *pGIF) +{ return pGIF->iRepeatCount; } /* GIF_getLoopCount() */ -int GIF_getComment(GIFIMAGE *pGIF, char *pDest) { - int32_t iOldPos; +int GIF_getComment(GIFIMAGE *pGIF, char *pDest) +{ +int32_t iOldPos; iOldPos = pGIF->GIFFile.iPos; // keep old position (*pGIF->pfnSeek)(&pGIF->GIFFile, pGIF->iCommentPos); @@ -150,7 +158,8 @@ int GIF_getComment(GIFIMAGE *pGIF, char *pDest) { } /* GIF_getComment() */ -int GIF_getLastError(GIFIMAGE *pGIF) { +int GIF_getLastError(GIFIMAGE *pGIF) +{ return pGIF->iError; } /* GIF_getLastError() */ @@ -158,26 +167,24 @@ int GIF_init(GIFIMAGE *pGIF) { return GIFInit(pGIF); } -#endif // !__cplusplus // // Helper functions for memory based images // -static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { +static int32_t readMem(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ int32_t iBytesRead; iBytesRead = iLen; - if ((pFile->iSize - pFile->iPos) < iLen) { - iBytesRead = pFile->iSize - pFile->iPos; - } - if (iBytesRead <= 0) { - return 0; - } + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos; + if (iBytesRead <= 0) + return 0; memmove(pBuf, &pFile->pData[pFile->iPos], iBytesRead); pFile->iPos += iBytesRead; return iBytesRead; } /* readMem() */ -/* +#ifndef CIRCUITPY static int32_t readFLASH(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { int32_t iBytesRead; @@ -190,44 +197,41 @@ static int32_t readFLASH(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) memcpy_P(pBuf, &pFile->pData[pFile->iPos], iBytesRead); pFile->iPos += iBytesRead; return iBytesRead; -} *//* readFLASH() */ +} /* readFLASH() */ +#endif -static int32_t seekMem(GIFFILE *pFile, int32_t iPosition) { - if (iPosition < 0) { - iPosition = 0; - } else if (iPosition >= pFile->iSize) { - iPosition = pFile->iSize - 1; - } +static int32_t seekMem(GIFFILE *pFile, int32_t iPosition) +{ + if (iPosition < 0) iPosition = 0; + else if (iPosition >= pFile->iSize) iPosition = pFile->iSize-1; pFile->iPos = iPosition; return iPosition; } /* seekMem() */ -#if defined(__LINUX__) || defined(__MCUXPRESSO) -static void closeFile(void *handle) { +#if defined ( __LINUX__ ) || defined( __MCUXPRESSO ) +static void closeFile(void *handle) +{ fclose((FILE *)handle); } /* closeFile() */ -static int32_t seekFile(GIFFILE *pFile, int32_t iPosition) { - if (iPosition < 0) { - iPosition = 0; - } else if (iPosition >= pFile->iSize) { - iPosition = pFile->iSize - 1; - } +static int32_t seekFile(GIFFILE *pFile, int32_t iPosition) +{ + if (iPosition < 0) iPosition = 0; + else if (iPosition >= pFile->iSize) iPosition = pFile->iSize-1; pFile->iPos = iPosition; fseek((FILE *)pFile->fHandle, iPosition, SEEK_SET); return iPosition; } /* seekMem() */ -static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { +static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ int32_t iBytesRead; iBytesRead = iLen; - if ((pFile->iSize - pFile->iPos) < iLen) { - iBytesRead = pFile->iSize - pFile->iPos; - } - if (iBytesRead <= 0) { - return 0; - } + if ((pFile->iSize - pFile->iPos) < iLen) + iBytesRead = pFile->iSize - pFile->iPos; + if (iBytesRead <= 0) + return 0; iBytesRead = (int)fread(pBuf, 1, iBytesRead, (FILE *)pFile->fHandle); pFile->iPos += iBytesRead; return iBytesRead; @@ -243,17 +247,17 @@ static int32_t readFile(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen) { // returns 1 for success, 0 for failure // Fills in the canvas size of the GIFIMAGE structure // -static int GIFInit(GIFIMAGE *pGIF) { +static int GIFInit(GIFIMAGE *pGIF) +{ pGIF->GIFFile.iPos = 0; // start at beginning of file - if (!GIFParseInfo(pGIF, 1)) { // gather info for the first frame - return 0; // something went wrong; not a GIF file? - } + if (!GIFParseInfo(pGIF, 1)) // gather info for the first frame + return 0; // something went wrong; not a GIF file? (*pGIF->pfnSeek)(&pGIF->GIFFile, 0); // seek back to start of the file if (pGIF->iCanvasWidth > MAX_WIDTH) { // need to allocate more space pGIF->iError = GIF_TOO_WIDE; return 0; } - return 1; + return 1; } /* GIFInit() */ // @@ -262,7 +266,8 @@ static int GIFInit(GIFIMAGE *pGIF) { // and return the canvas size only // Returns 1 for success, 0 for failure // -static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { +static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) +{ int i, j, iColorTableBits; int iBytesRead; unsigned char c, *p; @@ -276,92 +281,97 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { pPage->iRepeatCount = -1; // assume NETSCAPE loop count is not specified iReadSize = (bInfoOnly) ? 12 : MAX_CHUNK_SIZE; // If you try to read past the EOF, the SD lib will return garbage data - if (iStartPos + iReadSize > pPage->GIFFile.iSize) { - iReadSize = (pPage->GIFFile.iSize - iStartPos - 1); - } + if (iStartPos + iReadSize > pPage->GIFFile.iSize) + iReadSize = (pPage->GIFFile.iSize - iStartPos - 1); p = pPage->ucFileBuf; - iBytesRead = (*pPage->pfnRead)(&pPage->GIFFile, pPage->ucFileBuf, iReadSize); // 255 is plenty for now + iBytesRead = (*pPage->pfnRead)(&pPage->GIFFile, pPage->ucFileBuf, iReadSize); // 255 is plenty for now - if (iBytesRead != iReadSize) { // we're at the end of the file - pPage->iError = GIF_EARLY_EOF; - return 0; + if (iBytesRead != iReadSize) // we're at the end of the file + { + pPage->iError = GIF_EARLY_EOF; + return 0; } - if (iStartPos == 0) { // start of the file - // canvas size - if (memcmp(p, "GIF89", 5) != 0 && memcmp(p, "GIF87", 5) != 0) { // not a GIF file - pPage->iError = GIF_BAD_FILE; - return 0; + if (iStartPos == 0) // start of the file + { // canvas size + if (memcmp(p, "GIF89", 5) != 0 && memcmp(p, "GIF87", 5) != 0) // not a GIF file + { + pPage->iError = GIF_BAD_FILE; + return 0; } pPage->iCanvasWidth = pPage->iWidth = INTELSHORT(&p[6]); pPage->iCanvasHeight = pPage->iHeight = INTELSHORT(&p[8]); pPage->iBpp = ((p[10] & 0x70) >> 4) + 1; - if (bInfoOnly) { - return 1; // we've got the info we needed, leave - } + if (bInfoOnly) + return 1; // we've got the info we needed, leave iColorTableBits = (p[10] & 7) + 1; // Log2(size) of the color table pPage->ucBackground = p[11]; // background color pPage->ucGIFBits = 0; iOffset = 13; - if (p[10] & 0x80) { // global color table? - // by default, convert to byte-reversed RGB565 for immediate use + if (p[10] & 0x80) // global color table? + { // by default, convert to byte-reversed RGB565 for immediate use // Read enough additional data for the color table - iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], 3 * (1 << iColorTableBits)); - if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { - for (i = 0; i < (1 << iColorTableBits); i++) + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], 3*(1<ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) + { + for (i=0; i<(1<> 3) << 11); // R - usRGB565 |= ((p[iOffset + 1] >> 2) << 5); // G - usRGB565 |= (p[iOffset + 2] >> 3); // B - if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) { + usRGB565 |= ((p[iOffset+1] >> 2) << 5); // G + usRGB565 |= (p[iOffset+2] >> 3); // B + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) pPage->pPalette[i] = usRGB565; - } else { + else pPage->pPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first - } iOffset += 3; } - } else { // just copy it as-is - memcpy(pPage->pPalette, &p[iOffset], (1 << iColorTableBits) * 3); + } + else // just copy it as-is + { + memcpy(pPage->pPalette, &p[iOffset], (1<ucGIFBits = p[iOffset + 1]; // packed fields - pPage->iFrameDelay = (INTELSHORT(&p[iOffset + 2])) * 10; // delay in ms - if (pPage->iFrameDelay <= 1) { // 0-1 is going to make it run at 60fps; use 100 (10fps) as a reasonable substitute - pPage->iFrameDelay = 100; - } - if (pPage->ucGIFBits & 1) { // transparent color is used - pPage->ucTransparent = p[iOffset + 4]; // transparent color index - } + if (p[iOffset] == 4) // correct length + { + pPage->ucGIFBits = p[iOffset+1]; // packed fields + pPage->iFrameDelay = (INTELSHORT(&p[iOffset+2]))*10; // delay in ms + if (pPage->iFrameDelay <= 1) // 0-1 is going to make it run at 60fps; use 100 (10fps) as a reasonable substitute + pPage->iFrameDelay = 100; + if (pPage->ucGIFBits & 1) // transparent color is used + pPage->ucTransparent = p[iOffset+4]; // transparent color index iOffset += 6; } // else // error break; case 0xff: /* App extension */ c = 1; - while (c) { /* Skip all data sub-blocks */ + while (c) /* Skip all data sub-blocks */ + { c = p[iOffset++]; /* Block length */ - if ((iBytesRead - iOffset) < (c + 32)) { // need to read more data first - memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead - iOffset)); // move existing data down + if ((iBytesRead - iOffset) < (c+32)) // need to read more data first + { + memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead-iOffset)); // move existing data down iBytesRead -= iOffset; iStartPos += iOffset; iOffset = 0; - iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c + 32); + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c+32); } - if (c == 11) { // fixed block length - // Netscape app block contains the repeat count - if (memcmp(&p[iOffset], "NETSCAPE2.0", 11) == 0) { - if (p[iOffset + 11] == 3 && p[iOffset + 12] == 1) { // loop count - pPage->iRepeatCount = INTELSHORT(&p[iOffset + 13]); - } + if (c == 11) // fixed block length + { // Netscape app block contains the repeat count + if (memcmp(&p[iOffset], "NETSCAPE2.0", 11) == 0) + { + if (p[iOffset+11] == 3 && p[iOffset+12] == 1) // loop count + pPage->iRepeatCount = INTELSHORT(&p[iOffset+13]); } } iOffset += (int)c; /* Skip to next sub-block */ @@ -370,13 +380,14 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { case 0x01: /* Text extension */ c = 1; j = 0; - while (c) { /* Skip all data sub-blocks */ + while (c) /* Skip all data sub-blocks */ + { c = p[iOffset++]; /* Block length */ - if (j == 0) { // use only first block + if (j == 0) // use only first block + { j = c; - if (j > 127) { // max comment length = 127 + if (j > 127) // max comment length = 127 j = 127; - } // memcpy(pPage->szInfo1, &p[iOffset], j); // pPage->szInfo1[j] = '\0'; j = 1; @@ -386,16 +397,19 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { break; case 0xfe: /* Comment */ c = 1; - while (c) { /* Skip all data sub-blocks */ + while (c) /* Skip all data sub-blocks */ + { c = p[iOffset++]; /* Block length */ - if ((iBytesRead - iOffset) < (c + 32)) { // need to read more data first - memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead - iOffset)); // move existing data down + if ((iBytesRead - iOffset) < (c+32)) // need to read more data first + { + memmove(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], (iBytesRead-iOffset)); // move existing data down iBytesRead -= iOffset; iStartPos += iOffset; iOffset = 0; - iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c + 32); + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], c+32); } - if (pPage->iCommentPos == 0) { // Save first block info + if (pPage->iCommentPos == 0) // Save first block info + { pPage->iCommentPos = iStartPos + iOffset; pPage->sCommentLen = c; } @@ -407,13 +421,14 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { pPage->iError = GIF_DECODE_ERROR; return 0; } /* switch */ - } else { // invalid byte, stop decoding - if (pPage->GIFFile.iSize - iStartPos < 32) { // non-image bytes at end of file? + } + else // invalid byte, stop decoding + { + if (pPage->GIFFile.iSize - iStartPos < 32) // non-image bytes at end of file? pPage->iError = GIF_EMPTY_FRAME; - } else { + else /* Bad header info */ pPage->iError = GIF_DECODE_ERROR; - } return 0; } } /* while */ @@ -422,14 +437,13 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { return 1; } - if (p[iOffset] == ',') { + if (p[iOffset] == ',') iOffset++; - } // This particular frame's size and position on the main frame (if animated) pPage->iX = INTELSHORT(&p[iOffset]); - pPage->iY = INTELSHORT(&p[iOffset + 2]); - pPage->iWidth = INTELSHORT(&p[iOffset + 4]); - pPage->iHeight = INTELSHORT(&p[iOffset + 6]); + pPage->iY = INTELSHORT(&p[iOffset+2]); + pPage->iWidth = INTELSHORT(&p[iOffset+4]); + pPage->iHeight = INTELSHORT(&p[iOffset+6]); iOffset += 8; /* Image descriptor @@ -440,28 +454,30 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { pixel+1 = # bits per pixel for this image */ pPage->ucMap = p[iOffset++]; - if (pPage->ucMap & 0x80) { // local color table? - // by default, convert to byte-reversed RGB565 for immediate use - j = (1 << ((pPage->ucMap & 7) + 1)); + if (pPage->ucMap & 0x80) // local color table? + {// by default, convert to byte-reversed RGB565 for immediate use + j = (1<<((pPage->ucMap & 7)+1)); // Read enough additional data for the color table - iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], j *3); - if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { - for (i = 0; i < j; i++) + iBytesRead += (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucFileBuf[iBytesRead], j*3); + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) + { + for (i=0; i> 3) << 11); // R - usRGB565 |= ((p[iOffset + 1] >> 2) << 5); // G - usRGB565 |= (p[iOffset + 2] >> 3); // B - if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) { + usRGB565 |= ((p[iOffset+1] >> 2) << 5); // G + usRGB565 |= (p[iOffset+2] >> 3); // B + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE) pPage->pLocalPalette[i] = usRGB565; - } else { + else pPage->pLocalPalette[i] = __builtin_bswap16(usRGB565); // SPI wants MSB first - } iOffset += 3; } - } else { // just copy it as-is + } + else // just copy it as-is + { memcpy(pPage->pLocalPalette, &p[iOffset], j * 3); - iOffset += j * 3; + iOffset += j*3; } pPage->bUseLocalPalette = 1; } @@ -470,40 +486,45 @@ static int GIFParseInfo(GIFIMAGE *pPage, int bInfoOnly) { pPage->iBpp = cGIFBits[pPage->ucCodeStart]; // we are re-using the same buffer turning GIF file data // into "pure" LZW - pPage->iLZWSize = 0; // we're starting with no LZW data yet - c = 1; // get chunk length - while (c && iOffset < iBytesRead) { + pPage->iLZWSize = 0; // we're starting with no LZW data yet + c = 1; // get chunk length + while (c && iOffset < iBytesRead) + { // Serial.printf("iOffset=%d, iBytesRead=%d\n", iOffset, iBytesRead); - c = p[iOffset++]; // get chunk length + c = p[iOffset++]; // get chunk length // Serial.printf("Chunk size = %d\n", c); - if (c <= (iBytesRead - iOffset)) { - memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], c); - pPage->iLZWSize += c; - iOffset += c; - } else { // partial chunk in our buffer - int iPartialLen = (iBytesRead - iOffset); - memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], iPartialLen); - pPage->iLZWSize += iPartialLen; - iOffset += iPartialLen; - (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c - iPartialLen); - pPage->iLZWSize += (c - iPartialLen); - } - if (c == 0) { - pPage->bEndOfFrame = 1; // signal not to read beyond the end of the frame - } - } + if (c <= (iBytesRead - iOffset)) + { + memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], c); + pPage->iLZWSize += c; + iOffset += c; + } + else // partial chunk in our buffer + { + int iPartialLen = (iBytesRead - iOffset); + memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], iPartialLen); + pPage->iLZWSize += iPartialLen; + iOffset += iPartialLen; + (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c - iPartialLen); + pPage->iLZWSize += (c - iPartialLen); + } + if (c == 0) + pPage->bEndOfFrame = 1; // signal not to read beyond the end of the frame + } // seeking on an SD card is VERY VERY SLOW, so use the data we've already read by de-chunking it // in this case, there's too much data, so we have to seek backwards a bit - if (iOffset < iBytesRead) { + if (iOffset < iBytesRead) + { // Serial.printf("Need to seek back %d bytes\n", iBytesRead - iOffset); - (*pPage->pfnSeek)(&pPage->GIFFile, iStartPos + iOffset); // position file to new spot - } + (*pPage->pfnSeek)(&pPage->GIFFile, iStartPos + iOffset); // position file to new spot + } return 1; // we are now at the start of the chunk data } /* GIFParseInfo() */ // // Gather info about an animated GIF file // -int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { +int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) +{ int iOff, iNumFrames; int iDelay, iMaxDelay, iMinDelay, iTotalDelay; int iReadAmount; @@ -518,7 +539,7 @@ int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { iMinDelay = 10000; iNumFrames = 1; iDataRemaining = pPage->GIFFile.iSize; - cBuf = (uint8_t *)pPage->ucFileBuf; + cBuf = (uint8_t *) pPage->ucFileBuf; (*pPage->pfnSeek)(&pPage->GIFFile, 0); iDataAvailable = (*pPage->pfnRead)(&pPage->GIFFile, cBuf, FILE_BUF_SIZE); iDataRemaining -= iDataAvailable; @@ -526,68 +547,72 @@ int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { iOff = 10; c = cBuf[iOff]; // get info bits iOff += 3; /* Skip flags, background color & aspect ratio */ - if (c & 0x80) { /* Deal with global color table */ + if (c & 0x80) /* Deal with global color table */ + { c &= 7; /* Get the number of colors defined */ - iOff += (2 << c) * 3; /* skip color table */ + iOff += (2<pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); iDataAvailable += iReadAmount; iDataRemaining -= iReadAmount; lFileOff += iReadAmount; } - switch (cBuf[iOff]) + switch(cBuf[iOff]) { case 0x3b: /* End of file */ /* we were fooled into thinking there were more pages */ iNumFrames--; goto gifpagesz; - // F9 = Graphic Control Extension (fixed length of 4 bytes) - // FE = Comment Extension - // FF = Application Extension - // 01 = Plain Text Extension + // F9 = Graphic Control Extension (fixed length of 4 bytes) + // FE = Comment Extension + // FF = Application Extension + // 01 = Plain Text Extension case 0x21: /* Extension block */ - if (cBuf[iOff + 1] == 0xf9 && cBuf[iOff + 2] == 4) { // Graphic Control Extension - // cBuf[iOff+3]; // page disposition flags - iDelay = cBuf[iOff + 4]; // delay low byte - iDelay |= ((uint16_t)(cBuf[iOff + 5]) << 8); // delay high byte - if (iDelay < 2) { // too fast, provide a default + if (cBuf[iOff+1] == 0xf9 && cBuf[iOff+2] == 4) // Graphic Control Extension + { + //cBuf[iOff+3]; // page disposition flags + iDelay = cBuf[iOff+4]; // delay low byte + iDelay |= ((uint16_t)(cBuf[iOff+5]) << 8); // delay high byte + if (iDelay < 2) // too fast, provide a default iDelay = 2; - } iDelay *= 10; // turn JIFFIES into milliseconds iTotalDelay += iDelay; - if (iDelay > iMaxDelay) { - iMaxDelay = iDelay; - } else if (iDelay < iMinDelay) { - iMinDelay = iDelay; - } - // (cBuf[iOff+6]; // transparent color index + if (iDelay > iMaxDelay) iMaxDelay = iDelay; + else if (iDelay < iMinDelay) iMinDelay = iDelay; + // (cBuf[iOff+6]; // transparent color index } iOff += 2; /* skip to length */ iOff += (int)cBuf[iOff]; /* Skip the data block */ iOff++; - // block terminator or optional sub blocks + // block terminator or optional sub blocks c = cBuf[iOff++]; /* Skip any sub-blocks */ - while (c) { - iOff += (int)c; - c = cBuf[iOff++]; - if ((iDataAvailable - iOff) < (c + 258)) { // need to read more data first - memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + while (c) + { + iOff += (int)c; + c = cBuf[iOff++]; + if ((iDataAvailable - iOff) < (c+258)) // need to read more data first + { + memmove(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down iDataAvailable -= iOff; iOff = 0; - iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); iDataAvailable += iReadAmount; iDataRemaining -= iReadAmount; lFileOff += iReadAmount; } } - if (c != 0) { // problem, we went past the end + if (c != 0) // problem, we went past the end + { iNumFrames--; // possible corrupt data; stop goto gifpagesz; } @@ -596,47 +621,51 @@ int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { bExt = 0; /* Stop doing extension blocks */ break; default: - /* Corrupt data, stop here */ + /* Corrupt data, stop here */ iNumFrames--; goto gifpagesz; } // switch } // while - if (iOff >= iDataAvailable) { // problem - iNumFrames--; // possible corrupt data; stop - goto gifpagesz; + if (iOff >= iDataAvailable) // problem + { + iNumFrames--; // possible corrupt data; stop + goto gifpagesz; } - /* Start of image data */ - c = cBuf[iOff + 9]; /* Get the flags byte */ + /* Start of image data */ + c = cBuf[iOff+9]; /* Get the flags byte */ iOff += 10; /* Skip image position and size */ - if (c & 0x80) { /* Local color table */ + if (c & 0x80) /* Local color table */ + { c &= 7; - iOff += (2 << c) * 3; + iOff += (2<pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE - iDataAvailable); - iDataAvailable += iReadAmount; - iDataRemaining -= iReadAmount; - lFileOff += iReadAmount; - } + if ((iDataAvailable - iOff) < (c+258)) // need to read more data first + { + if (iOff < iDataAvailable) { + memmove(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down + iDataAvailable -= iOff; + iOff = 0; + } else { // already points beyond end + iOff -= iDataAvailable; + iDataAvailable = 0; + } + iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], FILE_BUF_SIZE-iDataAvailable); + iDataAvailable += iReadAmount; + iDataRemaining -= iReadAmount; + lFileOff += iReadAmount; + } c = cBuf[iOff++]; - while (c) { /* While there are more data blocks */ - if (iOff > (3 * FILE_BUF_SIZE / 4) && iDataRemaining > 0) { /* Near end of buffer, re-align */ - memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + while (c) /* While there are more data blocks */ + { + if (iOff > (3*FILE_BUF_SIZE/4) && iDataRemaining > 0) /* Near end of buffer, re-align */ + { + memmove(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down iDataAvailable -= iOff; iOff = 0; iReadAmount = (FILE_BUF_SIZE - iDataAvailable); - if (iReadAmount > iDataRemaining) { + if (iReadAmount > iDataRemaining) iReadAmount = iDataRemaining; - } iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); iDataAvailable += iReadAmount; iDataRemaining -= iReadAmount; @@ -651,19 +680,22 @@ int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { c = cBuf[iOff++]; /* Get length of next */ } /* End of image data, check for more pages... */ - if (cBuf[iOff] == 0x3b || (iDataRemaining == 0 && (iDataAvailable - iOff) < 32)) { + if (cBuf[iOff] == 0x3b || (iDataRemaining == 0 && (iDataAvailable - iOff) < 32)) + { bDone = 1; /* End of file has been reached */ - } else { /* More pages to scan */ + } + else /* More pages to scan */ + { iNumFrames++; - // read new page data starting at this offset - if (pPage->GIFFile.iSize > FILE_BUF_SIZE && iDataRemaining > 0) { // since we didn't read the whole file in one shot - memmove(cBuf, &cBuf[iOff], (iDataAvailable - iOff)); // move existing data down + // read new page data starting at this offset + if (pPage->GIFFile.iSize > FILE_BUF_SIZE && iDataRemaining > 0) // since we didn't read the whole file in one shot + { + memmove(cBuf, &cBuf[iOff], (iDataAvailable-iOff)); // move existing data down iDataAvailable -= iOff; iOff = 0; iReadAmount = (FILE_BUF_SIZE - iDataAvailable); - if (iReadAmount > iDataRemaining) { + if (iReadAmount > iDataRemaining) iReadAmount = iDataRemaining; - } iReadAmount = (*pPage->pfnRead)(&pPage->GIFFile, &cBuf[iDataAvailable], iReadAmount); iDataAvailable += iReadAmount; iDataRemaining -= iReadAmount; @@ -684,57 +716,62 @@ int GIF_getInfo(GIFIMAGE *pPage, GIFINFO *pInfo) { // returns 1 to signify more data available for this image // 0 indicates there is no more data // -static int GIFGetMoreData(GIFIMAGE *pPage) { +static int GIFGetMoreData(GIFIMAGE *pPage) +{ int iDelta = (pPage->iLZWSize - pPage->iLZWOff); unsigned char c = 1; // move any existing data down - if (pPage->bEndOfFrame || iDelta >= (LZW_BUF_SIZE - MAX_CHUNK_SIZE) || iDelta <= 0) { + if (pPage->bEndOfFrame || iDelta >= (LZW_BUF_SIZE - MAX_CHUNK_SIZE) || iDelta <= 0) return 1; // frame is finished or buffer is already full; no need to read more data - } - if (pPage->iLZWOff != 0) { + if (pPage->iLZWOff != 0) + { // NB: memcpy() fails on some systems because the src and dest ptrs overlap // so copy the bytes in a simple loop to avoid problems - for (int i = 0; i < pPage->iLZWSize - pPage->iLZWOff; i++) { - pPage->ucLZW[i] = pPage->ucLZW[i + pPage->iLZWOff]; - } - pPage->iLZWSize -= pPage->iLZWOff; - pPage->iLZWOff = 0; + for (int i=0; iiLZWSize - pPage->iLZWOff; i++) { + pPage->ucLZW[i] = pPage->ucLZW[i + pPage->iLZWOff]; + } + pPage->iLZWSize -= pPage->iLZWOff; + pPage->iLZWOff = 0; } - while (c && pPage->GIFFile.iPos < pPage->GIFFile.iSize && pPage->iLZWSize < (LZW_BUF_SIZE - MAX_CHUNK_SIZE)) { + while (c && pPage->GIFFile.iPos < pPage->GIFFile.iSize && pPage->iLZWSize < (LZW_BUF_SIZE-MAX_CHUNK_SIZE)) + { (*pPage->pfnRead)(&pPage->GIFFile, &c, 1); // current length (*pPage->pfnRead)(&pPage->GIFFile, &pPage->ucLZW[pPage->iLZWSize], c); pPage->iLZWSize += c; } - if (c == 0) { // end of frame + if (c == 0) // end of frame pPage->bEndOfFrame = 1; - } - return c != 0 && pPage->GIFFile.iPos < pPage->GIFFile.iSize; // more data available? + return (c != 0 && pPage->GIFFile.iPos < pPage->GIFFile.iSize); // more data available? } /* GIFGetMoreData() */ // // Handle transparent pixels and disposal method // Used only when a frame buffer is allocated // -static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { +static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) +{ uint8_t *d, *s; int x, iPitch = pPage->iCanvasWidth; s = pDraw->pPixels; - d = &pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer - if (pDraw->ucDisposalMethod == 2) { // restore to background color + d = &pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer + if (pDraw->ucDisposalMethod == 2) // restore to background color + { memset(d, pDraw->ucBackground, pDraw->iWidth); } // Apply the new pixels to the main image - if (pDraw->ucHasTransparency) { // if transparency used + if (pDraw->ucHasTransparency) // if transparency used + { uint8_t c, ucTransparent = pDraw->ucTransparent; - for (x = 0; x < pDraw->iWidth; x++) + for (x=0; xiWidth; x++) { c = *s++; - if (c != ucTransparent) { + if (c != ucTransparent) *d = c; - } d++; } - } else { + } + else + { memcpy(d, s, pDraw->iWidth); // just overwrite the old pixels } } /* DrawNewPixels() */ @@ -743,25 +780,29 @@ static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { // to either RGB565 or RGB888 output // Used only when a frame buffer has been allocated // -static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { +static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) +{ uint8_t *d, *s; int x; s = &pPage->pFrameBuffer[(pPage->iCanvasWidth * (pDraw->iY + pDraw->y)) + pDraw->iX]; d = &pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; // point past bottom of frame buffer - if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { + if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) + { uint16_t *pPal, *pu16; pPal = (uint16_t *)pDraw->pPalette; pu16 = (uint16_t *)d; - for (x = 0; x < pPage->iWidth; x++) + for (x=0; xiWidth; x++) { *pu16++ = pPal[*s++]; // convert to RGB565 pixels } - } else { + } + else + { uint8_t *pPal; int pixel; pPal = (uint8_t *)pDraw->pPalette; - for (x = 0; x < pPage->iWidth; x++) + for (x=0; xiWidth; x++) { pixel = *s++; *d++ = pPal[(pixel * 3) + 0]; // convert to RGB888 pixels @@ -774,7 +815,8 @@ static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) { // // GIFMakePels // -static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { +static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) +{ int iPixCount; unsigned short *giftabs; unsigned char *buf, *s, *pEnd, *gifpels; @@ -785,8 +827,10 @@ static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { buf = pPage->ucLineBuf + (pPage->iWidth - pPage->iXCount); giftabs = pPage->usGIFTable; gifpels = &pPage->ucGIFPixels[PIXEL_LAST]; - while (code < LINK_UNUSED) { - if (s == pPage->ucFileBuf) { /* Houston, we have a problem */ + while (code < LINK_UNUSED) + { + if (s == pPage->ucFileBuf) /* Houston, we have a problem */ + { return; /* Exit with error */ } *(--s) = gifpels[code]; @@ -794,24 +838,29 @@ static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { } iPixCount = (int)(intptr_t)(pPage->ucFileBuf + FILE_BUF_SIZE - s); - while (iPixCount && pPage->iYCount > 0) { - if (pPage->iXCount > iPixCount) { /* Pixels fit completely on the line */ - // memcpy(buf, s, iPixCount); - // buf += iPixCount; - pEnd = buf + iPixCount; - while (buf < pEnd) { - *buf++ = *s++; - } + while (iPixCount && pPage->iYCount > 0) + { + if (pPage->iXCount > iPixCount) /* Pixels fit completely on the line */ + { + // memcpy(buf, s, iPixCount); + // buf += iPixCount; + pEnd = buf + iPixCount; + while (buf < pEnd) + { + *buf++ = *s++; + } pPage->iXCount -= iPixCount; // iPixCount = 0; - if (ucNeedMore) { + if (ucNeedMore) GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines - } return; - } else { /* Pixels cross into next line */ + } + else /* Pixels cross into next line */ + { GIFDRAW gd; pEnd = buf + pPage->iXCount; - while (buf < pEnd) { + while (buf < pEnd) + { *buf++ = *s++; } iPixCount -= pPage->iXCount; @@ -824,30 +873,31 @@ static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { gd.pPixels = pPage->ucLineBuf; gd.pPalette = (pPage->bUseLocalPalette) ? pPage->pLocalPalette : pPage->pPalette; gd.pPalette24 = (uint8_t *)gd.pPalette; // just cast the pointer for RGB888 - gd.ucIsGlobalPalette = pPage->bUseLocalPalette == 1?0:1; + gd.ucIsGlobalPalette = pPage->bUseLocalPalette==1?0:1; gd.y = pPage->iHeight - pPage->iYCount; // Ugly logic to handle the interlaced line position, but it // saves having to have another set of state variables if (pPage->ucMap & 0x40) { // interlaced? - int height = pPage->iHeight - 1; - if (gd.y > height / 2) { - gd.y = gd.y * 2 - (height | 1); - } else if (gd.y > height / 4) { - gd.y = gd.y * 4 - ((height & ~1) | 2); - } else if (gd.y > height / 8) { - gd.y = gd.y * 8 - ((height & ~3) | 4); - } else { - gd.y = gd.y * 8; - } + int height = pPage->iHeight-1; + if (gd.y > height / 2) + gd.y = gd.y * 2 - (height | 1); + else if (gd.y > height / 4) + gd.y = gd.y * 4 - ((height & ~1) | 2); + else if (gd.y > height / 8) + gd.y = gd.y * 8 - ((height & ~3) | 4); + else + gd.y = gd.y * 8; } - gd.ucDisposalMethod = (pPage->ucGIFBits & 0x1c) >> 2; + gd.ucDisposalMethod = (pPage->ucGIFBits & 0x1c)>>2; gd.ucTransparent = pPage->ucTransparent; gd.ucHasTransparency = pPage->ucGIFBits & 1; gd.ucBackground = pPage->ucBackground; gd.pUser = pPage->pUser; - if (pPage->pFrameBuffer) { // update the frame buffer + if (pPage->pFrameBuffer) // update the frame buffer + { DrawNewPixels(pPage, &gd); - if (pPage->ucDrawType == GIF_DRAW_COOKED) { + if (pPage->ucDrawType == GIF_DRAW_COOKED) + { ConvertNewPixels(pPage, &gd); // prepare for output gd.pPixels = &pPage->pFrameBuffer[pPage->iCanvasWidth * pPage->iCanvasHeight]; } @@ -855,35 +905,34 @@ static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) { (*pPage->pfnDraw)(&gd); // callback to handle this line pPage->iYCount--; buf = pPage->ucLineBuf; - if ((pPage->iYCount & 3) == 0) { // since we support only small images... + if ((pPage->iYCount & 3) == 0) // since we support only small images... ucNeedMore = 1; - } } } /* while */ - if (ucNeedMore) { + if (ucNeedMore) GIFGetMoreData(pPage); // check if we need to read more LZW data every 4 lines - } return; } /* GIFMakePels() */ // // Macro to extract a variable length code // #define GET_CODE if (bitnum > (REGISTER_WIDTH - codesize)) { pImage->iLZWOff += (bitnum >> 3); \ - bitnum &= 7; ulBits = INTELLONG(&p[pImage->iLZWOff]); } \ - code = (unsigned short)(ulBits >> bitnum); /* Read a 32-bit chunk */ \ - code &= sMask; bitnum += codesize; + bitnum &= 7; ulBits = INTELLONG(&p[pImage->iLZWOff]); } \ + code = (unsigned short) (ulBits >> bitnum); /* Read a 32-bit chunk */ \ + code &= sMask; bitnum += codesize; // // Decode LZW into an image // -static int DecodeLZW(GIFIMAGE *pImage, int iOptions) { +static int DecodeLZW(GIFIMAGE *pImage, int iOptions) +{ int i, bitnum; unsigned short oldcode, codesize, nextcode, nextlim; unsigned short *giftabs, cc, eoi; signed short sMask; unsigned char *gifpels, *p; // int iStripSize; - // unsigned char **index; + //unsigned char **index; uint32_t ulBits; unsigned short code; (void)iOptions; // not used for now @@ -907,7 +956,7 @@ static int DecodeLZW(GIFIMAGE *pImage, int iOptions) { // this part only needs to be initialized once for (i = 0; i < cc; i++) { - gifpels[PIXEL_FIRST + i] = gifpels[PIXEL_LAST + i] = (unsigned short)i; + gifpels[PIXEL_FIRST + i] = gifpels[PIXEL_LAST + i] = (unsigned short) i; giftabs[i] = LINK_END; } init_codetable: @@ -915,79 +964,80 @@ static int DecodeLZW(GIFIMAGE *pImage, int iOptions) { sMask = 0xffff << (pImage->ucCodeStart + 1); sMask = 0xffff - sMask; nextcode = cc + 2; - nextlim = (unsigned short)((1 << codesize)); + nextlim = (unsigned short) ((1 << codesize)); // This part of the table needs to be reset multiple times - memset(&giftabs[cc], LINK_UNUSED, (4096 - cc) * sizeof(short)); + memset(&giftabs[cc], LINK_UNUSED, (4096 - cc)*sizeof(short)); ulBits = INTELLONG(&p[pImage->iLZWOff]); // start by reading 4 bytes of LZW data GET_CODE - if (code == cc) { // we just reset the dictionary, so get another code - GET_CODE + if (code == cc) // we just reset the dictionary, so get another code + { + GET_CODE } oldcode = code; GIFMakePels(pImage, code); // first code is output as the first pixel // Main decode loop - while (code != eoi && pImage->iYCount > 0) { // && y < pImage->iHeight+1) /* Loop through all lines of the image (or strip) */ + while (code != eoi && pImage->iYCount > 0) // && y < pImage->iHeight+1) /* Loop through all lines of the image (or strip) */ + { GET_CODE - if (code == cc) { /* Clear code?, and not first code */ + if (code == cc) /* Clear code?, and not first code */ goto init_codetable; - } - if (code != eoi) { - if (nextcode < nextlim) { // for deferred cc case, don't let it overwrite the last entry (fff) - giftabs[nextcode] = oldcode; - gifpels[PIXEL_FIRST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; - if (giftabs[code] == LINK_UNUSED) { /* Old code */ - gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; - } else { - gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + code]; + if (code != eoi) + { + if (nextcode < nextlim) // for deferred cc case, don't let it overwrite the last entry (fff) + { + giftabs[nextcode] = oldcode; + gifpels[PIXEL_FIRST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; + if (giftabs[code] == LINK_UNUSED) /* Old code */ + gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + oldcode]; + else + gifpels[PIXEL_LAST + nextcode] = gifpels[PIXEL_FIRST + code]; + } + nextcode++; + if (nextcode >= nextlim && codesize < 12) + { + codesize++; + nextlim <<= 1; + sMask = (sMask << 1) | 1; } - } - nextcode++; - if (nextcode >= nextlim && codesize < 12) { - codesize++; - nextlim <<= 1; - sMask = (sMask << 1) | 1; - } GIFMakePels(pImage, code); oldcode = code; } } /* while not end of LZW code stream */ return 0; -// gif_forced_error: +//gif_forced_error: // free(pImage->pPixels); // pImage->pPixels = NULL; // return -1; } /* DecodeLZW() */ -void GIF_setDrawCallback(GIFIMAGE *pGIF, GIF_DRAW_CALLBACK *pfnDraw) { - pGIF->pfnDraw = pfnDraw; +void GIF_setDrawCallback(GIFIMAGE *pGIF, GIF_DRAW_CALLBACK *pfnDraw) +{ + pGIF->pfnDraw = pfnDraw; } /* GIF_setDrawCallback() */ // // Scale 2 scanlines down by 50% with pixel averaging // writes new values over previous line // expects RGB565 little endian pixels as input // -void GIF_scaleHalf(uint16_t *pCurrent, uint16_t *pPrev, int iWidth, int bBigEndian) { - int x; - uint16_t *d = pPrev; - uint32_t gSum, rbSum, pix0,pix1,pix2,pix3; - const uint32_t RBMask = 0xf81f, GMask = 0x7e0; +void GIF_scaleHalf(uint16_t *pCurrent, uint16_t *pPrev, int iWidth, int bBigEndian) +{ +int x; +uint16_t *d = pPrev; +uint32_t gSum, rbSum, pix0,pix1,pix2,pix3; +const uint32_t RBMask = 0xf81f, GMask = 0x7e0; - for (x = 0; x < iWidth; x += 2) - { - pix0 = pCurrent[0]; - pix1 = pCurrent[1]; - pix2 = pPrev[0]; - pix3 = pPrev[1]; - pCurrent += 2; - pPrev += 2; - gSum = (pix0 & GMask) + (pix1 & GMask) + (pix2 & GMask) + (pix3 & GMask); - gSum = ((gSum + 0x40) >> 2) & GMask; // for rounding towards 1 - rbSum = (pix0 & RBMask) + (pix1 & RBMask) + (pix2 & RBMask) + (pix3 & RBMask); - rbSum = ((rbSum + 0x1002) >> 2) & RBMask; - if (bBigEndian) { - *d++ = __builtin_bswap16((uint16_t)(gSum + rbSum)); - } else { - *d++ = (uint16_t)(gSum + rbSum); // store finished pixel - } - } // for x + for (x=0; x> 2) & GMask; // for rounding towards 1 + rbSum = (pix0 & RBMask) + (pix1 & RBMask) + (pix2 & RBMask) + (pix3 & RBMask); + rbSum = ((rbSum + 0x1002) >> 2) & RBMask; + if (bBigEndian) + *d++ = __builtin_bswap16((uint16_t)(gSum + rbSum)); + else + *d++ = (uint16_t)(gSum + rbSum); // store finished pixel + } // for x } /* GIF_scaleHalf() */ diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 6c8a513d8f300..5956b3e965992 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -580,7 +580,6 @@ SRC_SHARED_MODULE_ALL = \ displayio/I2CDisplay.c \ displayio/OnDiskBitmap.c \ displayio/OnDiskGif.c \ - displayio/gif.c \ displayio/Palette.c \ displayio/Shape.c \ displayio/TileGrid.c \ @@ -699,10 +698,10 @@ SRC_MOD += $(addprefix lib/protomatter/src/, \ $(BUILD)/lib/protomatter/src/core.o: CFLAGS += -include "shared-module/rgbmatrix/allocator.h" -DCIRCUITPY -Wno-missing-braces -Wno-missing-prototypes endif -#SRC_MOD += $(addprefix lib/AnimatedGIF/, \ - #gif.c \ -#) -#$(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -Wno-missing-braces -Wno-missing-prototypes +SRC_MOD += $(addprefix lib/AnimatedGIF/, \ + gif.c \ +) +$(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -DCIRCUITPY ifeq ($(CIRCUITPY_ZLIB),1) SRC_MOD += $(addprefix lib/uzlib/, \ diff --git a/shared-bindings/displayio/gif.c b/shared-bindings/displayio/gif.c deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/shared-module/displayio/OnDiskGif.h b/shared-module/displayio/OnDiskGif.h index 2184f78dbeacc..5089e52b42970 100644 --- a/shared-module/displayio/OnDiskGif.h +++ b/shared-module/displayio/OnDiskGif.h @@ -32,8 +32,7 @@ #include "py/obj.h" -// #include "lib/AnimatedGIF/AnimatedGIF_circuitpy.h" -#include "AnimatedGIF_circuitpy.h" +#include "lib/AnimatedGIF/AnimatedGIF_circuitpy.h" #include "Bitmap.h" #include "extmod/vfs_fat.h" From 29e91e0d4f6da2f5984660d0318d6d9dfbe1d160 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 12 Feb 2023 09:11:12 -0600 Subject: [PATCH 06/16] Fix for changing frame size and disposal method issues --- shared-module/displayio/OnDiskGif.c | 41 +++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/shared-module/displayio/OnDiskGif.c b/shared-module/displayio/OnDiskGif.c index 22a97020f075a..1ef7fbed96c18 100644 --- a/shared-module/displayio/OnDiskGif.c +++ b/shared-module/displayio/OnDiskGif.c @@ -79,7 +79,16 @@ static void GIFDraw(GIFDRAW *pDraw) { uint8_t *s; uint16_t *d; - int32_t row_start = pDraw->y * bitmap->stride; + int iWidth = pDraw->iWidth; + if (iWidth + pDraw->iX > bitmap->width) { + iWidth = bitmap->width - pDraw->iX; + } + + if (pDraw->iY + pDraw->y >= bitmap->height || pDraw->iX >= bitmap->width || iWidth < 1) { + return; + } + + int32_t row_start = (pDraw->y + pDraw->iY) * bitmap->stride; uint32_t *row = bitmap->data + row_start; s = pDraw->pPixels; d = (uint16_t *)row; @@ -88,20 +97,30 @@ static void GIFDraw(GIFDRAW *pDraw) { pPal = (uint16_t *)pDraw->pPalette; if (pDraw->ucDisposalMethod == 2) { // restore to background color - memset(d, pDraw->ucBackground, pDraw->iWidth); + // Not supported currently. Need to reset the area the previous frame occupied + // to the background color before the previous frame was drawn + // See: https://github.com/bitbank2/AnimatedGIF/issues/3 + + // To workaround clear the gif.bitmap object yourself as required. } - // We always check for transpancy even if the gif does not have it - // as we also convert the color to the palette here - // Could separate it but would not sure it would be much a speed up uint8_t c, ucTransparent = pDraw->ucTransparent; - for (int x = 0; x < pDraw->iWidth; x++) - { - c = *s++; - if (c != ucTransparent) { - *d = pPal[c]; + d += pDraw->iX; + if (pDraw->ucHasTransparency == 1) { + for (int x = 0; x < iWidth; x++) + { + c = *s++; + if (c != ucTransparent) { + *d = pPal[c]; + } + d++; + } + } else { + for (int x = 0; x < iWidth; x++) + { + c = *s++; + *d++ = pPal[c]; } - d++; } } From 953c98917742340e9a0aa765744f93fcefd649cb Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 12 Feb 2023 09:39:08 -0600 Subject: [PATCH 07/16] Fixign alignment issue on ESP and SAMD boards --- lib/AnimatedGIF/AnimatedGIF_circuitpy.h | 3 ++- lib/AnimatedGIF/gif.c | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/AnimatedGIF/AnimatedGIF_circuitpy.h b/lib/AnimatedGIF/AnimatedGIF_circuitpy.h index e401935a95040..c1f359194f5c0 100644 --- a/lib/AnimatedGIF/AnimatedGIF_circuitpy.h +++ b/lib/AnimatedGIF/AnimatedGIF_circuitpy.h @@ -137,7 +137,8 @@ typedef struct gif_image_tag GIF_CLOSE_CALLBACK *pfnClose; GIFFILE GIFFile; void *pUser; - unsigned char *pFrameBuffer; + //unsigned char *pFrameBuffer; + unsigned int *pFrameBuffer; unsigned char *pPixels, *pOldPixels; unsigned char ucLineBuf[MAX_WIDTH]; // current line unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack diff --git a/lib/AnimatedGIF/gif.c b/lib/AnimatedGIF/gif.c index 00a013756226d..0822b608c183a 100644 --- a/lib/AnimatedGIF/gif.c +++ b/lib/AnimatedGIF/gif.c @@ -753,7 +753,7 @@ static void DrawNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) int x, iPitch = pPage->iCanvasWidth; s = pDraw->pPixels; - d = &pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer + d = (uint8_t*)&pPage->pFrameBuffer[pDraw->iX + (pDraw->y + pDraw->iY) * iPitch]; // dest pointer in our complete canvas buffer if (pDraw->ucDisposalMethod == 2) // restore to background color { memset(d, pDraw->ucBackground, pDraw->iWidth); @@ -785,13 +785,13 @@ static void ConvertNewPixels(GIFIMAGE *pPage, GIFDRAW *pDraw) uint8_t *d, *s; int x; - s = &pPage->pFrameBuffer[(pPage->iCanvasWidth * (pDraw->iY + pDraw->y)) + pDraw->iX]; - d = &pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; // point past bottom of frame buffer + s = (uint8_t*)&pPage->pFrameBuffer[(pPage->iCanvasWidth * (pDraw->iY + pDraw->y)) + pDraw->iX]; + d = (uint8_t*)&pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; // point past bottom of frame buffer if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { uint16_t *pPal, *pu16; pPal = (uint16_t *)pDraw->pPalette; - pu16 = (uint16_t *)d; + pu16 = (uint16_t *)&pPage->pFrameBuffer[pPage->iCanvasHeight * pPage->iCanvasWidth]; for (x=0; xiWidth; x++) { *pu16++ = pPal[*s++]; // convert to RGB565 pixels @@ -899,7 +899,7 @@ static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) if (pPage->ucDrawType == GIF_DRAW_COOKED) { ConvertNewPixels(pPage, &gd); // prepare for output - gd.pPixels = &pPage->pFrameBuffer[pPage->iCanvasWidth * pPage->iCanvasHeight]; + gd.pPixels = (uint8_t*)&pPage->pFrameBuffer[pPage->iCanvasWidth * pPage->iCanvasHeight]; } } (*pPage->pfnDraw)(&gd); // callback to handle this line From 0c95e6a08ece6542c97ed6717bd3a671298de3dc Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 12 Feb 2023 11:17:34 -0600 Subject: [PATCH 08/16] Moving to gifio module --- py/circuitpy_defns.mk | 4 +- py/circuitpy_mpconfig.mk | 3 +- shared-bindings/displayio/OnDiskGif.c | 210 ------------------ shared-bindings/displayio/__init__.c | 2 - shared-bindings/gifio/OnDiskGif.c | 210 ++++++++++++++++++ .../{displayio => gifio}/OnDiskGif.h | 26 +-- shared-bindings/gifio/__init__.c | 2 + .../{displayio => gifio}/OnDiskGif.c | 24 +- .../{displayio => gifio}/OnDiskGif.h | 4 +- 9 files changed, 242 insertions(+), 243 deletions(-) delete mode 100644 shared-bindings/displayio/OnDiskGif.c create mode 100644 shared-bindings/gifio/OnDiskGif.c rename shared-bindings/{displayio => gifio}/OnDiskGif.h (59%) rename shared-module/{displayio => gifio}/OnDiskGif.c (85%) rename shared-module/{displayio => gifio}/OnDiskGif.h (96%) diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 5956b3e965992..df5f1488ea7b0 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -579,7 +579,6 @@ SRC_SHARED_MODULE_ALL = \ displayio/Group.c \ displayio/I2CDisplay.c \ displayio/OnDiskBitmap.c \ - displayio/OnDiskGif.c \ displayio/Palette.c \ displayio/Shape.c \ displayio/TileGrid.c \ @@ -593,6 +592,7 @@ SRC_SHARED_MODULE_ALL = \ getpass/__init__.c \ gifio/__init__.c \ gifio/GifWriter.c \ + gifio/OnDiskGif.c \ imagecapture/ParallelImageCapture.c \ ipaddress/IPv4Address.c \ ipaddress/__init__.c \ @@ -698,10 +698,12 @@ SRC_MOD += $(addprefix lib/protomatter/src/, \ $(BUILD)/lib/protomatter/src/core.o: CFLAGS += -include "shared-module/rgbmatrix/allocator.h" -DCIRCUITPY -Wno-missing-braces -Wno-missing-prototypes endif +ifeq ($(CIRCUITPY_GIFIO),1) SRC_MOD += $(addprefix lib/AnimatedGIF/, \ gif.c \ ) $(BUILD)/lib/AnimatedGIF/gif.o: CFLAGS += -DCIRCUITPY +endif ifeq ($(CIRCUITPY_ZLIB),1) SRC_MOD += $(addprefix lib/uzlib/, \ diff --git a/py/circuitpy_mpconfig.mk b/py/circuitpy_mpconfig.mk index 3c1c173a51554..c28e686b78045 100644 --- a/py/circuitpy_mpconfig.mk +++ b/py/circuitpy_mpconfig.mk @@ -249,7 +249,8 @@ CIRCUITPY_GETPASS ?= $(CIRCUITPY_FULL_BUILD) CFLAGS += -DCIRCUITPY_GETPASS=$(CIRCUITPY_GETPASS) ifeq ($(CIRCUITPY_DISPLAYIO),1) -CIRCUITPY_GIFIO ?= $(CIRCUITPY_CAMERA) +#CIRCUITPY_GIFIO ?= $(CIRCUITPY_CAMERA) +CIRCUITPY_GIFIO ?= 1 else CIRCUITPY_GIFIO ?= 0 endif diff --git a/shared-bindings/displayio/OnDiskGif.c b/shared-bindings/displayio/OnDiskGif.c deleted file mode 100644 index 51651a93e7ab7..0000000000000 --- a/shared-bindings/displayio/OnDiskGif.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * This file is part of the Micro Python project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include "shared-bindings/displayio/OnDiskGif.h" - -#include - -#include "py/runtime.h" -#include "py/objproperty.h" -#include "supervisor/shared/translate/translate.h" -#include "shared-bindings/displayio/OnDiskGif.h" - -//| class OnDiskGif: -//| """Loads values straight from disk. This minimizes memory use but can lead to -//| much slower pixel load times -//| -//| .. code-block:: Python -//| -//| import board -//| import displayio -//| import time -//| import pulseio -//| -//| splash = displayio.Group() -//| board.DISPLAY.show(splash) -//| -//| odg = displayio.OnDiskBitmap('/sample.gif') -//| odg.play_frame() # Load the first frame -//| face = displayio.TileGrid(odg, pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565)) -//| splash.append(face) -//| board.DISPLAY.refresh() -//| -//| # Wait forever -//| while True: -//| gif.play_frame() -//| time.sleep(0.1)""" -//| -//| def __init__(self, file: str) -> None: -//| """Create an OnDiskGif object with the given file. -//| -//| :param file file: The name of the GIF file. -//| -//| """ -//| ... -STATIC mp_obj_t displayio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { - mp_arg_check_num(n_args, n_kw, 1, 1, false); - mp_obj_t arg = all_args[0]; - - if (mp_obj_is_str(arg)) { - arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb)); - } - - if (!mp_obj_is_type(arg, &mp_type_fileio)) { - mp_raise_TypeError(translate("file must be a file opened in byte mode")); - } - - displayio_ondiskgif_t *self = m_new_obj(displayio_ondiskgif_t); - self->base.type = &displayio_ondiskgif_type; - common_hal_displayio_ondiskgif_construct(self, MP_OBJ_TO_PTR(arg)); - - return MP_OBJ_FROM_PTR(self); -} - -//| width: int -//| """Width of the gif. (read only)""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_width(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_width(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_width_obj, displayio_ondiskgif_obj_get_width); - -MP_PROPERTY_GETTER(displayio_ondiskgif_width_obj, - (mp_obj_t)&displayio_ondiskgif_get_width_obj); - -//| height: int -//| """Height of the gif. (read only)""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_height(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_height(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_height_obj, displayio_ondiskgif_obj_get_height); - -MP_PROPERTY_GETTER(displayio_ondiskgif_height_obj, - (mp_obj_t)&displayio_ondiskgif_get_height_obj); - -//| bitmap: Bitmap -//| """The bitmap used to hold the current frame.""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return common_hal_displayio_ondiskgif_get_bitmap(self); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_bitmap_obj, displayio_ondiskgif_obj_get_bitmap); - -MP_PROPERTY_GETTER(displayio_ondiskgif_bitmap_obj, - (mp_obj_t)&displayio_ondiskgif_get_bitmap_obj); - -//| play_frame: int -//| """Play next frame. Returns expected delay until the next frame.""" -STATIC mp_obj_t displayio_ondiskgif_obj_play_frame(size_t n_args, const mp_obj_t *args) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(args[0]); - bool setDirty = mp_const_true; - - if (n_args == 1) { - setDirty = mp_obj_is_true(args[1]); - } - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_play_frame(self, setDirty)); -} - -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(displayio_ondiskgif_play_frame_obj, 1, 2, displayio_ondiskgif_obj_play_frame); - -//| duration: int -//| """Returns the total duration of the GIF in milliseconds. (read only)""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_duration(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_duration(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_duration_obj, displayio_ondiskgif_obj_get_duration); - -MP_PROPERTY_GETTER(displayio_ondiskgif_duration_obj, - (mp_obj_t)&displayio_ondiskgif_get_duration_obj); - -//| frame_count: int -//| """Returns the number of frames in the GIF. (read only)""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_frame_count(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_frame_count(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_frame_count_obj, displayio_ondiskgif_obj_get_frame_count); - -MP_PROPERTY_GETTER(displayio_ondiskgif_frame_count_obj, - (mp_obj_t)&displayio_ondiskgif_get_frame_count_obj); - -//| min_delay: int -//| """The minimum delay found between frames. (read only)""" -STATIC mp_obj_t displayio_ondiskgif_obj_get_min_delay(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_min_delay(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_min_delay_obj, displayio_ondiskgif_obj_get_min_delay); - -MP_PROPERTY_GETTER(displayio_ondiskgif_min_delay_obj, - (mp_obj_t)&displayio_ondiskgif_get_min_delay_obj); - -//| max_delay: int -//| """The maximum delay found between frames. (read only)""" -//| -STATIC mp_obj_t displayio_ondiskgif_obj_get_max_delay(mp_obj_t self_in) { - displayio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - - return MP_OBJ_NEW_SMALL_INT(common_hal_displayio_ondiskgif_get_max_delay(self)); -} - -MP_DEFINE_CONST_FUN_OBJ_1(displayio_ondiskgif_get_max_delay_obj, displayio_ondiskgif_obj_get_max_delay); - -MP_PROPERTY_GETTER(displayio_ondiskgif_max_delay_obj, - (mp_obj_t)&displayio_ondiskgif_get_max_delay_obj); - -STATIC const mp_rom_map_elem_t displayio_ondiskgif_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&displayio_ondiskgif_height_obj) }, - { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&displayio_ondiskgif_bitmap_obj) }, - { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&displayio_ondiskgif_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_play_frame), MP_ROM_PTR(&displayio_ondiskgif_play_frame_obj) }, - { MP_ROM_QSTR(MP_QSTR_duration), MP_ROM_PTR(&displayio_ondiskgif_duration_obj) }, - { MP_ROM_QSTR(MP_QSTR_frame_count), MP_ROM_PTR(&displayio_ondiskgif_frame_count_obj) }, - { MP_ROM_QSTR(MP_QSTR_min_delay), MP_ROM_PTR(&displayio_ondiskgif_min_delay_obj) }, - { MP_ROM_QSTR(MP_QSTR_max_delay), MP_ROM_PTR(&displayio_ondiskgif_max_delay_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(displayio_ondiskgif_locals_dict, displayio_ondiskgif_locals_dict_table); - -const mp_obj_type_t displayio_ondiskgif_type = { - { &mp_type_type }, - .name = MP_QSTR_OnDiskGif, - .make_new = displayio_ondiskgif_make_new, - .locals_dict = (mp_obj_dict_t *)&displayio_ondiskgif_locals_dict, -}; diff --git a/shared-bindings/displayio/__init__.c b/shared-bindings/displayio/__init__.c index 4f2905e2e2576..3fcd1d082b2fa 100644 --- a/shared-bindings/displayio/__init__.c +++ b/shared-bindings/displayio/__init__.c @@ -39,7 +39,6 @@ #include "shared-bindings/displayio/Group.h" #include "shared-bindings/displayio/I2CDisplay.h" #include "shared-bindings/displayio/OnDiskBitmap.h" -#include "shared-bindings/displayio/OnDiskGif.h" #include "shared-bindings/displayio/Palette.h" #if CIRCUITPY_PARALLELDISPLAY #include "shared-bindings/paralleldisplay/ParallelBus.h" @@ -86,7 +85,6 @@ STATIC const mp_rom_map_elem_t displayio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_EPaperDisplay), MP_ROM_PTR(&displayio_epaperdisplay_type) }, { MP_ROM_QSTR(MP_QSTR_Group), MP_ROM_PTR(&displayio_group_type) }, { MP_ROM_QSTR(MP_QSTR_OnDiskBitmap), MP_ROM_PTR(&displayio_ondiskbitmap_type) }, - { MP_ROM_QSTR(MP_QSTR_OnDiskGif), MP_ROM_PTR(&displayio_ondiskgif_type) }, { MP_ROM_QSTR(MP_QSTR_Palette), MP_ROM_PTR(&displayio_palette_type) }, { MP_ROM_QSTR(MP_QSTR_Shape), MP_ROM_PTR(&displayio_shape_type) }, { MP_ROM_QSTR(MP_QSTR_TileGrid), MP_ROM_PTR(&displayio_tilegrid_type) }, diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c new file mode 100644 index 0000000000000..2030d50b71867 --- /dev/null +++ b/shared-bindings/gifio/OnDiskGif.c @@ -0,0 +1,210 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "shared-bindings/gifio/OnDiskGif.h" + +#include + +#include "py/runtime.h" +#include "py/objproperty.h" +#include "supervisor/shared/translate/translate.h" +#include "shared-bindings/gifio/OnDiskGif.h" + +//| class OnDiskGif: +//| """Loads values straight from disk. This minimizes memory use but can lead to +//| much slower pixel load times +//| +//| .. code-block:: Python +//| +//| import board +//| import gifio +//| import time +//| import pulseio +//| +//| splash = gifio.Group() +//| board.DISPLAY.show(splash) +//| +//| odg = gifio.OnDiskBitmap('/sample.gif') +//| odg.play_frame() # Load the first frame +//| face = gifio.TileGrid(odg, pixel_shader=gifio.ColorConverter(input_colorspace=gifio.Colorspace.RGB565)) +//| splash.append(face) +//| board.DISPLAY.refresh() +//| +//| # Wait forever +//| while True: +//| gif.play_frame() +//| time.sleep(0.1)""" +//| +//| def __init__(self, file: str) -> None: +//| """Create an OnDiskGif object with the given file. +//| +//| :param file file: The name of the GIF file. +//| +//| """ +//| ... +STATIC mp_obj_t gifio_ondiskgif_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + mp_arg_check_num(n_args, n_kw, 1, 1, false); + mp_obj_t arg = all_args[0]; + + if (mp_obj_is_str(arg)) { + arg = mp_call_function_2(MP_OBJ_FROM_PTR(&mp_builtin_open_obj), arg, MP_ROM_QSTR(MP_QSTR_rb)); + } + + if (!mp_obj_is_type(arg, &mp_type_fileio)) { + mp_raise_TypeError(translate("file must be a file opened in byte mode")); + } + + gifio_ondiskgif_t *self = m_new_obj(gifio_ondiskgif_t); + self->base.type = &gifio_ondiskgif_type; + common_hal_gifio_ondiskgif_construct(self, MP_OBJ_TO_PTR(arg)); + + return MP_OBJ_FROM_PTR(self); +} + +//| width: int +//| """Width of the gif. (read only)""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_width(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_width(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_width_obj, gifio_ondiskgif_obj_get_width); + +MP_PROPERTY_GETTER(gifio_ondiskgif_width_obj, + (mp_obj_t)&gifio_ondiskgif_get_width_obj); + +//| height: int +//| """Height of the gif. (read only)""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_height(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_height(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_height_obj, gifio_ondiskgif_obj_get_height); + +MP_PROPERTY_GETTER(gifio_ondiskgif_height_obj, + (mp_obj_t)&gifio_ondiskgif_get_height_obj); + +//| bitmap: Bitmap +//| """The bitmap used to hold the current frame.""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + return common_hal_gifio_ondiskgif_get_bitmap(self); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_bitmap_obj, gifio_ondiskgif_obj_get_bitmap); + +MP_PROPERTY_GETTER(gifio_ondiskgif_bitmap_obj, + (mp_obj_t)&gifio_ondiskgif_get_bitmap_obj); + +//| play_frame: int +//| """Play next frame. Returns expected delay until the next frame.""" +STATIC mp_obj_t gifio_ondiskgif_obj_play_frame(size_t n_args, const mp_obj_t *args) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(args[0]); + bool setDirty = mp_const_true; + + if (n_args == 1) { + setDirty = mp_obj_is_true(args[1]); + } + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_play_frame(self, setDirty)); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gifio_ondiskgif_play_frame_obj, 1, 2, gifio_ondiskgif_obj_play_frame); + +//| duration: int +//| """Returns the total duration of the GIF in milliseconds. (read only)""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_duration(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_duration(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_duration_obj, gifio_ondiskgif_obj_get_duration); + +MP_PROPERTY_GETTER(gifio_ondiskgif_duration_obj, + (mp_obj_t)&gifio_ondiskgif_get_duration_obj); + +//| frame_count: int +//| """Returns the number of frames in the GIF. (read only)""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_frame_count(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_frame_count(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_frame_count_obj, gifio_ondiskgif_obj_get_frame_count); + +MP_PROPERTY_GETTER(gifio_ondiskgif_frame_count_obj, + (mp_obj_t)&gifio_ondiskgif_get_frame_count_obj); + +//| min_delay: int +//| """The minimum delay found between frames. (read only)""" +STATIC mp_obj_t gifio_ondiskgif_obj_get_min_delay(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_min_delay(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_min_delay_obj, gifio_ondiskgif_obj_get_min_delay); + +MP_PROPERTY_GETTER(gifio_ondiskgif_min_delay_obj, + (mp_obj_t)&gifio_ondiskgif_get_min_delay_obj); + +//| max_delay: int +//| """The maximum delay found between frames. (read only)""" +//| +STATIC mp_obj_t gifio_ondiskgif_obj_get_max_delay(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); + + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_max_delay(self)); +} + +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_max_delay_obj, gifio_ondiskgif_obj_get_max_delay); + +MP_PROPERTY_GETTER(gifio_ondiskgif_max_delay_obj, + (mp_obj_t)&gifio_ondiskgif_get_max_delay_obj); + +STATIC const mp_rom_map_elem_t gifio_ondiskgif_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&gifio_ondiskgif_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&gifio_ondiskgif_bitmap_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&gifio_ondiskgif_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_play_frame), MP_ROM_PTR(&gifio_ondiskgif_play_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_duration), MP_ROM_PTR(&gifio_ondiskgif_duration_obj) }, + { MP_ROM_QSTR(MP_QSTR_frame_count), MP_ROM_PTR(&gifio_ondiskgif_frame_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_min_delay), MP_ROM_PTR(&gifio_ondiskgif_min_delay_obj) }, + { MP_ROM_QSTR(MP_QSTR_max_delay), MP_ROM_PTR(&gifio_ondiskgif_max_delay_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(gifio_ondiskgif_locals_dict, gifio_ondiskgif_locals_dict_table); + +const mp_obj_type_t gifio_ondiskgif_type = { + { &mp_type_type }, + .name = MP_QSTR_OnDiskGif, + .make_new = gifio_ondiskgif_make_new, + .locals_dict = (mp_obj_dict_t *)&gifio_ondiskgif_locals_dict, +}; diff --git a/shared-bindings/displayio/OnDiskGif.h b/shared-bindings/gifio/OnDiskGif.h similarity index 59% rename from shared-bindings/displayio/OnDiskGif.h rename to shared-bindings/gifio/OnDiskGif.h index 6e5041c012c8a..0399724379596 100644 --- a/shared-bindings/displayio/OnDiskGif.h +++ b/shared-bindings/gifio/OnDiskGif.h @@ -27,23 +27,23 @@ #ifndef MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H #define MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H -#include "shared-module/displayio/OnDiskGif.h" +#include "shared-module/gifio/OnDiskGif.h" #include "extmod/vfs_fat.h" -extern const mp_obj_type_t displayio_ondiskgif_type; +extern const mp_obj_type_t gifio_ondiskgif_type; -void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_file_obj_t *file); +void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, pyb_file_obj_t *file); -uint32_t common_hal_displayio_ondiskgif_get_pixel(displayio_ondiskgif_t *bitmap, +uint32_t common_hal_gifio_ondiskgif_get_pixel(gifio_ondiskgif_t *bitmap, int16_t x, int16_t y); -uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self); -mp_obj_t common_hal_displayio_ondiskgif_get_pixel_shader(displayio_ondiskgif_t *self); -mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self); -uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self); -uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self, bool setDirty); -int32_t common_hal_displayio_ondiskgif_get_duration(displayio_ondiskgif_t *self); -int32_t common_hal_displayio_ondiskgif_get_frame_count(displayio_ondiskgif_t *self); -int32_t common_hal_displayio_ondiskgif_get_min_delay(displayio_ondiskgif_t *self); -int32_t common_hal_displayio_ondiskgif_get_max_delay(displayio_ondiskgif_t *self); +uint16_t common_hal_gifio_ondiskgif_get_height(gifio_ondiskgif_t *self); +mp_obj_t common_hal_gifio_ondiskgif_get_pixel_shader(gifio_ondiskgif_t *self); +mp_obj_t common_hal_gifio_ondiskgif_get_bitmap(gifio_ondiskgif_t *self); +uint16_t common_hal_gifio_ondiskgif_get_width(gifio_ondiskgif_t *self); +uint8_t common_hal_gifio_ondiskgif_play_frame(gifio_ondiskgif_t *self, bool setDirty); +int32_t common_hal_gifio_ondiskgif_get_duration(gifio_ondiskgif_t *self); +int32_t common_hal_gifio_ondiskgif_get_frame_count(gifio_ondiskgif_t *self); +int32_t common_hal_gifio_ondiskgif_get_min_delay(gifio_ondiskgif_t *self); +int32_t common_hal_gifio_ondiskgif_get_max_delay(gifio_ondiskgif_t *self); #endif // MICROPY_INCLUDED_SHARED_BINDINGS_DISPLAYIO_ONDISKGIF_H diff --git a/shared-bindings/gifio/__init__.c b/shared-bindings/gifio/__init__.c index 6bd5765966310..bfc87f0d99517 100644 --- a/shared-bindings/gifio/__init__.c +++ b/shared-bindings/gifio/__init__.c @@ -27,6 +27,7 @@ #include "py/runtime.h" #include "py/mphal.h" #include "shared-bindings/gifio/GifWriter.h" +#include "shared-bindings/gifio/OnDiskGif.h" #include "shared-bindings/util.h" //| """Access GIF-format images @@ -34,6 +35,7 @@ STATIC const mp_rom_map_elem_t gifio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_gifio) }, { MP_OBJ_NEW_QSTR(MP_QSTR_GifWriter), MP_ROM_PTR(&gifio_gifwriter_type)}, + { MP_ROM_QSTR(MP_QSTR_OnDiskGif), MP_ROM_PTR(&gifio_ondiskgif_type) }, }; STATIC MP_DEFINE_CONST_DICT(gifio_module_globals, gifio_module_globals_table); diff --git a/shared-module/displayio/OnDiskGif.c b/shared-module/gifio/OnDiskGif.c similarity index 85% rename from shared-module/displayio/OnDiskGif.c rename to shared-module/gifio/OnDiskGif.c index 1ef7fbed96c18..faa20d5586d20 100644 --- a/shared-module/displayio/OnDiskGif.c +++ b/shared-module/gifio/OnDiskGif.c @@ -24,12 +24,8 @@ * THE SOFTWARE. */ -#include "shared-bindings/displayio/OnDiskGif.h" -#include "shared-bindings/displayio/ColorConverter.h" -#include "shared-bindings/displayio/Palette.h" +#include "shared-bindings/gifio/OnDiskGif.h" #include "shared-bindings/displayio/Bitmap.h" -#include "shared-module/displayio/ColorConverter.h" -#include "shared-module/displayio/Palette.h" #include @@ -124,7 +120,7 @@ static void GIFDraw(GIFDRAW *pDraw) { } } -void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_file_obj_t *file) { +void common_hal_gifio_ondiskgif_construct(gifio_ondiskgif_t *self, pyb_file_obj_t *file) { // mp_printf(&mp_plat_print, "Begin OnDiskGif\n"); self->file = file; @@ -161,35 +157,35 @@ void common_hal_displayio_ondiskgif_construct(displayio_ondiskgif_t *self, pyb_f // mp_printf(&mp_plat_print, "GIF_init returned %d %x\n", result, bitmap->data); } -uint16_t common_hal_displayio_ondiskgif_get_height(displayio_ondiskgif_t *self) { +uint16_t common_hal_gifio_ondiskgif_get_height(gifio_ondiskgif_t *self) { return (uint16_t)self->gif.iCanvasHeight; } -uint16_t common_hal_displayio_ondiskgif_get_width(displayio_ondiskgif_t *self) { +uint16_t common_hal_gifio_ondiskgif_get_width(gifio_ondiskgif_t *self) { return (uint16_t)self->gif.iCanvasWidth; } -mp_obj_t common_hal_displayio_ondiskgif_get_bitmap(displayio_ondiskgif_t *self) { +mp_obj_t common_hal_gifio_ondiskgif_get_bitmap(gifio_ondiskgif_t *self) { return MP_OBJ_FROM_PTR(self->bitmap); } -int32_t common_hal_displayio_ondiskgif_get_duration(displayio_ondiskgif_t *self) { +int32_t common_hal_gifio_ondiskgif_get_duration(gifio_ondiskgif_t *self) { return self->duration; } -int32_t common_hal_displayio_ondiskgif_get_frame_count(displayio_ondiskgif_t *self) { +int32_t common_hal_gifio_ondiskgif_get_frame_count(gifio_ondiskgif_t *self) { return self->frame_count; } -int32_t common_hal_displayio_ondiskgif_get_min_delay(displayio_ondiskgif_t *self) { +int32_t common_hal_gifio_ondiskgif_get_min_delay(gifio_ondiskgif_t *self) { return self->min_delay; } -int32_t common_hal_displayio_ondiskgif_get_max_delay(displayio_ondiskgif_t *self) { +int32_t common_hal_gifio_ondiskgif_get_max_delay(gifio_ondiskgif_t *self) { return self->max_delay; } -uint8_t common_hal_displayio_ondiskgif_play_frame(displayio_ondiskgif_t *self, bool setDirty) { +uint8_t common_hal_gifio_ondiskgif_play_frame(gifio_ondiskgif_t *self, bool setDirty) { int nextDelay = 0; int result = GIF_playFrame(&self->gif, &nextDelay, self->bitmap); diff --git a/shared-module/displayio/OnDiskGif.h b/shared-module/gifio/OnDiskGif.h similarity index 96% rename from shared-module/displayio/OnDiskGif.h rename to shared-module/gifio/OnDiskGif.h index 5089e52b42970..b142d7893872b 100644 --- a/shared-module/displayio/OnDiskGif.h +++ b/shared-module/gifio/OnDiskGif.h @@ -33,7 +33,7 @@ #include "py/obj.h" #include "lib/AnimatedGIF/AnimatedGIF_circuitpy.h" -#include "Bitmap.h" +#include "shared-module/displayio/Bitmap.h" #include "extmod/vfs_fat.h" @@ -46,6 +46,6 @@ typedef struct { int32_t frame_count; int32_t min_delay; int32_t max_delay; -} displayio_ondiskgif_t; +} gifio_ondiskgif_t; #endif // MICROPY_INCLUDED_SHARED_MODULE_DISPLAYIO_ONDISKGIF_H From cab38ed6a6088526526642a53624aa7e17c146e9 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sun, 12 Feb 2023 11:51:23 -0600 Subject: [PATCH 09/16] Doc fixes and renamed play_frame to next_frame --- shared-bindings/gifio/OnDiskGif.c | 32 ++++++++++++++++--------------- shared-bindings/gifio/OnDiskGif.h | 4 ++-- shared-module/gifio/OnDiskGif.c | 4 ++-- shared-module/gifio/OnDiskGif.h | 2 +- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c index 2030d50b71867..b790d1dc3ab07 100644 --- a/shared-bindings/gifio/OnDiskGif.c +++ b/shared-bindings/gifio/OnDiskGif.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * Copyright (c) 2023 Mark Komus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -34,28 +34,28 @@ #include "shared-bindings/gifio/OnDiskGif.h" //| class OnDiskGif: -//| """Loads values straight from disk. This minimizes memory use but can lead to -//| much slower pixel load times +//| """Loads frames of the GIF straight from disk. This minimizes memory use but can +//| lead to much slower pixel load times //| //| .. code-block:: Python //| //| import board //| import gifio +//| import displayio //| import time -//| import pulseio //| -//| splash = gifio.Group() +//| splash = displayio.Group() //| board.DISPLAY.show(splash) //| -//| odg = gifio.OnDiskBitmap('/sample.gif') -//| odg.play_frame() # Load the first frame -//| face = gifio.TileGrid(odg, pixel_shader=gifio.ColorConverter(input_colorspace=gifio.Colorspace.RGB565)) +//| odg = gifio.OnDiskGif('/sample.gif') +//| odg.next_frame() # Load the first frame +//| face = displayio.TileGrid(odg, pixel_shader=displayio.ColorConverter(input_colorspace=displayio.Colorspace.RGB565)) //| splash.append(face) //| board.DISPLAY.refresh() //| //| # Wait forever //| while True: -//| gif.play_frame() +//| gif.next_frame() //| time.sleep(0.1)""" //| //| def __init__(self, file: str) -> None: @@ -122,9 +122,11 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_bitmap_obj, gifio_ondiskgif_obj_ge MP_PROPERTY_GETTER(gifio_ondiskgif_bitmap_obj, (mp_obj_t)&gifio_ondiskgif_get_bitmap_obj); -//| play_frame: int -//| """Play next frame. Returns expected delay until the next frame.""" -STATIC mp_obj_t gifio_ondiskgif_obj_play_frame(size_t n_args, const mp_obj_t *args) { +//| def next_frame(self, set_dirty: bool = True) -> int: +//| """Loads the next frame. Returns expected delay before the next frame in milliseconds. +//| +//| :param set_dirty: Mark the bitmap as dirty""" +STATIC mp_obj_t gifio_ondiskgif_obj_next_frame(size_t n_args, const mp_obj_t *args) { gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(args[0]); bool setDirty = mp_const_true; @@ -132,10 +134,10 @@ STATIC mp_obj_t gifio_ondiskgif_obj_play_frame(size_t n_args, const mp_obj_t *ar setDirty = mp_obj_is_true(args[1]); } - return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_play_frame(self, setDirty)); + return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_next_frame(self, setDirty)); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gifio_ondiskgif_play_frame_obj, 1, 2, gifio_ondiskgif_obj_play_frame); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gifio_ondiskgif_next_frame_obj, 1, 2, gifio_ondiskgif_obj_next_frame); //| duration: int //| """Returns the total duration of the GIF in milliseconds. (read only)""" @@ -194,7 +196,7 @@ STATIC const mp_rom_map_elem_t gifio_ondiskgif_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&gifio_ondiskgif_height_obj) }, { MP_ROM_QSTR(MP_QSTR_bitmap), MP_ROM_PTR(&gifio_ondiskgif_bitmap_obj) }, { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&gifio_ondiskgif_width_obj) }, - { MP_ROM_QSTR(MP_QSTR_play_frame), MP_ROM_PTR(&gifio_ondiskgif_play_frame_obj) }, + { MP_ROM_QSTR(MP_QSTR_next_frame), MP_ROM_PTR(&gifio_ondiskgif_next_frame_obj) }, { MP_ROM_QSTR(MP_QSTR_duration), MP_ROM_PTR(&gifio_ondiskgif_duration_obj) }, { MP_ROM_QSTR(MP_QSTR_frame_count), MP_ROM_PTR(&gifio_ondiskgif_frame_count_obj) }, { MP_ROM_QSTR(MP_QSTR_min_delay), MP_ROM_PTR(&gifio_ondiskgif_min_delay_obj) }, diff --git a/shared-bindings/gifio/OnDiskGif.h b/shared-bindings/gifio/OnDiskGif.h index 0399724379596..aa84baf32d0f2 100644 --- a/shared-bindings/gifio/OnDiskGif.h +++ b/shared-bindings/gifio/OnDiskGif.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * Copyright (c) 2023 Mark Komus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,7 +41,7 @@ uint16_t common_hal_gifio_ondiskgif_get_height(gifio_ondiskgif_t *self); mp_obj_t common_hal_gifio_ondiskgif_get_pixel_shader(gifio_ondiskgif_t *self); mp_obj_t common_hal_gifio_ondiskgif_get_bitmap(gifio_ondiskgif_t *self); uint16_t common_hal_gifio_ondiskgif_get_width(gifio_ondiskgif_t *self); -uint8_t common_hal_gifio_ondiskgif_play_frame(gifio_ondiskgif_t *self, bool setDirty); +uint8_t common_hal_gifio_ondiskgif_next_frame(gifio_ondiskgif_t *self, bool setDirty); int32_t common_hal_gifio_ondiskgif_get_duration(gifio_ondiskgif_t *self); int32_t common_hal_gifio_ondiskgif_get_frame_count(gifio_ondiskgif_t *self); int32_t common_hal_gifio_ondiskgif_get_min_delay(gifio_ondiskgif_t *self); diff --git a/shared-module/gifio/OnDiskGif.c b/shared-module/gifio/OnDiskGif.c index faa20d5586d20..05de2d59ff3b3 100644 --- a/shared-module/gifio/OnDiskGif.c +++ b/shared-module/gifio/OnDiskGif.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * Copyright (c) 2023 Mark Komus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -185,7 +185,7 @@ int32_t common_hal_gifio_ondiskgif_get_max_delay(gifio_ondiskgif_t *self) { return self->max_delay; } -uint8_t common_hal_gifio_ondiskgif_play_frame(gifio_ondiskgif_t *self, bool setDirty) { +uint8_t common_hal_gifio_ondiskgif_next_frame(gifio_ondiskgif_t *self, bool setDirty) { int nextDelay = 0; int result = GIF_playFrame(&self->gif, &nextDelay, self->bitmap); diff --git a/shared-module/gifio/OnDiskGif.h b/shared-module/gifio/OnDiskGif.h index b142d7893872b..c40781ef1fe8e 100644 --- a/shared-module/gifio/OnDiskGif.h +++ b/shared-module/gifio/OnDiskGif.h @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * Copyright (c) 2023 Mark Komus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From 5c2bd38684c94631b5750ef5c56834e3263a56a6 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Feb 2023 09:40:16 -0600 Subject: [PATCH 10/16] Removed gifio module from tests --- .../unix/variants/coverage/mpconfigvariant.mk | 1 - tests/unix/extra_coverage.py.exp | 22 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ports/unix/variants/coverage/mpconfigvariant.mk b/ports/unix/variants/coverage/mpconfigvariant.mk index e351fc0836a4a..fd12bbd8b5562 100644 --- a/ports/unix/variants/coverage/mpconfigvariant.mk +++ b/ports/unix/variants/coverage/mpconfigvariant.mk @@ -26,7 +26,6 @@ CFLAGS += -DCIRCUITPY_QRIO=1 $(BUILD)/lib/quirc/lib/%.o: CFLAGS += -Wno-shadow -Wno-sign-compare -include shared-module/qrio/quirc_alloc.h SRC_BITMAP := \ - $(patsubst ../../%,%,$(wildcard ../../shared-bindings/gifio/*.c ../../shared-module/gifio/*.c)) \ shared/runtime/context_manager_helpers.c \ displayio_min.c \ shared-bindings/aesio/aes.c \ diff --git a/tests/unix/extra_coverage.py.exp b/tests/unix/extra_coverage.py.exp index 582d90e1bce6a..65f67f072798e 100644 --- a/tests/unix/extra_coverage.py.exp +++ b/tests/unix/extra_coverage.py.exp @@ -33,17 +33,17 @@ builtins micropython _asyncio _thread _uasyncio aesio array binascii bitmaptools btree cexample cmath collections cppexample displayio errno -ffi framebuf gc gifio -hashlib json math qrio -rainbowio re sys termios -traceback ubinascii uctypes uerrno -uheapq uio ujson ulab -ulab.numpy ulab.numpy.fft ulab.numpy.linalg -ulab.scipy ulab.scipy.linalg -ulab.scipy.optimize ulab.scipy.signal -ulab.scipy.special ulab.utils uos -urandom ure uselect ustruct -utime utimeq uzlib zlib +ffi framebuf gc hashlib +json math qrio rainbowio +re sys termios traceback +ubinascii uctypes uerrno uheapq +uio ujson ulab ulab.numpy +ulab.numpy.fft ulab.numpy.linalg ulab.scipy +ulab.scipy.linalg ulab.scipy.optimize +ulab.scipy.signal ulab.scipy.special +ulab.utils uos urandom ure +uselect ustruct utime utimeq +uzlib zlib ime utime utimeq From 88212b066ed931a9108178b5743d38cf6ca8d0b0 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Feb 2023 11:32:25 -0600 Subject: [PATCH 11/16] Doc fix --- shared-bindings/gifio/OnDiskGif.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c index b790d1dc3ab07..3d5bc03f623d6 100644 --- a/shared-bindings/gifio/OnDiskGif.c +++ b/shared-bindings/gifio/OnDiskGif.c @@ -110,7 +110,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_height_obj, gifio_ondiskgif_obj_ge MP_PROPERTY_GETTER(gifio_ondiskgif_height_obj, (mp_obj_t)&gifio_ondiskgif_get_height_obj); -//| bitmap: Bitmap +//| bitmap: displayio.Bitmap //| """The bitmap used to hold the current frame.""" STATIC mp_obj_t gifio_ondiskgif_obj_get_bitmap(mp_obj_t self_in) { gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); From be67cb72add7918de5b4e073569cbbe6c88111eb Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Mon, 13 Feb 2023 17:04:54 -0600 Subject: [PATCH 12/16] Disable for pewpew_m4 for space --- ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk index 32be4805d7418..914a676e94a36 100644 --- a/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk +++ b/ports/atmel-samd/boards/pewpew_m4/mpconfigboard.mk @@ -37,6 +37,7 @@ CIRCUITPY_USB_MIDI = 0 CIRCUITPY_VECTORIO = 0 CIRCUITPY_BUSDEVICE = 0 CIRCUITPY_BITMAPTOOLS = 0 +CIRCUITPY_GIFIO = 0 CIRCUITPY_ANALOGIO = 1 CIRCUITPY_AUDIOIO = 1 From 20eb7d11bd840a9e2142c59ed69ee067d0f1fa33 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Fri, 17 Feb 2023 13:06:56 -0600 Subject: [PATCH 13/16] Changes times to seconds, remove dirty bitmap flag --- shared-bindings/gifio/OnDiskGif.c | 34 +++++++++++++------------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c index 3d5bc03f623d6..32e6153b9a7b5 100644 --- a/shared-bindings/gifio/OnDiskGif.c +++ b/shared-bindings/gifio/OnDiskGif.c @@ -122,29 +122,23 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_bitmap_obj, gifio_ondiskgif_obj_ge MP_PROPERTY_GETTER(gifio_ondiskgif_bitmap_obj, (mp_obj_t)&gifio_ondiskgif_get_bitmap_obj); -//| def next_frame(self, set_dirty: bool = True) -> int: -//| """Loads the next frame. Returns expected delay before the next frame in milliseconds. -//| -//| :param set_dirty: Mark the bitmap as dirty""" -STATIC mp_obj_t gifio_ondiskgif_obj_next_frame(size_t n_args, const mp_obj_t *args) { - gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(args[0]); - bool setDirty = mp_const_true; - - if (n_args == 1) { - setDirty = mp_obj_is_true(args[1]); - } +//| def next_frame(self) -> float: +//| """Loads the next frame. Returns expected delay before the next frame in seconds.""" +STATIC mp_obj_t gifio_ondiskgif_obj_next_frame(mp_obj_t self_in) { + gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_next_frame(self, setDirty)); + return mp_obj_new_float((float)common_hal_gifio_ondiskgif_next_frame(self, true) / 1000); } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(gifio_ondiskgif_next_frame_obj, 1, 2, gifio_ondiskgif_obj_next_frame); +MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_next_frame_obj, gifio_ondiskgif_obj_next_frame); + -//| duration: int -//| """Returns the total duration of the GIF in milliseconds. (read only)""" +//| duration: float +//| """Returns the total duration of the GIF in seconds. (read only)""" STATIC mp_obj_t gifio_ondiskgif_obj_get_duration(mp_obj_t self_in) { gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_duration(self)); + return mp_obj_new_float((float)common_hal_gifio_ondiskgif_get_duration(self) / 1000); } MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_duration_obj, gifio_ondiskgif_obj_get_duration); @@ -165,12 +159,12 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_frame_count_obj, gifio_ondiskgif_o MP_PROPERTY_GETTER(gifio_ondiskgif_frame_count_obj, (mp_obj_t)&gifio_ondiskgif_get_frame_count_obj); -//| min_delay: int +//| min_delay: float //| """The minimum delay found between frames. (read only)""" STATIC mp_obj_t gifio_ondiskgif_obj_get_min_delay(mp_obj_t self_in) { gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_min_delay(self)); + return mp_obj_new_float((float)common_hal_gifio_ondiskgif_get_min_delay(self) / 1000); } MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_min_delay_obj, gifio_ondiskgif_obj_get_min_delay); @@ -178,13 +172,13 @@ MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_min_delay_obj, gifio_ondiskgif_obj MP_PROPERTY_GETTER(gifio_ondiskgif_min_delay_obj, (mp_obj_t)&gifio_ondiskgif_get_min_delay_obj); -//| max_delay: int +//| max_delay: float //| """The maximum delay found between frames. (read only)""" //| STATIC mp_obj_t gifio_ondiskgif_obj_get_max_delay(mp_obj_t self_in) { gifio_ondiskgif_t *self = MP_OBJ_TO_PTR(self_in); - return MP_OBJ_NEW_SMALL_INT(common_hal_gifio_ondiskgif_get_max_delay(self)); + return mp_obj_new_float((float)common_hal_gifio_ondiskgif_get_max_delay(self) / 1000); } MP_DEFINE_CONST_FUN_OBJ_1(gifio_ondiskgif_get_max_delay_obj, gifio_ondiskgif_obj_get_max_delay); From b2a205e6150d71b29db8d389d9cd0c7c4be07f03 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Fri, 17 Feb 2023 15:48:26 -0600 Subject: [PATCH 14/16] Doc fix --- shared-bindings/gifio/OnDiskGif.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared-bindings/gifio/OnDiskGif.c b/shared-bindings/gifio/OnDiskGif.c index 32e6153b9a7b5..83cf5539e77fe 100644 --- a/shared-bindings/gifio/OnDiskGif.c +++ b/shared-bindings/gifio/OnDiskGif.c @@ -34,8 +34,7 @@ #include "shared-bindings/gifio/OnDiskGif.h" //| class OnDiskGif: -//| """Loads frames of the GIF straight from disk. This minimizes memory use but can -//| lead to much slower pixel load times +//| """Loads one frame of a GIF into memory at a time. //| //| .. code-block:: Python //| From c637ec9ea03448a619d8237613532ead02794aca Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Fri, 17 Feb 2023 23:37:49 -0600 Subject: [PATCH 15/16] Removed forgotten func defn --- shared-bindings/gifio/OnDiskGif.h | 1 - 1 file changed, 1 deletion(-) diff --git a/shared-bindings/gifio/OnDiskGif.h b/shared-bindings/gifio/OnDiskGif.h index aa84baf32d0f2..cdd884ec1d47d 100644 --- a/shared-bindings/gifio/OnDiskGif.h +++ b/shared-bindings/gifio/OnDiskGif.h @@ -38,7 +38,6 @@ uint32_t common_hal_gifio_ondiskgif_get_pixel(gifio_ondiskgif_t *bitmap, int16_t x, int16_t y); uint16_t common_hal_gifio_ondiskgif_get_height(gifio_ondiskgif_t *self); -mp_obj_t common_hal_gifio_ondiskgif_get_pixel_shader(gifio_ondiskgif_t *self); mp_obj_t common_hal_gifio_ondiskgif_get_bitmap(gifio_ondiskgif_t *self); uint16_t common_hal_gifio_ondiskgif_get_width(gifio_ondiskgif_t *self); uint8_t common_hal_gifio_ondiskgif_next_frame(gifio_ondiskgif_t *self, bool setDirty); From f7f58a64e98a3e543f96db9490a8a7d864312889 Mon Sep 17 00:00:00 2001 From: gamblor21 Date: Sat, 18 Feb 2023 15:15:34 -0600 Subject: [PATCH 16/16] Moved output colorspace to object --- shared-bindings/displayio/ColorConverter.c | 4 +--- shared-module/displayio/ColorConverter.c | 1 + shared-module/displayio/ColorConverter.h | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared-bindings/displayio/ColorConverter.c b/shared-bindings/displayio/ColorConverter.c index 9c3d297fdcf34..06de5a4b0961a 100644 --- a/shared-bindings/displayio/ColorConverter.c +++ b/shared-bindings/displayio/ColorConverter.c @@ -73,10 +73,8 @@ STATIC mp_obj_t displayio_colorconverter_obj_convert(mp_obj_t self_in, mp_obj_t displayio_colorconverter_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t color = mp_arg_validate_type_int(color_obj, MP_QSTR_color); - _displayio_colorspace_t colorspace; - colorspace.depth = 16; uint32_t output_color; - common_hal_displayio_colorconverter_convert(self, &colorspace, color, &output_color); + common_hal_displayio_colorconverter_convert(self, &self->output_colorspace, color, &output_color); return MP_OBJ_NEW_SMALL_INT(output_color); } MP_DEFINE_CONST_FUN_OBJ_2(displayio_colorconverter_convert_obj, displayio_colorconverter_obj_convert); diff --git a/shared-module/displayio/ColorConverter.c b/shared-module/displayio/ColorConverter.c index 707601a3e7fdd..078c7876e4455 100644 --- a/shared-module/displayio/ColorConverter.c +++ b/shared-module/displayio/ColorConverter.c @@ -45,6 +45,7 @@ void common_hal_displayio_colorconverter_construct(displayio_colorconverter_t *s self->dither = dither; self->transparent_color = NO_TRANSPARENT_COLOR; self->input_colorspace = input_colorspace; + self->output_colorspace.depth = 16; } uint16_t displayio_colorconverter_compute_rgb565(uint32_t color_rgb888) { diff --git a/shared-module/displayio/ColorConverter.h b/shared-module/displayio/ColorConverter.h index 7e4e9819acdff..f67b1dbfda641 100644 --- a/shared-module/displayio/ColorConverter.h +++ b/shared-module/displayio/ColorConverter.h @@ -37,6 +37,7 @@ typedef struct displayio_colorconverter { mp_obj_base_t base; bool dither; uint8_t input_colorspace; + _displayio_colorspace_t output_colorspace; uint32_t transparent_color; } displayio_colorconverter_t;