diff --git a/COPYRIGHTS.md b/COPYRIGHTS.md index 99ed2ea51b..81494453b4 100644 --- a/COPYRIGHTS.md +++ b/COPYRIGHTS.md @@ -14,10 +14,6 @@ For the licenses, see the corresponding `LICENSE.txt` file in each library’s f - Source: https://github.com/freetype/freetype - Note: Only the interfaces are used; FreeType itself is not part of LVGL. -**GifDec (GIF decoder library)** -- Path: src/libs/gifdec -- Source: https://github.com/lecram/gifdec - **LodePNG (PNG decoder)** - Path: src/libs/lodepng - Source: https://github.com/lvandeve/lodepng @@ -61,3 +57,7 @@ For the licenses, see the corresponding `LICENSE.txt` file in each library’s f **FT800-FT813 (EVE GPU driver)** - Path src/libs/FT800-FT813 - Source: https://github.com/RudolphRiedel/FT800-FT813 + +**AnimatedGIF (GIF decoder library)** +- Path: src/libs/gif/AnimatedGIF +- Source: https://github.com/bitbank2/AnimatedGIF diff --git a/docs/src/details/libs/gif.rst b/docs/src/details/libs/gif.rst index 5107db1700..d2bc2944e9 100644 --- a/docs/src/details/libs/gif.rst +++ b/docs/src/details/libs/gif.rst @@ -6,8 +6,8 @@ GIF Decoder **GIF Decoder** is an LVGL extension that enables you to use GIF images in your LVGL UI. -For a detailed introduction, see: https://github.com/lecram/gifdec . - +The implementation uses the `AnimatedGIF `__ +library. Usage @@ -16,6 +16,22 @@ Usage Once enabled in ``lv_conf.h`` by setting :c:macro:`LV_USE_GIF` to ``1``, :cpp:expr:`lv_gif_create(parent)` can be used to create a gif widget. +Set the color format of the gif framebuffer with :cpp:expr:`lv_gif_set_color_format(widget, color_format)`. +Use :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888` for gifs with transparency. +It is set to :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888` by default. +Significant RAM can be saved by using a smaller color format. +A color format conversion can be saved during rendering if the color format matches the display +color format. +This function can be called before :cpp:func:`lv_gif_set_src` to prevent the initial default ARGB8888 +framebuffer from being allocated. + +The supported color formats are: + +- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565` +- :cpp:enumerator:`LV_COLOR_FORMAT_RGB565_SWAPPED` +- :cpp:enumerator:`LV_COLOR_FORMAT_RGB888` +- :cpp:enumerator:`LV_COLOR_FORMAT_ARGB8888` + :cpp:expr:`lv_gif_set_src(widget, src)` works very similarly to :cpp:func:`lv_image_set_src`. As source, it also accepts images as variables (:c:struct:`lv_image_dsc_t`) or files. @@ -43,15 +59,12 @@ from files. To do so, follow the instructions in :ref:`file_system`. Memory Requirements ******************* -To decode and display a GIF animation the following amount of RAM (in bytes) is -required for each of the following color depths: - .. |times| unicode:: U+000D7 .. MULTIPLICATION SIGN -- :c:macro:`LV_COLOR_DEPTH` ``8``: 3 |times| image width |times| image height -- :c:macro:`LV_COLOR_DEPTH` ``16``: 4 |times| image width |times| image height -- :c:macro:`LV_COLOR_DEPTH` ``32``: 5 |times| image width |times| image height - +To decode and display a GIF animation ~25 kB of RAM is needed plus +(color format pixel size + 1) |times| image width |times| image height. +RGB565 has a pixel size of 2, RGB888 has a pixel size of 3, and +ARGB8888 has a pixel size of 4. .. _gif_example: diff --git a/examples/libs/gif/lv_example_gif_1.c b/examples/libs/gif/lv_example_gif_1.c index e972e39aee..23af6ff4cd 100644 --- a/examples/libs/gif/lv_example_gif_1.c +++ b/examples/libs/gif/lv_example_gif_1.c @@ -10,10 +10,12 @@ void lv_example_gif_1(void) lv_obj_t * img; img = lv_gif_create(lv_screen_active()); + lv_gif_set_color_format(img, LV_COLOR_FORMAT_ARGB8888); lv_gif_set_src(img, &img_bulb_gif); lv_obj_align(img, LV_ALIGN_LEFT_MID, 20, 0); img = lv_gif_create(lv_screen_active()); + lv_gif_set_color_format(img, LV_COLOR_FORMAT_ARGB8888); /* Assuming a File system is attached to letter 'A' * E.g. set LV_USE_FS_STDIO 'A' in lv_conf.h */ lv_gif_set_src(img, "A:lvgl/examples/libs/gif/bulb.gif"); diff --git a/lvgl_private.h b/lvgl_private.h index a68044794c..9a4ee0f8da 100644 --- a/lvgl_private.h +++ b/lvgl_private.h @@ -32,7 +32,6 @@ extern "C" { #include "src/others/xml/lv_xml_private.h" #include "src/libs/qrcode/lv_qrcode_private.h" #include "src/libs/barcode/lv_barcode_private.h" -#include "src/libs/gif/lv_gif_private.h" #include "src/draw/lv_draw_triangle_private.h" #include "src/draw/lv_draw_private.h" #include "src/draw/lv_draw_rect_private.h" diff --git a/scripts/code-format.cfg b/scripts/code-format.cfg index 3ee0adcf3e..1bb3a6b931 100644 --- a/scripts/code-format.cfg +++ b/scripts/code-format.cfg @@ -37,8 +37,7 @@ --exclude=../src/lv_conf_internal.h --exclude=../src/core/lv_obj_style_gen.c --exclude=../src/core/lv_obj_style_gen.h ---exclude=../src/libs/gif/gifdec.c ---exclude=../src/libs/gif/gifdec.h +--exclude=../src/libs/gif/AnimatedGIF --exclude=../src/libs/lodepng/lodepng.c --exclude=../src/libs/lodepng/lodepng.h --exclude=../src/libs/qrcode/qrcodegen.c diff --git a/src/libs/gif/AnimatedGIF/LICENSE b/src/libs/gif/AnimatedGIF/LICENSE new file mode 100644 index 0000000000..716f427d06 --- /dev/null +++ b/src/libs/gif/AnimatedGIF/LICENSE @@ -0,0 +1,204 @@ +Copyright 2020 BitBank Software, Inc. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. + diff --git a/src/libs/gif/AnimatedGIF/src/AnimatedGIF.h b/src/libs/gif/AnimatedGIF/src/AnimatedGIF.h new file mode 100644 index 0000000000..118ab66b2a --- /dev/null +++ b/src/libs/gif/AnimatedGIF/src/AnimatedGIF.h @@ -0,0 +1,227 @@ +// 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 "../../../../misc/lv_fs.h" +#include "../../../../lv_conf_internal.h" +#include LV_STDINT_INCLUDE +#include LV_LIMITS_INCLUDE +#include "../../../../stdlib/lv_string.h" + +// +// GIF Animator +// Written by Larry Bank +// Copyright (c) 2020 BitBank Software, Inc. +// bitbank@pobox.com +// +// Designed to decode images up to 480x320 on MCUs +// using less than 22K of RAM +// ...and decode any sized image when more RAM is available +// +// ** NEW ** +// Turbo mode added Feb 18, 2024. This option decodes images +// up to 30x faster if there is enough RAM (48K + full framebuffer) +// + +/* GIF Defines and variables */ +#define MAX_CHUNK_SIZE 255 +// +// These 2 macros can be changed to limit the amount of RAM +// required by the decoder. For example, decoding 1-bit images to +// a 128x32 display will not need a max code size of 12 nor a palette +// with 256 entries +// +#define TURBO_BUFFER_SIZE 0x6100 + +// If you intend to decode generic GIFs, you want this value to be 12. If you are using GIFs solely for animations in +// your own project, and you control the GIFs you intend to play, then you can save additional RAM here: +// the decoder must reserve a minimum of 4 byte * (1<10kB RAM, but you will not be able to decode arbitrary +// images anymore. One application to craft such GIFs can be found here (use option -d) +// https://create.stephan-brumme.com/flexigif-lossless-gif-lzw-optimization/ +#define MAX_CODE_SIZE 12 + +#define MAX_COLORS 256 +#define MAX_WIDTH 480 +#define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE) +#define LZW_HIGHWATER (4*MAX_CHUNK_SIZE) +// This buffer is used to store the pixel sequence in reverse order +// it needs to be large enough to hold the longest possible +// sequence (1<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() */ + +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; + if (LV_FS_RES_OK != lv_fs_open(&pGIF->GIFFile.fHandle, szFilename, LV_FS_MODE_RD)) + return 0; + lv_fs_seek(&pGIF->GIFFile.fHandle, 0, LV_FS_SEEK_END); + uint32_t pos; + lv_fs_tell(&pGIF->GIFFile.fHandle, &pos); + pGIF->GIFFile.iSize = pos; + lv_fs_seek(&pGIF->GIFFile.fHandle, 0, LV_FS_SEEK_SET); + return GIFInit(pGIF); +} /* GIF_openFile() */ + +void GIF_close(GIFIMAGE *pGIF) +{ + if (pGIF->pfnClose) + (*pGIF->pfnClose)(&pGIF->GIFFile.fHandle); +} /* GIF_close() */ + +void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType) +{ + lv_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; + if (pGIF->pTurboBuffer) { // the presence of the Turbo buffer indicates Turbo mode + rc = DecodeLZWTurbo(pGIF, 0); + } else { + 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() */ + +// +// 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; + lv_memmove(pBuf, &pFile->pData[pFile->iPos], iBytesRead); + pFile->iPos += iBytesRead; + return iBytesRead; +} /* readMem() */ + +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() */ + +static void closeFile(lv_fs_file_t *handle) +{ + lv_fs_close(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; + lv_fs_seek(&pFile->fHandle, iPosition, LV_FS_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; + uint32_t br; + lv_fs_read(&pFile->fHandle, pBuf, iBytesRead, &br); + iBytesRead = br; + pFile->iPos += iBytesRead; + return iBytesRead; +} /* readFile() */ + +// +// 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 || pGIF->iCanvasHeight > 32767) { // too big or corrupt + 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 = 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 (lv_memcmp(p, "GIF89", 5) != 0 && lv_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; + 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<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) + pPage->pPalette[i] = usRGB565; + else + pPage->pPalette[i] = (usRGB565 << 8) | (usRGB565 >> 8); // SPI wants MSB first + iOffset += 3; + } + } else if (pPage->ucPaletteType == GIF_PALETTE_1BPP || pPage->ucPaletteType == GIF_PALETTE_1BPP_OLED) { + uint8_t *pPal1 = (uint8_t*)pPage->pPalette; + for (i=0; i<(1<= 512); // bright enough = 1 + iOffset += 3; + } + } else { // just copy it as-is (RGB888 & RGB8888 output) + lv_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 + 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 + { + lv_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 (lv_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 + { + lv_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 (bInfoOnly) + return 1; // we've got the info we needed, leave + 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]); + if (pPage->iWidth > pPage->iCanvasWidth || pPage->iHeight > pPage->iCanvasHeight || + pPage->iWidth + pPage->iX > pPage->iCanvasWidth || pPage->iHeight + pPage->iY > pPage->iCanvasHeight) { + pPage->iError = GIF_DECODE_ERROR; // must be a corrupt file to encounter this error here + return 0; + } + 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> 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] = (usRGB565 << 8) | (usRGB565 >> 8); // SPI wants MSB first + iOffset += 3; + } + } else if (pPage->ucPaletteType == GIF_PALETTE_1BPP || pPage->ucPaletteType == GIF_PALETTE_1BPP_OLED) { + uint8_t *pPal1 = (uint8_t*)pPage->pLocalPalette; + for (i=0; i= 512); // bright enough = 1 + iOffset += 3; + } + } else { // just copy it as-is + lv_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)) + { + lv_memcpy(&pPage->ucLZW[pPage->iLZWSize], &p[iOffset], c); + pPage->iLZWSize += c; + iOffset += c; + } + else // partial chunk in our buffer + { + int iPartialLen = (iBytesRead - iOffset); + lv_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<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 + { + lv_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<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 */ + { + lv_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 + { + lv_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); + int iLZWBufSize; + unsigned char c = 1; + + // Turbo mode uses combined buffers to read more compressed data + iLZWBufSize = (pPage->pTurboBuffer) ? LZW_BUF_SIZE_TURBO : LZW_BUF_SIZE; + // move any existing data down + if (pPage->bEndOfFrame || iDelta >= (iLZWBufSize - 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; 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 < (iLZWBufSize-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() */ +// +// Draw and convert pixels when the user wants fully rendered output +// +static void DrawCooked(GIFIMAGE *pPage, GIFDRAW *pDraw, void *pDest) +{ + uint8_t c, *s, *d8, *pEnd; + uint8_t *pActivePalette; + + pActivePalette = (pPage->bUseLocalPalette) ? (uint8_t *)pPage->pLocalPalette : (uint8_t *)pPage->pPalette; + // d8 points to the line in the full sized canvas where the new opaque pixels will be merged + d8 = &pPage->pFrameBuffer[pDraw->iX + (pDraw->iY + pDraw->y) * pPage->iCanvasWidth]; + s = pDraw->pPixels; // s points to the newly decoded pixels of this line of the current frame + pEnd = s + pDraw->iWidth; // faster way to loop over the source pixels - eliminates a counter variable + + if (pPage->ucPaletteType == GIF_PALETTE_1BPP || pPage->ucPaletteType == GIF_PALETTE_1BPP_OLED) { // 1-bit mono + uint8_t *d = NULL; + uint8_t *pPal = pActivePalette; + uint8_t uc, ucMask; + int iPitch = 0; + if (pPage->ucPaletteType == GIF_PALETTE_1BPP) { // horizontal pixels + d = pPage->pFrameBuffer; + iPitch = (pPage->iCanvasWidth+7)/8; + d += (pPage->iCanvasWidth * pPage->iCanvasHeight); + d += pDraw->iX/8; // starting column + d += (pDraw->iY + pDraw->y) * iPitch; + // Apply the new pixels to the main image and generate 1-bpp output + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t ucTransparent = pDraw->ucTransparent; + if (pDraw->ucDisposalMethod == 2) { // restore to background color + uint8_t u8BG = pPal[pDraw->ucBackground]; + if (u8BG == 1) u8BG = 0xff; // set all bits to use mask + uc = *d; ucMask = (0x80 >> (pDraw->iX & 7));; + while (s < pEnd) { + c = *s++; + if (c != ucTransparent) { + if (pPal[c]) + uc |= ucMask; + else + uc &= ~ucMask; + *d8++ = c; + } else { + uc |= (u8BG & ucMask); // transparent pixel is restored to background color + *d8++ = pDraw->ucBackground; + } + ucMask >>= 1; + if (ucMask == 0) { // write the completed byte + *d++ = uc; + uc = *d; + ucMask = 0x80; + } + } + *d = uc; // write last partial byte + } else { // no disposal, just write non-transparent pixels + uc = *d; ucMask = (0x80 >> (pDraw->iX & 7)); + while (s < pEnd) { + c = *s++; + if (c != ucTransparent) { + if (pPal[c]) + uc |= ucMask; + else + uc &= ~ucMask; + *d8 = c; + } + d8++; + ucMask >>= 1; + if (ucMask == 0) { + *d++ = uc; + uc = *d; + ucMask = 0x80; + } + } + *d = uc; + } + } else { // convert everything as opaque + uc = *d; ucMask = (0x80 >> (pDraw->iX & 7)); // left pixel is MSB + while (s < pEnd) { + c = *d8++ = *s++; // just write the new opaque pixels over the old + if (pPal[c]) // if non-zero, set white pixel + uc |= ucMask; + else + uc &= ~ucMask; + ucMask >>= 1; + if (ucMask == 0) { // time to write the current byte + *d++ = uc; + uc = *d; + ucMask = 0x80; + } + } + *d = uc; + } + } else { // vertical pixels + d = pPage->pFrameBuffer; + d += (pPage->iCanvasWidth * pPage->iCanvasHeight); + d += pDraw->iX; // starting column + d += ((pDraw->iY + pDraw->y)>>3) * pPage->iCanvasWidth; + ucMask = 1 << ((pDraw->iY + pDraw->y) & 7); + // Apply the new pixels to the main image and generate 1-bpp output + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t ucTransparent = pDraw->ucTransparent; + if (pDraw->ucDisposalMethod == 2) { // restore to background color + uint8_t u8BG = pPal[pDraw->ucBackground]; + u8BG *= ucMask; // set the right bit + while (s < pEnd) { + c = *s++; + uc = *d & ~ucMask; // clear old pixel + if (c != ucTransparent) { + uc |= (pPal[c] * ucMask); + *d8++ = c; + } else { + uc |= u8BG; // transparent pixel is restored to background color + *d8++ = pDraw->ucBackground; + } + *d++ = uc; // write back the updated pixel + } + } else { // no disposal, just write non-transparent pixels + while (s < pEnd) { + c = *s++; + uc = *d & ~ucMask; + if (c != ucTransparent) { + *d = uc | (pPal[c] * ucMask); + *d8 = c; + } + d++; + d8++; + } + } + } else { // convert everything as opaque + while (s < pEnd) { + c = *d8++ = *s++; // just write the new opaque pixels over the old + uc = *d & ~ucMask; + *d++ = uc | (pPal[c] * ucMask); + } + } + } + } else if (pPage->ucPaletteType == GIF_PALETTE_RGB565_LE || pPage->ucPaletteType == GIF_PALETTE_RGB565_BE) { + uint16_t *d, *pPal = (uint16_t *)pActivePalette; + d = (uint16_t *)pDest; // dest pointer to the cooked pixels + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t ucTransparent = pDraw->ucTransparent; + if (pDraw->ucDisposalMethod == 2) { // restore to background color + uint16_t u16BG = pPal[pDraw->ucBackground]; + while (s < pEnd) { + c = *s++; + if (c != ucTransparent) { + *d++ = pPal[c]; + *d8++ = c; + } else { + *d++ = u16BG; // transparent pixel is restored to background color + *d8++ = pDraw->ucBackground; + } + } + } else { // no disposal, just write non-transparent pixels + while (s < pEnd) { + c = *s++; + if (c != ucTransparent) { + *d = pPal[c]; + *d8 = c; + } + d++; + d8++; + } + } + } else { // convert all pixels through the palette without transparency +#if REGISTER_WIDTH == 64 + // parallelize the writes + // optimizing for the write buffer helps; reading 4 bytes at a time vs 1 doesn't on M1 + while (s < pEnd + 4) { // group 4 pixels + BIGUINT bu; + uint8_t s0, s1, s2, s3; + uint16_t d1, d2, d3; + *(uint32_t *)d8 = *(uint32_t *)s; // just copy new opaque pixels over the old + s0 = s[0]; s1 = s[1]; s2 = s[2]; s3 = s[3]; + bu = pPal[s0]; // not much difference on Apple M1 + d1 = pPal[s1]; // but other processors may gain + d2 = pPal[s2]; // from unrolling the reads + d3 = pPal[s3]; + bu |= (BIGUINT)d1 << 16; + bu |= (BIGUINT)d2 << 32; + bu |= (BIGUINT)d3 << 48; + s += 4; + d8 += 4; + *(BIGUINT *)d = bu; + d += 4; + } +#endif + while (s < pEnd) { + c = *d8++ = *s++; // just write the new opaque pixels over the old + *d++ = pPal[c]; // and create the cooked pixels through the palette + } + } + } else { // 24bpp or 32bpp + uint8_t pixel, *d, *pPal; + int x; + d = (uint8_t *)pDest; + pPal = pActivePalette; + if (pDraw->ucHasTransparency) { + uint8_t ucTransparent = pDraw->ucTransparent; + if (pDraw->ucDisposalMethod == 2) { // restore to background color + uint8_t * bg = &pPal[pDraw->ucBackground * 3]; + if (pPage->ucPaletteType == GIF_PALETTE_RGB888) { + while (s < pEnd) { + pixel = *s++; + if (pixel != ucTransparent) { + *d8++ = pixel; + d[0] = pPal[(pixel * 3) + 2]; + d[1] = pPal[(pixel * 3) + 1]; + d[2] = pPal[(pixel * 3) + 0]; + d += 3; + } else { + *d8++ = pDraw->ucBackground; + d[0] = bg[2]; + d[1] = bg[1]; + d[2] = bg[0]; + d += 3; + } + } + } else { /* GIF_PALETTE_RGB8888 */ + while (s < pEnd) { + pixel = *s++; + if (pixel != ucTransparent) { + *d8++ = pixel; + d[0] = pPal[(pixel * 3) + 2]; + d[1] = pPal[(pixel * 3) + 1]; + d[2] = pPal[(pixel * 3) + 0]; + d[3] = 0xFF; + d += 4; + } else { + *d8++ = pDraw->ucBackground; + d[3] = 0x00; + d += 4; + } + } + } + } else { // no disposal, just write non-transparent pixels + if (pPage->ucPaletteType == GIF_PALETTE_RGB888) { + for (x=0; xiWidth; x++) { + pixel = *s++; + if (pixel != ucTransparent) { + *d8 = pixel; + d[0] = pPal[(pixel * 3) + 2]; // convert to RGB888 pixels + d[1] = pPal[(pixel * 3) + 1]; + d[2] = pPal[(pixel * 3) + 0]; + } + d8++; + d += 3; + } + } else { // must be RGBA32 + for (x=0; xiWidth; x++) { + pixel = *s++; + if (pixel != ucTransparent) { + *d8 = pixel; + d[0] = pPal[(pixel * 3) + 2]; // convert to RGB8888 pixels + d[1] = pPal[(pixel * 3) + 1]; + d[2] = pPal[(pixel * 3) + 0]; + d[3] = 0xff; + } + d8++; + d += 4; + } + } + } + } else { // no transparency + if (pPage->ucPaletteType == GIF_PALETTE_RGB888) { + for (x=0; xiWidth; x++) { + pixel = *d8++ = *s++; + *d++ = pPal[(pixel * 3) + 2]; // convert to RGB888 pixels + *d++ = pPal[(pixel * 3) + 1]; + *d++ = pPal[(pixel * 3) + 0]; + } + } else { // must be RGBA32 + for (x=0; xiWidth; x++) { + pixel = *d8++ = *s++; + *d++ = pPal[(pixel * 3) + 2]; // convert to RGB8888 pixels + *d++ = pPal[(pixel * 3) + 1]; + *d++ = pPal[(pixel * 3) + 0]; + *d++ = 0xff; + } + } + } // opaque + } +} /* DrawCooked() */ + +// +// 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 + + // Apply the new pixels to the main image + if (pDraw->ucHasTransparency) { // if transparency used + uint8_t c, ucTransparent = pDraw->ucTransparent; + if (pDraw->ucDisposalMethod == 2) { + lv_memset(d, pDraw->ucBackground, pDraw->iWidth); // start with background color + } + for (x=0; xiWidth; x++) { + c = *s++; + if (c != ucTransparent) + *d = c; + d++; + } + } else { // disposal method doesn't matter when there aren't any transparent pixels + lv_memcpy(d, s, pDraw->iWidth); // just overwrite the old pixels + } +} /* DrawNewPixels() */ +// +// LZWCopyBytes +// +// Output the bytes for a single code (checks for buffer len) +// +static int LZWCopyBytes(unsigned char *buf, int iOffset, uint32_t *pSymbols, uint16_t *pLengths) +{ +int iLen; +uint8_t c, *s, *d, *pEnd; +uint32_t u32Offset; + + iLen = *pLengths; + u32Offset = *pSymbols; + // The string data frequently writes past the end of the framebuffer (past last pixel) + // ...but with the placement of our code tables AFTER the framebuffer, it doesn't matter + // Adding a check for buffer overrun here slows everything down about 10% + s = &buf[u32Offset & 0x7fffff]; + d = &buf[iOffset]; + pEnd = &d[iLen]; + while (d < pEnd) // most frequent are 1-8 bytes in length, copy 4 or 8 bytes in these cases too + { +#ifdef ALLOWS_UNALIGNED +// This is a significant perf improvement compared to copying 1 byte at a time +// even though it will often copy too many bytes + BIGUINT tmp = *(BIGUINT *) s; + s += sizeof(BIGUINT); + *(BIGUINT *)d = tmp; + d += sizeof(BIGUINT); +#else +// CPUs which enforce unaligned address exceptions must do it 1 byte at a time + *d++ = *s++; +#endif + } + if (u32Offset & 0x800000) // was a newly used code + { + d = pEnd; // in case we overshot + c = (uint8_t)(u32Offset >> 24); + iLen++; + // since the code with extension byte has now been written to the output, fix the code + *pSymbols = iOffset; +// pSymbols[SYM_EXTRAS] = 0xffffffff; + *d = c; + *pLengths = (uint16_t)iLen; + } + return iLen; +} /* LZWCopyBytes() */ +// +// Macro to extract a variable length code +// +#define GET_CODE_TURBO if (bitnum > (REGISTER_WIDTH - MAX_CODE_SIZE/*codesize*/)) { p += (bitnum >> 3); \ + bitnum &= 7; ulBits = INTELLONG(p); } \ + code = ((ulBits >> bitnum) & sMask); \ + bitnum += codesize; + +// +// DecodeLZWTurbo +// +// Theory of operation: +// +// The 'traditional' LZW decoder maintains a dictionary with a linked list of codes. +// These codes build into longer chains as more data is decoded. To output the pixels, +// the linked list is traversed backwards from the last node to the first, then these +// pixels are copied in reverse order to the output bitmap. +// +// My decoder takes a different approach. The output image becomes the dictionary and +// the tables keep track of where in the output image the 'run' begins and its length. +// ** NB ** +// These tables cannot be 16-bit values because a single dictionary's output can be +// bigger than 64K +// +// I also work with the compressed data differently. Most decoders wind their way through +// the chunked data by constantly checking if the current chunk has run out of data. I +// take a different approach since modern machines have plenty of memory - I 'de-chunk' +// the data first so that the inner loop can just decode as fast as possible. I also keep +// a set of codes in a 64-bit local variable to minimize memory reads. +// +// These 2 changes result in a much faster decoder. For poorly compressed images, the +// speed gain is about 2.5x compared to giflib. For well compressed images (long runs) +// the speed can be as much as 30x faster. This is because it doesn't have to walk +// backwards through the linked list of codes when outputting pixels. It also doesn't +// have to copy pixels in reverse order, then unwind them. +// +static int DecodeLZWTurbo(GIFIMAGE *pImage, int iOptions) +{ +int i, bitnum; +int iUncompressedLen; +uint32_t code, oldcode, codesize, nextcode, nextlim; +uint32_t cc, eoi; +uint32_t sMask; +uint8_t c, *p, *buf, codestart, *pHighWater; +BIGUINT ulBits; +int iLen, iColors; +int iErr = GIF_SUCCESS; +int iOffset; +uint32_t *pSymbols; +uint16_t *pLengths; + + (void)iOptions; + pImage->iYCount = pImage->iHeight; // count down the lines + pImage->iXCount = pImage->iWidth; + bitnum = 0; + pHighWater = pImage->ucLZW + LZW_HIGHWATER_TURBO; + pImage->iLZWOff = 0; // Offset into compressed data + GIFGetMoreData(pImage); // Read some data to start + codestart = pImage->ucCodeStart; + iColors = 1 << codestart; + sMask = UINT32_MAX << (codestart+1); + sMask = 0xffffffff - sMask; + cc = (sMask >> 1) + 1; /* Clear code */ + eoi = cc + 1; + iUncompressedLen = (pImage->iWidth * pImage->iHeight); + buf = (uint8_t *)pImage->pTurboBuffer; + pSymbols = (uint32_t *)&buf[iUncompressedLen+256]; // we need 32-bits (really 23) for the offsets + pLengths = (uint16_t *)&pSymbols[4096]; // but only 16-bits for the length of any single string + iOffset = 0; // output data offset + p = pImage->ucLZW; // un-chunked LZW data + ulBits = INTELLONG(p); // start by reading some LZW data + // set up the default symbols (0..iColors-1) + for (i = 0; i= nextlim && codesize < MAX_CODE_SIZE) { + codesize++; + nextlim <<= 1; + sMask = (sMask << 1) | 1; + } + if (p >= pHighWater) { + pImage->iLZWOff = (int)(p - pImage->ucLZW); // restore object member var + GIFGetMoreData(pImage); // We need to read more LZW data + p = &pImage->ucLZW[pImage->iLZWOff]; + } + oldcode = code; + GET_CODE_TURBO + } /* while not end of LZW code stream */ + } // while not end of frame + if (pImage->ucDrawType == GIF_DRAW_COOKED && pImage->pfnDraw && pImage->pFrameBuffer) { // convert each line through the palette + GIFDRAW gd; + gd.iX = pImage->iX; + gd.iY = pImage->iY; + gd.iWidth = pImage->iWidth; + gd.iHeight = pImage->iHeight; + gd.pPalette = (pImage->bUseLocalPalette) ? pImage->pLocalPalette : pImage->pPalette; + gd.pPalette24 = (uint8_t *)gd.pPalette; // just cast the pointer for RGB888 + gd.ucIsGlobalPalette = pImage->bUseLocalPalette==1?0:1; + gd.pUser = pImage->pUser; + gd.ucPaletteType = pImage->ucPaletteType; + for (int y=0; yiHeight; y++) { + gd.y = y; + gd.pPixels = &buf[(y * pImage->iWidth)]; // source pixels + // Ugly logic to handle the interlaced line position, but it + // saves having to have another set of state variables + if (pImage->ucMap & 0x40) { // interlaced? + int height = pImage->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 = (pImage->ucGIFBits & 0x1c)>>2; + gd.ucTransparent = pImage->ucTransparent; + gd.ucHasTransparency = pImage->ucGIFBits & 1; + gd.ucBackground = pImage->ucBackground; + gd.iCanvasWidth = pImage->iCanvasWidth; + DrawCooked(pImage, &gd, &buf[pImage->iCanvasHeight * pImage->iCanvasWidth]); // dest = past end of canvas + gd.pPixels = &buf[pImage->iCanvasHeight * pImage->iCanvasWidth]; // point to the line we just converted + (*pImage->pfnDraw)(&gd); // callback to handle this line + } + } + return iErr; +} /* DecodeLZWTurbo() */ + +// +// GIFMakePels +// +static void GIFMakePels(GIFIMAGE *pPage, unsigned int code) +{ + int iPixCount; + unsigned short *giftabs; + unsigned char *buf, *s, *pEnd, *gifpels; + /* Copy this string of sequential pixels to output buffer */ + // iPixCount = 0; + pEnd = pPage->ucFileBuf; + s = pEnd + 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 == pEnd) /* Houston, we have a problem */ + { + return; /* Exit with error */ + } + *(--s) = gifpels[code]; + code = giftabs[code]; + } + iPixCount = (int)(intptr_t)(pEnd + FILE_BUF_SIZE - s); + while (iPixCount && pPage->iYCount > 0) + { + if (pPage->iXCount > iPixCount) /* Pixels fit completely on the line */ + { + pEnd = buf + iPixCount; + while (buf < pEnd) { +#ifdef ALLOWS_UNALIGNED +// This is a significant perf improvement compared to copying 1 byte at a time +// even though it will often copy too many bytes. Since we're not at the end of +// the line, it's okay to copy a few extra pixels. + BIGUINT tmp = *(BIGUINT *) s; + s += sizeof(BIGUINT); + *(BIGUINT *)buf = tmp; + buf += sizeof(BIGUINT); +#else + *buf++ = *s++; +#endif + } + pPage->iXCount -= iPixCount; + // iPixCount = 0; + if (pPage->iLZWOff >= LZW_HIGHWATER) + GIFGetMoreData(pPage); // We need to read more LZW data + 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.iCanvasWidth = pPage->iCanvasWidth; + gd.pUser = pPage->pUser; + gd.ucPaletteType = pPage->ucPaletteType; + if (pPage->pFrameBuffer) // update the frame buffer + { + int iPitch = 0, iBpp = 1, iOffset = pPage->iCanvasWidth * pPage->iCanvasHeight; + if (pPage->ucDrawType == GIF_DRAW_COOKED) { + if (!pPage->pfnDraw) { // no draw callback, prepare the full frame + switch (pPage->ucPaletteType) { + case GIF_PALETTE_1BPP: + iPitch = (pPage->iCanvasWidth + 7) / 8; + break; + case GIF_PALETTE_RGB565_BE: + case GIF_PALETTE_RGB565_LE: + iPitch = (pPage->iCanvasWidth * 2); + iBpp = 2; + break; + case GIF_PALETTE_RGB888: + iPitch = pPage->iCanvasWidth * 3; + iBpp = 3; + break; + case GIF_PALETTE_RGB8888: + iPitch = pPage->iCanvasWidth * 4; + iBpp = 4; + break; + } + iOffset += (iBpp * pPage->iX) + ((gd.y + pPage->iY) * iPitch); + } + DrawCooked(pPage, &gd, &pPage->pFrameBuffer[iOffset]); + // pass the cooked pixel pointer to the GIFDraw callback + gd.pPixels = &pPage->pFrameBuffer[iOffset]; + } else { // the user will manage converting them through the palette + DrawNewPixels(pPage, &gd); // merge the new opaque pixels + } + } + if (pPage->pfnDraw) { + (*pPage->pfnDraw)(&gd); // callback to handle this line + } + pPage->iYCount--; + buf = pPage->ucLineBuf; + if (pPage->iLZWOff >= LZW_HIGHWATER) + GIFGetMoreData(pPage); // We need to read more LZW data + } + } /* while */ + if (pPage->iLZWOff >= LZW_HIGHWATER) + GIFGetMoreData(pPage); // We need to read more LZW data + 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 REGISTER_WIDTH 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 c, *gifpels, *p; + // int iStripSize; + //unsigned char **index; + BIGUINT 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); + if (pImage->ucDrawType == GIF_DRAW_COOKED && pImage->pFrameBuffer == NULL && pImage->pfnDraw == NULL) { // without a framebuffer and a GIFDRAW callback, we cannot continue + pImage->iError = GIF_INVALID_PARAMETER; + return 1; // indicate a problem + } + // If the user selected RAW output and there is no GIFDRAW callback, that won't work either + if (pImage->ucDrawType == GIF_DRAW_RAW && pImage->pfnDraw == NULL) { + pImage->iError = GIF_INVALID_PARAMETER; + return 1; // indicate a problem + } + 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 + lv_memset(&giftabs[cc], (uint8_t) LINK_UNUSED, sizeof(pImage->usGIFTable) - sizeof(giftabs[0])*cc); + 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 + } + c = 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] = c; // oldcode pixel value + gifpels[PIXEL_LAST + nextcode] = c = gifpels[PIXEL_FIRST + code]; + } + nextcode++; + if (nextcode >= nextlim && codesize < MAX_CODE_SIZE) + { + codesize++; + nextlim <<= 1; + sMask = nextlim - 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() */ + + diff --git a/src/libs/gif/LICENSE.txt b/src/libs/gif/LICENSE.txt deleted file mode 100644 index c53d51993d..0000000000 --- a/src/libs/gif/LICENSE.txt +++ /dev/null @@ -1,2 +0,0 @@ -All of the source code and documentation for gifdec is released into the -public domain and provided without warranty of any kind. diff --git a/src/libs/gif/gifdec.c b/src/libs/gif/gifdec.c deleted file mode 100644 index 7c278d6eda..0000000000 --- a/src/libs/gif/gifdec.c +++ /dev/null @@ -1,823 +0,0 @@ -#include "gifdec.h" -#include "../../misc/lv_log.h" -#include "../../stdlib/lv_mem.h" -#include "../../misc/lv_color.h" -#if LV_USE_GIF - -#include -#include -#include - -#define MIN(A, B) ((A) < (B) ? (A) : (B)) -#define MAX(A, B) ((A) > (B) ? (A) : (B)) - -typedef struct Entry { - uint16_t length; - uint16_t prefix; - uint8_t suffix; -} Entry; - -typedef struct Table { - int bulk; - int nentries; - Entry * entries; -} Table; - -#if LV_GIF_CACHE_DECODE_DATA -#define LZW_MAXBITS 12 -#define LZW_TABLE_SIZE (1 << LZW_MAXBITS) -#define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4) -#endif - -static gd_GIF * gif_open(gd_GIF * gif); -static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file); -static void f_gif_read(gd_GIF * gif, void * buf, size_t len); -static int f_gif_seek(gd_GIF * gif, size_t pos, int k); -static void f_gif_close(gd_GIF * gif); - -#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM - #include "gifdec_mve.h" -#endif - -static uint16_t -read_num(gd_GIF * gif) -{ - uint8_t bytes[2]; - - f_gif_read(gif, bytes, 2); - return bytes[0] + (((uint16_t) bytes[1]) << 8); -} - -gd_GIF * -gd_open_gif_file(const char * fname) -{ - gd_GIF gif_base; - memset(&gif_base, 0, sizeof(gif_base)); - - bool res = f_gif_open(&gif_base, fname, true); - if(!res) return NULL; - - return gif_open(&gif_base); -} - -gd_GIF * -gd_open_gif_data(const void * data) -{ - gd_GIF gif_base; - memset(&gif_base, 0, sizeof(gif_base)); - - bool res = f_gif_open(&gif_base, data, false); - if(!res) return NULL; - - return gif_open(&gif_base); -} - -static gd_GIF * gif_open(gd_GIF * gif_base) -{ - uint8_t sigver[3]; - uint16_t width, height, depth; - uint8_t fdsz, bgidx, aspect; - uint8_t * bgcolor; - int gct_sz; - gd_GIF * gif = NULL; - - /* Header */ - f_gif_read(gif_base, sigver, 3); - if(memcmp(sigver, "GIF", 3) != 0) { - LV_LOG_WARN("invalid signature"); - goto fail; - } - /* Version */ - f_gif_read(gif_base, sigver, 3); - if(memcmp(sigver, "89a", 3) != 0) { - LV_LOG_WARN("invalid version"); - goto fail; - } - /* Width x Height */ - width = read_num(gif_base); - height = read_num(gif_base); - /* FDSZ */ - f_gif_read(gif_base, &fdsz, 1); - /* Presence of GCT */ - if(!(fdsz & 0x80)) { - LV_LOG_WARN("no global color table"); - goto fail; - } - /* Color Space's Depth */ - depth = ((fdsz >> 4) & 7) + 1; - /* Ignore Sort Flag. */ - /* GCT Size */ - gct_sz = 1 << ((fdsz & 0x07) + 1); - /* Background Color Index */ - f_gif_read(gif_base, &bgidx, 1); - /* Aspect Ratio */ - f_gif_read(gif_base, &aspect, 1); - /* Create gd_GIF Structure. */ - if(0 == width || 0 == height){ - LV_LOG_WARN("Zero size image"); - goto fail; - } -#if LV_GIF_CACHE_DECODE_DATA - if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){ - LV_LOG_WARN("Image dimensions are too large"); - goto fail; - } - gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE); - #else - if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){ - LV_LOG_WARN("Image dimensions are too large"); - goto fail; - } - gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height); - #endif - if(!gif) goto fail; - memcpy(gif, gif_base, sizeof(gd_GIF)); - gif->width = width; - gif->height = height; - gif->depth = depth; - /* Read GCT */ - gif->gct.size = gct_sz; - f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size); - gif->palette = &gif->gct; - gif->bgindex = bgidx; - gif->canvas = (uint8_t *) &gif[1]; - gif->frame = &gif->canvas[4 * width * height]; - if(gif->bgindex) { - memset(gif->frame, gif->bgindex, gif->width * gif->height); - } - bgcolor = &gif->palette->colors[gif->bgindex * 3]; - #if LV_GIF_CACHE_DECODE_DATA - gif->lzw_cache = gif->frame + width * height; - #endif - -#ifdef GIFDEC_FILL_BG - GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0xff); -#else - for(int i = 0; i < gif->width * gif->height; i++) { - gif->canvas[i * 4 + 0] = *(bgcolor + 2); - gif->canvas[i * 4 + 1] = *(bgcolor + 1); - gif->canvas[i * 4 + 2] = *(bgcolor + 0); - gif->canvas[i * 4 + 3] = 0xff; - } -#endif - gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->loop_count = -1; - goto ok; -fail: - f_gif_close(gif_base); -ok: - return gif; -} - -static void -discard_sub_blocks(gd_GIF * gif) -{ - uint8_t size; - - do { - f_gif_read(gif, &size, 1); - f_gif_seek(gif, size, LV_FS_SEEK_CUR); - } while(size); -} - -static void -read_plain_text_ext(gd_GIF * gif) -{ - if(gif->plain_text) { - uint16_t tx, ty, tw, th; - uint8_t cw, ch, fg, bg; - size_t sub_block; - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */ - tx = read_num(gif); - ty = read_num(gif); - tw = read_num(gif); - th = read_num(gif); - f_gif_read(gif, &cw, 1); - f_gif_read(gif, &ch, 1); - f_gif_read(gif, &fg, 1); - f_gif_read(gif, &bg, 1); - sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - } - else { - /* Discard plain text metadata. */ - f_gif_seek(gif, 13, LV_FS_SEEK_CUR); - } - /* Discard plain text sub-blocks. */ - discard_sub_blocks(gif); -} - -static void -read_graphic_control_ext(gd_GIF * gif) -{ - uint8_t rdit; - - /* Discard block size (always 0x04). */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - f_gif_read(gif, &rdit, 1); - gif->gce.disposal = (rdit >> 2) & 3; - gif->gce.input = rdit & 2; - gif->gce.transparency = rdit & 1; - gif->gce.delay = read_num(gif); - f_gif_read(gif, &gif->gce.tindex, 1); - /* Skip block terminator. */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); -} - -static void -read_comment_ext(gd_GIF * gif) -{ - if(gif->comment) { - size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->comment(gif); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - } - /* Discard comment sub-blocks. */ - discard_sub_blocks(gif); -} - -static void -read_application_ext(gd_GIF * gif) -{ - char app_id[8]; - char app_auth_code[3]; - uint16_t loop_count; - - /* Discard block size (always 0x0B). */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - /* Application Identifier. */ - f_gif_read(gif, app_id, 8); - /* Application Authentication Code. */ - f_gif_read(gif, app_auth_code, 3); - if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { - /* Discard block size (0x03) and constant byte (0x01). */ - f_gif_seek(gif, 2, LV_FS_SEEK_CUR); - loop_count = read_num(gif); - if(gif->loop_count < 0) { - if(loop_count == 0) { - gif->loop_count = 0; - } - else { - gif->loop_count = loop_count + 1; - } - } - /* Skip block terminator. */ - f_gif_seek(gif, 1, LV_FS_SEEK_CUR); - } - else if(gif->application) { - size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - gif->application(gif, app_id, app_auth_code); - f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); - discard_sub_blocks(gif); - } - else { - discard_sub_blocks(gif); - } -} - -static void -read_ext(gd_GIF * gif) -{ - uint8_t label; - - f_gif_read(gif, &label, 1); - switch(label) { - case 0x01: - read_plain_text_ext(gif); - break; - case 0xF9: - read_graphic_control_ext(gif); - break; - case 0xFE: - read_comment_ext(gif); - break; - case 0xFF: - read_application_ext(gif); - break; - default: - LV_LOG_WARN("unknown extension: %02X\n", label); - } -} - -static uint16_t -get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) -{ - int bits_read; - int rpad; - int frag_size; - uint16_t key; - - key = 0; - for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { - rpad = (*shift + bits_read) % 8; - if (rpad == 0) { - /* Update byte. */ - if (*sub_len == 0) { - f_gif_read(gif, sub_len, 1); /* Must be nonzero! */ - if (*sub_len == 0) return 0x1000; - } - f_gif_read(gif, byte, 1); - (*sub_len)--; - } - frag_size = MIN(key_size - bits_read, 8 - rpad); - key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; - } - /* Clear extra bits to the left. */ - key &= (1 << key_size) - 1; - *shift = (*shift + key_size) % 8; - return key; -} - -#if LV_GIF_CACHE_DECODE_DATA -/* Decompress image pixels. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image_data(gd_GIF *gif, int interlace) -{ - uint8_t sub_len, shift, byte; - int ret = 0; - int key_size; - int y, pass, linesize; - uint8_t *ptr = NULL; - uint8_t *ptr_row_start = NULL; - uint8_t *ptr_base = NULL; - size_t start, end; - uint16_t key, clear_code, stop_code, curr_code; - int frm_off, frm_size,curr_size,top_slot,new_codes,slot; - /* The first value of the value sequence corresponding to key */ - int first_value; - int last_key; - uint8_t *sp = NULL; - uint8_t *p_stack = NULL; - uint8_t *p_suffix = NULL; - uint16_t *p_prefix = NULL; - - /* get initial key size and clear code, stop code */ - f_gif_read(gif, &byte, 1); - key_size = (int) byte; - clear_code = 1 << key_size; - stop_code = clear_code + 1; - key = 0; - - start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - discard_sub_blocks(gif); - end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - f_gif_seek(gif, start, LV_FS_SEEK_SET); - - linesize = gif->width; - ptr_base = &gif->frame[gif->fy * linesize + gif->fx]; - ptr_row_start = ptr_base; - ptr = ptr_row_start; - sub_len = shift = 0; - /* decoder */ - pass = 0; - y = 0; - p_stack = gif->lzw_cache; - p_suffix = gif->lzw_cache + LZW_TABLE_SIZE; - p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2); - frm_off = 0; - frm_size = gif->fw * gif->fh; - curr_size = key_size + 1; - top_slot = 1 << curr_size; - new_codes = clear_code + 2; - slot = new_codes; - first_value = -1; - last_key = -1; - sp = p_stack; - - while (frm_off < frm_size) { - /* copy data to frame buffer */ - while (sp > p_stack) { - if(frm_off >= frm_size){ - LV_LOG_WARN("LZW table token overflows the frame buffer"); - return -1; - } - *ptr++ = *(--sp); - frm_off += 1; - /* read one line */ - if ((ptr - ptr_row_start) == gif->fw) { - if (interlace) { - switch(pass) { - case 0: - case 1: - y += 8; - ptr_row_start += linesize * 8; - break; - case 2: - y += 4; - ptr_row_start += linesize * 4; - break; - case 3: - y += 2; - ptr_row_start += linesize * 2; - break; - default: - break; - } - while (y >= gif->fh) { - y = 4 >> pass; - ptr_row_start = ptr_base + linesize * y; - pass++; - } - } else { - ptr_row_start += linesize; - } - ptr = ptr_row_start; - } - } - - key = get_key(gif, curr_size, &sub_len, &shift, &byte); - - if (key == stop_code || key >= LZW_TABLE_SIZE) - break; - - if (key == clear_code) { - curr_size = key_size + 1; - slot = new_codes; - top_slot = 1 << curr_size; - first_value = last_key = -1; - sp = p_stack; - continue; - } - - curr_code = key; - /* - * If the current code is a code that will be added to the decoding - * dictionary, it is composed of the data list corresponding to the - * previous key and its first data. - * */ - if (curr_code == slot && first_value >= 0) { - *sp++ = first_value; - curr_code = last_key; - }else if(curr_code >= slot) - break; - - while (curr_code >= new_codes) { - *sp++ = p_suffix[curr_code]; - curr_code = p_prefix[curr_code]; - } - *sp++ = curr_code; - - /* Add code to decoding dictionary */ - if (slot < top_slot && last_key >= 0) { - p_suffix[slot] = curr_code; - p_prefix[slot++] = last_key; - } - first_value = curr_code; - last_key = key; - if (slot >= top_slot) { - if (curr_size < LZW_MAXBITS) { - top_slot <<= 1; - curr_size += 1; - } - } - } - - if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ - f_gif_seek(gif, end, LV_FS_SEEK_SET); - return ret; -} -#else -static Table * -new_table(int key_size) -{ - int key; - int init_bulk = MAX(1 << (key_size + 1), 0x100); - Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk); - if(table) { - table->bulk = init_bulk; - table->nentries = (1 << key_size) + 2; - table->entries = (Entry *) &table[1]; - for(key = 0; key < (1 << key_size); key++) - table->entries[key] = (Entry) { - 1, 0xFFF, key - }; - } - return table; -} - -/* Add table entry. Return value: - * 0 on success - * +1 if key size must be incremented after this addition - * -1 if could not realloc table */ -static int -add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix) -{ - Table * table = *tablep; - if(table->nentries == table->bulk) { - table->bulk *= 2; - table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); - if(!table) return -1; - table->entries = (Entry *) &table[1]; - *tablep = table; - } - table->entries[table->nentries] = (Entry) { - length, prefix, suffix - }; - table->nentries++; - if((table->nentries & (table->nentries - 1)) == 0) - return 1; - return 0; -} - -/* Compute output index of y-th input line, in frame of height h. */ -static int -interlaced_line_index(int h, int y) -{ - int p; /* number of lines in current pass */ - - p = (h - 1) / 8 + 1; - if(y < p) /* pass 1 */ - return y * 8; - y -= p; - p = (h - 5) / 8 + 1; - if(y < p) /* pass 2 */ - return y * 8 + 4; - y -= p; - p = (h - 3) / 4 + 1; - if(y < p) /* pass 3 */ - return y * 4 + 2; - y -= p; - /* pass 4 */ - return y * 2 + 1; -} - -/* Decompress image pixels. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image_data(gd_GIF * gif, int interlace) -{ - uint8_t sub_len, shift, byte; - int init_key_size, key_size, table_is_full = 0; - int frm_off, frm_size, str_len = 0, i, p, x, y; - uint16_t key, clear, stop; - int ret; - Table * table; - Entry entry = {0}; - size_t start, end; - - f_gif_read(gif, &byte, 1); - key_size = (int) byte; - start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - discard_sub_blocks(gif); - end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); - f_gif_seek(gif, start, LV_FS_SEEK_SET); - clear = 1 << key_size; - stop = clear + 1; - table = new_table(key_size); - key_size++; - init_key_size = key_size; - sub_len = shift = 0; - key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ - frm_off = 0; - ret = 0; - frm_size = gif->fw * gif->fh; - while(frm_off < frm_size) { - if(key == clear) { - key_size = init_key_size; - table->nentries = (1 << (key_size - 1)) + 2; - table_is_full = 0; - } - else if(!table_is_full) { - ret = add_entry(&table, str_len + 1, key, entry.suffix); - if(ret == -1) { - lv_free(table); - return -1; - } - if(table->nentries == 0x1000) { - ret = 0; - table_is_full = 1; - } - } - key = get_key(gif, key_size, &sub_len, &shift, &byte); - if(key == clear) continue; - if(key == stop || key == 0x1000) break; - if(ret == 1) key_size++; - entry = table->entries[key]; - str_len = entry.length; - if(frm_off + str_len > frm_size){ - LV_LOG_WARN("LZW table token overflows the frame buffer"); - lv_free(table); - return -1; - } - for(i = 0; i < str_len; i++) { - p = frm_off + entry.length - 1; - x = p % gif->fw; - y = p / gif->fw; - if(interlace) - y = interlaced_line_index((int) gif->fh, y); - gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; - if(entry.prefix == 0xFFF) - break; - else - entry = table->entries[entry.prefix]; - } - frm_off += str_len; - if(key < table->nentries - 1 && !table_is_full) - table->entries[table->nentries - 1].suffix = entry.suffix; - } - lv_free(table); - if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ - f_gif_seek(gif, end, LV_FS_SEEK_SET); - return 0; -} - -#endif - -/* Read image. - * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */ -static int -read_image(gd_GIF * gif) -{ - uint8_t fisrz; - int interlace; - - /* Image Descriptor. */ - gif->fx = read_num(gif); - gif->fy = read_num(gif); - gif->fw = read_num(gif); - gif->fh = read_num(gif); - if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){ - LV_LOG_WARN("Frame coordinates out of image bounds"); - return -1; - } - f_gif_read(gif, &fisrz, 1); - interlace = fisrz & 0x40; - /* Ignore Sort Flag. */ - /* Local Color Table? */ - if(fisrz & 0x80) { - /* Read LCT */ - gif->lct.size = 1 << ((fisrz & 0x07) + 1); - f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size); - gif->palette = &gif->lct; - } - else - gif->palette = &gif->gct; - /* Image Data. */ - return read_image_data(gif, interlace); -} - -static void -render_frame_rect(gd_GIF * gif, uint8_t * buffer) -{ - int i = gif->fy * gif->width + gif->fx; -#ifdef GIFDEC_RENDER_FRAME - GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width, - &gif->frame[i], gif->palette->colors, - gif->gce.transparency ? gif->gce.tindex : 0x100); -#else - int j, k; - uint8_t index, * color; - - for(j = 0; j < gif->fh; j++) { - for(k = 0; k < gif->fw; k++) { - index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; - color = &gif->palette->colors[index * 3]; - if(!gif->gce.transparency || index != gif->gce.tindex) { - buffer[(i + k) * 4 + 0] = *(color + 2); - buffer[(i + k) * 4 + 1] = *(color + 1); - buffer[(i + k) * 4 + 2] = *(color + 0); - buffer[(i + k) * 4 + 3] = 0xFF; - } - } - i += gif->width; - } -#endif -} - -static void -dispose(gd_GIF * gif) -{ - int i; - uint8_t * bgcolor; - switch(gif->gce.disposal) { - case 2: /* Restore to background color. */ - bgcolor = &gif->palette->colors[gif->bgindex * 3]; - - uint8_t opa = 0xff; - if(gif->gce.transparency) opa = 0x00; - - i = gif->fy * gif->width + gif->fx; -#ifdef GIFDEC_FILL_BG - GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa); -#else - int j, k; - for(j = 0; j < gif->fh; j++) { - for(k = 0; k < gif->fw; k++) { - gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2); - gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1); - gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0); - gif->canvas[(i + k) * 4 + 3] = opa; - } - i += gif->width; - } -#endif - break; - case 3: /* Restore to previous, i.e., don't update canvas.*/ - break; - default: - /* Add frame non-transparent pixels to canvas. */ - render_frame_rect(gif, gif->canvas); - } -} - -/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ -int -gd_get_frame(gd_GIF * gif) -{ - char sep; - - dispose(gif); - f_gif_read(gif, &sep, 1); - while(sep != ',') { - if(sep == ';') { - f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); - if(gif->loop_count == 1 || gif->loop_count < 0) { - return 0; - } - else if(gif->loop_count > 1) { - gif->loop_count--; - } - } - else if(sep == '!') - read_ext(gif); - else return -1; - f_gif_read(gif, &sep, 1); - } - if(read_image(gif) == -1) - return -1; - return 1; -} - -void -gd_render_frame(gd_GIF * gif, uint8_t * buffer) -{ - render_frame_rect(gif, buffer); -} - -void -gd_rewind(gd_GIF * gif) -{ - gif->loop_count = -1; - f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); -} - -void -gd_close_gif(gd_GIF * gif) -{ - f_gif_close(gif); - lv_free(gif); -} - -static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file) -{ - gif->f_rw_p = 0; - gif->data = NULL; - gif->is_file = is_file; - - if(is_file) { - lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD); - if(res != LV_FS_RES_OK) return false; - else return true; - } - else { - gif->data = path; - return true; - } -} - -static void f_gif_read(gd_GIF * gif, void * buf, size_t len) -{ - if(gif->is_file) { - lv_fs_read(&gif->fd, buf, len, NULL); - } - else { - memcpy(buf, &gif->data[gif->f_rw_p], len); - gif->f_rw_p += len; - } -} - -static int f_gif_seek(gd_GIF * gif, size_t pos, int k) -{ - if(gif->is_file) { - lv_fs_seek(&gif->fd, pos, k); - uint32_t x; - lv_fs_tell(&gif->fd, &x); - return x; - } - else { - if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos; - else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos; - return gif->f_rw_p; - } -} - -static void f_gif_close(gd_GIF * gif) -{ - if(gif->is_file) { - lv_fs_close(&gif->fd); - } -} - -#endif /*LV_USE_GIF*/ diff --git a/src/libs/gif/gifdec.h b/src/libs/gif/gifdec.h deleted file mode 100644 index 21ff210ab5..0000000000 --- a/src/libs/gif/gifdec.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef GIFDEC_H -#define GIFDEC_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include "../../misc/lv_fs.h" - -#if LV_USE_GIF -#include - -typedef struct _gd_Palette { - int size; - uint8_t colors[0x100 * 3]; -} gd_Palette; - -typedef struct _gd_GCE { - uint16_t delay; - uint8_t tindex; - uint8_t disposal; - int input; - int transparency; -} gd_GCE; - - - -typedef struct _gd_GIF { - lv_fs_file_t fd; - const char * data; - uint8_t is_file; - uint32_t f_rw_p; - int32_t anim_start; - uint16_t width, height; - uint16_t depth; - int32_t loop_count; - gd_GCE gce; - gd_Palette * palette; - gd_Palette lct, gct; - void (*plain_text)( - struct _gd_GIF * gif, uint16_t tx, uint16_t ty, - uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, - uint8_t fg, uint8_t bg - ); - void (*comment)(struct _gd_GIF * gif); - void (*application)(struct _gd_GIF * gif, char id[8], char auth[3]); - uint16_t fx, fy, fw, fh; - uint8_t bgindex; - uint8_t * canvas, * frame; - #if LV_GIF_CACHE_DECODE_DATA - uint8_t *lzw_cache; - #endif -} gd_GIF; - -gd_GIF * gd_open_gif_file(const char * fname); - -gd_GIF * gd_open_gif_data(const void * data); - -void gd_render_frame(gd_GIF * gif, uint8_t * buffer); - -int gd_get_frame(gd_GIF * gif); -void gd_rewind(gd_GIF * gif); -void gd_close_gif(gd_GIF * gif); - -#endif /*LV_USE_GIF*/ - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* GIFDEC_H */ diff --git a/src/libs/gif/gifdec_mve.h b/src/libs/gif/gifdec_mve.h deleted file mode 100644 index 6d83393497..0000000000 --- a/src/libs/gif/gifdec_mve.h +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @file gifdec_mve.h - * - */ - -#ifndef GIFDEC_MVE_H -#define GIFDEC_MVE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/********************* - * INCLUDES - *********************/ -#include -#include "../../misc/lv_color.h" - -/********************* - * DEFINES - *********************/ - -#define GIFDEC_FILL_BG(dst, w, h, stride, color, opa) \ - _gifdec_fill_bg_mve(dst, w, h, stride, color, opa) - -#define GIFDEC_RENDER_FRAME(dst, w, h, stride, frame, pattern, tindex) \ - _gifdec_render_frame_mve(dst, w, h, stride, frame, pattern, tindex) - -/********************** - * MACROS - **********************/ - -/********************** - * TYPEDEFS - **********************/ - -/********************** - * GLOBAL PROTOTYPES - **********************/ - -static inline void _gifdec_fill_bg_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * color, - uint8_t opa) -{ - lv_color32_t c = lv_color32_make(*(color + 0), *(color + 1), *(color + 2), opa); - uint32_t color_32 = *(uint32_t *)&c; - - __asm volatile( - ".p2align 2 \n" - "vdup.32 q0, %[src] \n" - "3: \n" - "mov r0, %[dst] \n" - - "wlstp.32 lr, %[w], 1f \n" - "2: \n" - - "vstrw.32 q0, [r0], #16 \n" - "letp lr, 2b \n" - "1: \n" - "add %[dst], %[iTargetStride] \n" - "subs %[h], #1 \n" - "bne 3b \n" - : [dst] "+r"(dst), - [h] "+r"(h) - : [src] "r"(color_32), - [w] "r"(w), - [iTargetStride] "r"(stride * sizeof(uint32_t)) - : "r0", "q0", "memory", "r14", "cc"); -} - -static inline void _gifdec_render_frame_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * frame, - uint8_t * pattern, uint16_t tindex) -{ - if(w == 0 || h == 0) { - return; - } - - __asm volatile( - "vmov.u16 q3, #255 \n" - "vshl.u16 q3, q3, #8 \n" /* left shift 8 for a*/ - - "mov r0, #2 \n" - "vidup.u16 q6, r0, #4 \n" /* [2, 6, 10, 14, 18, 22, 26, 30] */ - "mov r0, #0 \n" - "vidup.u16 q7, r0, #4 \n" /* [0, 4, 8, 12, 16, 20, 24, 28] */ - - "3: \n" - "mov r1, %[dst] \n" - "mov r2, %[frame] \n" - - "wlstp.16 lr, %[w], 1f \n" - "2: \n" - - "mov r0, #3 \n" - "vldrb.u16 q4, [r2], #8 \n" - "vmul.u16 q5, q4, r0 \n" - - "mov r0, #1 \n" - "vldrb.u16 q2, [%[pattern], q5] \n" /* load 8 pixel r*/ - - "vadd.u16 q5, q5, r0 \n" - "vldrb.u16 q1, [%[pattern], q5] \n" /* load 8 pixel g*/ - - "vadd.u16 q5, q5, r0 \n" - "vldrb.u16 q0, [%[pattern], q5] \n" /* load 8 pixel b*/ - - "vshl.u16 q1, q1, #8 \n" /* left shift 8 for g*/ - - "vorr.u16 q0, q0, q1 \n" /* make 8 pixel gb*/ - "vorr.u16 q1, q2, q3 \n" /* make 8 pixel ar*/ - - "vcmp.i16 ne, q4, %[tindex] \n" - "vpstt \n" - "vstrht.16 q0, [r1, q7] \n" - "vstrht.16 q1, [r1, q6] \n" - "add r1, r1, #32 \n" - - "letp lr, 2b \n" - - "1: \n" - "mov r0, %[stride], LSL #2 \n" - "add %[dst], r0 \n" - "add %[frame], %[stride] \n" - "subs %[h], #1 \n" - "bne 3b \n" - - : [dst] "+r"(dst), - [frame] "+r"(frame), - [h] "+r"(h) - : [pattern] "r"(pattern), - [w] "r"(w), - [stride] "r"(stride), - [tindex] "r"(tindex) - : "r0", "r1", "r2", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory", "r14", "cc"); -} - -#ifdef __cplusplus -} /*extern "C"*/ -#endif - -#endif /*GIFDEC_MVE_H*/ diff --git a/src/libs/gif/lv_gif.c b/src/libs/gif/lv_gif.c index 8b54db7612..8a93c661a1 100644 --- a/src/libs/gif/lv_gif.c +++ b/src/libs/gif/lv_gif.c @@ -6,13 +6,13 @@ /********************* * INCLUDES *********************/ -#include "lv_gif_private.h" +#include "lv_gif.h" #if LV_USE_GIF #include "../../misc/lv_timer_private.h" #include "../../misc/cache/lv_cache.h" #include "../../core/lv_obj_class_private.h" - -#include "gifdec.h" +#include "../../widgets/image/lv_image_private.h" +#include "AnimatedGIF/src/AnimatedGIF.h" /********************* * DEFINES @@ -23,11 +23,26 @@ * TYPEDEFS **********************/ +/* the type of the AnimatedGIF pallete type passed to `GIF_begin` */ +typedef unsigned char animatedgif_color_format_t; + +typedef struct { + lv_image_t img; + GIFIMAGE gif; + const void * src; + lv_color_format_t color_format; + lv_timer_t * timer; + lv_image_dsc_t imgdsc; + int32_t loop_count; + uint32_t is_open : 1; +} lv_gif_t; + /********************** * STATIC PROTOTYPES **********************/ static void lv_gif_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_gif_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void initialize(lv_gif_t * gifobj); static void next_frame_task_cb(lv_timer_t * t); /********************** @@ -59,63 +74,52 @@ lv_obj_t * lv_gif_create(lv_obj_t * parent) return obj; } -void lv_gif_set_src(lv_obj_t * obj, const void * src) +void lv_gif_set_color_format(lv_obj_t * obj, lv_color_format_t color_format) { lv_gif_t * gifobj = (lv_gif_t *) obj; - gd_GIF * gif = gifobj->gif; - /*Close previous gif if any*/ - if(gif != NULL) { - lv_image_cache_drop(lv_image_get_src(obj)); - - gd_close_gif(gif); - gifobj->gif = NULL; - gifobj->imgdsc.data = NULL; - } - - if(lv_image_src_get_type(src) == LV_IMAGE_SRC_VARIABLE) { - const lv_image_dsc_t * img_dsc = src; - gif = gd_open_gif_data(img_dsc->data); - } - else if(lv_image_src_get_type(src) == LV_IMAGE_SRC_FILE) { - gif = gd_open_gif_file(src); - } - if(gif == NULL) { - LV_LOG_WARN("Couldn't load the source"); + if(gifobj->color_format == color_format) { return; } - gifobj->gif = gif; - gifobj->imgdsc.data = gif->canvas; - gifobj->imgdsc.header.magic = LV_IMAGE_HEADER_MAGIC; - gifobj->imgdsc.header.flags = LV_IMAGE_FLAGS_MODIFIABLE; - gifobj->imgdsc.header.cf = LV_COLOR_FORMAT_ARGB8888; - gifobj->imgdsc.header.h = gif->height; - gifobj->imgdsc.header.w = gif->width; - gifobj->imgdsc.header.stride = gif->width * 4; - gifobj->imgdsc.data_size = gif->width * gif->height * 4; + switch(color_format) { + case LV_COLOR_FORMAT_RGB565: + case LV_COLOR_FORMAT_RGB565_SWAPPED: + case LV_COLOR_FORMAT_RGB888: + case LV_COLOR_FORMAT_ARGB8888: + break; + default: + LV_LOG_WARN("gif widget does not support this color format"); + return; + } - gifobj->last_call = lv_tick_get(); + gifobj->color_format = color_format; - lv_image_set_src(obj, &gifobj->imgdsc); + if(gifobj->src != NULL) { + initialize(gifobj); + } +} - lv_timer_resume(gifobj->timer); - lv_timer_reset(gifobj->timer); +void lv_gif_set_src(lv_obj_t * obj, const void * src) +{ + lv_gif_t * gifobj = (lv_gif_t *) obj; - next_frame_task_cb(gifobj->timer); + gifobj->src = src; + initialize(gifobj); } void lv_gif_restart(lv_obj_t * obj) { lv_gif_t * gifobj = (lv_gif_t *) obj; - if(gifobj->gif == NULL) { + if(!gifobj->is_open) { LV_LOG_WARN("Gif resource not loaded correctly"); return; } - gd_rewind(gifobj->gif); + GIF_reset(&gifobj->gif); + gifobj->loop_count = -1; /* match the behavior of the old library */ lv_timer_resume(gifobj->timer); lv_timer_reset(gifobj->timer); } @@ -130,7 +134,7 @@ void lv_gif_resume(lv_obj_t * obj) { lv_gif_t * gifobj = (lv_gif_t *) obj; - if(gifobj->gif == NULL) { + if(!gifobj->is_open) { LV_LOG_WARN("Gif resource not loaded correctly"); return; } @@ -142,30 +146,30 @@ bool lv_gif_is_loaded(lv_obj_t * obj) { lv_gif_t * gifobj = (lv_gif_t *) obj; - return (gifobj->gif != NULL); + return gifobj->is_open; } int32_t lv_gif_get_loop_count(lv_obj_t * obj) { lv_gif_t * gifobj = (lv_gif_t *) obj; - if(gifobj->gif == NULL) { + if(!gifobj->is_open) { return -1; } - return gifobj->gif->loop_count; + return gifobj->loop_count; } void lv_gif_set_loop_count(lv_obj_t * obj, int32_t count) { lv_gif_t * gifobj = (lv_gif_t *) obj; - if(gifobj->gif == NULL) { + if(!gifobj->is_open) { LV_LOG_WARN("Gif resource not loaded correctly"); return; } - gifobj->gif->loop_count = count; + gifobj->loop_count = count; } /********************** @@ -178,7 +182,8 @@ static void lv_gif_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_gif_t * gifobj = (lv_gif_t *) obj; - gifobj->gif = NULL; + gifobj->color_format = LV_COLOR_FORMAT_ARGB8888; + gifobj->is_open = 0; gifobj->timer = lv_timer_create(next_frame_task_cb, 10, obj); lv_timer_pause(gifobj->timer); } @@ -190,29 +195,121 @@ static void lv_gif_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) lv_image_cache_drop(lv_image_get_src(obj)); - if(gifobj->gif) - gd_close_gif(gifobj->gif); + if(gifobj->is_open) { + void * framebuffer = gifobj->gif.pFrameBuffer; + GIF_close(&gifobj->gif); + lv_free(framebuffer); + } lv_timer_delete(gifobj->timer); } +static void initialize(lv_gif_t * gifobj) +{ + GIFIMAGE * gif = &gifobj->gif; + + /*Close previous gif if any*/ + if(gifobj->is_open) { + lv_image_cache_drop(lv_image_get_src((lv_obj_t *) gifobj)); + + void * framebuffer = gif->pFrameBuffer; + GIF_close(gif); + lv_free(framebuffer); + gifobj->is_open = 0; + gifobj->imgdsc.data = NULL; + } + + animatedgif_color_format_t decoder_cf; + uint32_t pixel_size_bytes; + switch(gifobj->color_format) { + case LV_COLOR_FORMAT_RGB565: + decoder_cf = GIF_PALETTE_RGB565_LE; + pixel_size_bytes = 2; + break; + case LV_COLOR_FORMAT_RGB565_SWAPPED: + decoder_cf = GIF_PALETTE_RGB565_BE; + pixel_size_bytes = 2; + break; + case LV_COLOR_FORMAT_RGB888: + decoder_cf = GIF_PALETTE_RGB888; + pixel_size_bytes = 3; + break; + case LV_COLOR_FORMAT_ARGB8888: + decoder_cf = GIF_PALETTE_RGB8888; + pixel_size_bytes = 4; + break; + default: + return; + } + + GIF_begin(gif, decoder_cf); + + if(lv_image_src_get_type(gifobj->src) == LV_IMAGE_SRC_VARIABLE) { + const lv_image_dsc_t * img_dsc = gifobj->src; + gifobj->is_open = GIF_openRAM(gif, (uint8_t *) img_dsc->data, img_dsc->data_size, NULL); + } + else if(lv_image_src_get_type(gifobj->src) == LV_IMAGE_SRC_FILE) { + gifobj->is_open = GIF_openFile(gif, gifobj->src, NULL); + } + if(gifobj->is_open == 0) { + LV_LOG_WARN("Couldn't load the source"); + return; + } + + uint32_t width = GIF_getCanvasWidth(gif); + uint32_t height = GIF_getCanvasHeight(gif); + gif->pFrameBuffer = lv_malloc(width * height * (pixel_size_bytes + 1)); + gif->ucDrawType = GIF_DRAW_COOKED; + LV_ASSERT_MALLOC(gif->pFrameBuffer); + if(gif->pFrameBuffer == NULL) { + LV_LOG_WARN("Couldn't allocate a buffer for a GIF"); + GIF_close(gif); + gifobj->is_open = 0; + return; + } + + gifobj->imgdsc.data = gif->pFrameBuffer + width * height; + gifobj->imgdsc.header.magic = LV_IMAGE_HEADER_MAGIC; + gifobj->imgdsc.header.flags = LV_IMAGE_FLAGS_MODIFIABLE; + gifobj->imgdsc.header.cf = gifobj->color_format; + gifobj->imgdsc.header.h = height; + gifobj->imgdsc.header.w = width; + gifobj->imgdsc.header.stride = width * pixel_size_bytes; + gifobj->imgdsc.data_size = width * height * pixel_size_bytes; + + lv_image_set_src((lv_obj_t *) gifobj, &gifobj->imgdsc); + + gifobj->loop_count = GIF_getLoopCount(&gifobj->gif); + + lv_timer_resume(gifobj->timer); + lv_timer_reset(gifobj->timer); + + next_frame_task_cb(gifobj->timer); + +} + static void next_frame_task_cb(lv_timer_t * t) { lv_obj_t * obj = t->user_data; lv_gif_t * gifobj = (lv_gif_t *) obj; - uint32_t elaps = lv_tick_elaps(gifobj->last_call); - if(elaps < gifobj->gif->gce.delay * 10) return; - gifobj->last_call = lv_tick_get(); - - int has_next = gd_get_frame(gifobj->gif); - if(has_next == 0) { + int ms_delay_next; + int has_next = GIF_playFrame(&gifobj->gif, &ms_delay_next, gifobj); + if(has_next <= 0) { /*It was the last repeat*/ lv_result_t res = lv_obj_send_event(obj, LV_EVENT_READY, NULL); - lv_timer_pause(t); + if(gifobj->loop_count > 0) { + if(gifobj->loop_count == 1) { + lv_timer_pause(t); + } + else { + gifobj->loop_count--; + } + } if(res != LV_RESULT_OK) return; } - - gd_render_frame(gifobj->gif, (uint8_t *)gifobj->imgdsc.data); + else { + lv_timer_set_period(gifobj->timer, ms_delay_next); + } lv_image_cache_drop(lv_image_get_src(obj)); lv_obj_invalidate(obj); diff --git a/src/libs/gif/lv_gif.h b/src/libs/gif/lv_gif.h index cf1bee4794..ff72e1a59a 100644 --- a/src/libs/gif/lv_gif.h +++ b/src/libs/gif/lv_gif.h @@ -16,14 +16,11 @@ extern "C" { #include "../../lv_conf_internal.h" #include "../../misc/lv_types.h" -#include "../../draw/lv_draw_buf.h" -#include "../../widgets/image/lv_image.h" -#include "../../core/lv_obj_class.h" #include LV_STDBOOL_INCLUDE #include LV_STDINT_INCLUDE #if LV_USE_GIF -#include "gifdec.h" +#include "../../misc/lv_color.h" /********************* * DEFINES @@ -46,6 +43,15 @@ LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_gif_class; */ lv_obj_t * lv_gif_create(lv_obj_t * parent); +/** + * Set the color format of the internally allocated framebuffer that the gif + * will be decoded to. The default is LV_COLOR_FORMAT_ARGB8888. + * Call this before `lv_gif_set_src` to avoid reallocating the framebuffer. + * @param obj pointer to a gif object + * @param color_format the color format of the gif framebuffer + */ +void lv_gif_set_color_format(lv_obj_t * obj, lv_color_format_t color_format); + /** * Set the gif data to display on the object * @param obj pointer to a gif object diff --git a/src/libs/gif/lv_gif_private.h b/src/libs/gif/lv_gif_private.h deleted file mode 100644 index c88507f126..0000000000 --- a/src/libs/gif/lv_gif_private.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file lv_gif_private.h - * - */ - -#ifndef LV_GIF_PRIVATE_H -#define LV_GIF_PRIVATE_H - -#ifdef __cplusplus -extern "C" { -#endif - -/********************* - * INCLUDES - *********************/ - -#include "../../widgets/image/lv_image_private.h" -#include "lv_gif.h" - -#if LV_USE_GIF - -/********************* - * DEFINES - *********************/ - -/********************** - * TYPEDEFS - **********************/ - -/********************** - * TYPEDEFS - **********************/ - -struct _lv_gif_t { - lv_image_t img; - gd_GIF * gif; - lv_timer_t * timer; - lv_image_dsc_t imgdsc; - uint32_t last_call; -}; - - -/********************** - * GLOBAL PROTOTYPES - **********************/ - -/********************** - * MACROS - **********************/ - -#endif /* LV_USE_GIF */ - -#ifdef __cplusplus -} /*extern "C"*/ -#endif - -#endif /*LV_GIF_PRIVATE_H*/ diff --git a/src/misc/lv_types.h b/src/misc/lv_types.h index a7a03d0f70..b27b1b43ac 100644 --- a/src/misc/lv_types.h +++ b/src/misc/lv_types.h @@ -292,8 +292,6 @@ typedef struct _lv_file_explorer_t lv_file_explorer_t; typedef struct _lv_barcode_t lv_barcode_t; -typedef struct _lv_gif_t lv_gif_t; - typedef struct _lv_qrcode_t lv_qrcode_t; typedef struct _lv_freetype_outline_vector_t lv_freetype_outline_vector_t; diff --git a/tests/perf.py b/tests/perf.py index ed4adabcf9..2ceb9b3455 100755 --- a/tests/perf.py +++ b/tests/perf.py @@ -522,6 +522,7 @@ def run_tests( lvgl_src_dir = os.path.join(lvgl_test_dir, "..", "src") lv_conf_path = os.path.join(lvgl_test_dir, "src", lv_conf_name) lvgl_h_path = os.path.join(lvgl_test_dir, "..", "lvgl.h") + lvgl_private_h_path = os.path.join(lvgl_test_dir, "..", "lvgl_private.h") commands_ini_path = os.path.join(build_dir, "commands.ini") docker_image_name = perf_test_options[options_name]["image_name"] @@ -533,6 +534,7 @@ def run_tests( volume(lvgl_src_dir, so3_usr_lib("lvgl/src")), volume(lv_conf_path, so3_usr_lib("lv_conf.h")), volume(lvgl_h_path, so3_usr_lib("lvgl/lvgl.h")), + volume(lvgl_private_h_path, so3_usr_lib("lvgl/lvgl_private.h")), # We also need to add the current "lvgl.h" and mount it in the correct path # As there's a `#include "../../lvgl.h"` in the `unity_support.h` file volume(lvgl_h_path, "/so3/usr/lvgl.h"),