Initial gif proof of concept
This commit is contained in:
parent
db5ca717da
commit
6a9b7199ec
|
@ -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 \
|
||||
|
|
|
@ -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 <stdint.h>
|
||||
|
||||
#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
|
||||
//| <https://www.adafruit.com/product/3900>`_.
|
||||
//|
|
||||
//| .. 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,
|
||||
};
|
|
@ -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
|
|
@ -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) },
|
||||
|
|
|
@ -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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
//
|
||||
// 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__
|
|
@ -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 <string.h>
|
||||
|
||||
#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;
|
||||
}
|
|
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
|
@ -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() */
|
Loading…
Reference in New Issue