diff --git a/.gitmodules b/.gitmodules index 1156e2f79b..7607e1ac02 100644 --- a/.gitmodules +++ b/.gitmodules @@ -189,6 +189,10 @@ path = Sming/Libraries/Adafruit_VL53L0X url = https://github.com/adafruit/Adafruit_VL53L0X.git ignore = dirty +[submodule "Libraries.AnimatedGIF"] + path = Sming/Libraries/AnimatedGIF/AnimatedGIF + url = https://github.com/bitbank2/AnimatedGIF.git + ignore = dirty [submodule "Libraries.ArduinoFFT"] path = Sming/Libraries/ArduinoFFT url = https://github.com/kosme/arduinoFFT.git diff --git a/Sming/Libraries/AnimatedGIF/AnimatedGIF b/Sming/Libraries/AnimatedGIF/AnimatedGIF new file mode 160000 index 0000000000..ed58028768 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/AnimatedGIF @@ -0,0 +1 @@ +Subproject commit ed58028768a7a9afdbc80c9b438c808c5aaa30f8 diff --git a/Sming/Libraries/AnimatedGIF/README.rst b/Sming/Libraries/AnimatedGIF/README.rst new file mode 100644 index 0000000000..cf386681e7 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/README.rst @@ -0,0 +1,50 @@ +AnimatedGIF +=========== + +.. highlight:: c++ + +Introduction +------------ + +AnimatedGIF is an optimized library for playing animated GIFs on embedded devices. + +Features +-------- + +- Supports any MCU with at least 24K of RAM (Cortex-M0+ is the simplest I've tested). +- Optimized for speed; the main limitation will be how fast you can copy the pixels to the display. You can use SPI+DMA to help. +- GIF image data can come from memory (FLASH/RAM), SDCard or any media you provide. +- GIF files can be any length, (e.g. hundreds of megabytes) +- Simple C++ class and callback design allows you to easily add GIF support to any application. +- The C code doing the heavy lifting is completely portable and has no external dependencies. +- Does not use dynamic memory (malloc/free/new/delete), so it's easy to build it for a minimal bare metal system. + + +Using +----- + +1. Add ``COMPONENT_DEPENDS += AnimatedGIF`` to your application componenent.mk file. +2. Add these lines to your application:: + + #include + + namespace + { + AnimatedGifTask* task; + + // ... + + } // namespace + + void init() + { + // ... + + initDisplay(); + tft.setOrientation(Graphics::Orientation::deg270); + + auto surface = tft.createSurface(); + assert(surface != nullptr); + task = new AnimatedGifTask(*surface, gifData); + task->resume(); + } diff --git a/Sming/Libraries/AnimatedGIF/component.mk b/Sming/Libraries/AnimatedGIF/component.mk new file mode 100644 index 0000000000..d414a3106d --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/component.mk @@ -0,0 +1,6 @@ +COMPONENT_SUBMODULES := AnimatedGIF + +COMPONENT_DEPENDS := Graphics + +COMPONENT_SRCDIRS := src $(COMPONENT_SUBMODULES)/src +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/Makefile b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/README.rst b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/README.rst new file mode 100644 index 0000000000..cc5015a9b8 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/README.rst @@ -0,0 +1,12 @@ +Basic AnimatedGIF +================= + +Sample demonstating the usage of the optimized :library:`AnimatedGIF` library +together with :library:`Graphics` library. + +You should be able to see the following animated image: + +.. image:: files/frog.gif + :height: 235px + +Image source: `Animated Images Dot Org `__. \ No newline at end of file diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/app/.cs b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/app/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/app/application.cpp b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/app/application.cpp new file mode 100644 index 0000000000..baa14325ae --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/app/application.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +namespace +{ +AnimatedGifTask* task; + +IMPORT_FSTR(gifData, PROJECT_DIR "/files/frog.gif") + +} // namespace + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); + Serial.systemDebugOutput(true); + + initDisplay(); + tft.setOrientation(Graphics::Orientation::deg270); + + auto surface = tft.createSurface(); + assert(surface != nullptr); + task = new AnimatedGifTask(*surface, gifData); + task->resume(); +} diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/component.mk b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/component.mk new file mode 100644 index 0000000000..234d9bef96 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/component.mk @@ -0,0 +1,6 @@ +## If project doesn't require networking, saves RAM and build time +DISABLE_NETWORK := 1 +COMPONENT_DEPENDS := AnimatedGIF + +COMPONENT_DOCFILES := files/frog.gif + diff --git a/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/files/frog.gif b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/files/frog.gif new file mode 100644 index 0000000000..cad08a4ab4 Binary files /dev/null and b/Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/files/frog.gif differ diff --git a/Sming/Libraries/AnimatedGIF/src/.cs b/Sming/Libraries/AnimatedGIF/src/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.cpp b/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.cpp new file mode 100644 index 0000000000..36b63e4179 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.cpp @@ -0,0 +1,138 @@ +/**** + * AnimatedGifTask.cpp + * + * This library is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, version 3 or later. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this library. + * If not, see . + * + * @author: Feb 2022 - Slavey Karadzhov + * + ****/ + +#include "AnimatedGifTask.h" + +void draw(GIFDRAW* pDraw) +{ + auto surface = static_cast(pDraw->pUser); + if(surface == nullptr) { + return; + } + + const auto& tftSize = surface->getSize(); + const int DISPLAY_WIDTH = tftSize.w; + const int DISPLAY_HEIGHT = tftSize.h; + + auto pixelFormat = surface->getPixelFormat(); + auto bytesPerPixel = Graphics::getBytesPerPixel(pixelFormat); + + uint16_t usTemp[DISPLAY_WIDTH]; + Graphics::SharedBuffer buffer(bytesPerPixel * DISPLAY_WIDTH); + + int iWidth = pDraw->iWidth; + if(iWidth + pDraw->iX > DISPLAY_WIDTH) { + iWidth = DISPLAY_WIDTH - pDraw->iX; + } + const uint16_t* usPalette = pDraw->pPalette; + int y = pDraw->iY + pDraw->y; // current line + if(y >= DISPLAY_HEIGHT || pDraw->iX >= DISPLAY_WIDTH || iWidth < 1) { + return; + } + + auto s = pDraw->pPixels; + if(pDraw->ucDisposalMethod == 2) // restore to background color + { + for(int x = 0; x < iWidth; x++) { + if(s[x] == pDraw->ucTransparent) { + s[x] = pDraw->ucBackground; + } + } + pDraw->ucHasTransparency = 0; + } + + // Apply the new pixels to the main image + if(pDraw->ucHasTransparency) // if transparency used + { + uint8_t ucTransparent = pDraw->ucTransparent; + uint8_t* pEnd = s + iWidth; + int x = 0; + int iCount = 0; // count non-transparent pixels + while(x < iWidth) { + uint8_t c = ucTransparent - 1; + uint16_t* d = usTemp; + while(c != ucTransparent && s < pEnd) { + c = *s++; + if(c == ucTransparent) { + // done, stop: back up to treat it like transparent + s--; + } else { + // opaque + *d++ = __builtin_bswap16(usPalette[c]); + iCount++; + } + } // while looking for opaque pixels + if(iCount != 0) // any opaque pixels? + { + Graphics::convert(usTemp, Graphics::PixelFormat::RGB565, buffer.get(), pixelFormat, iCount); + Graphics::Rect r(pDraw->iX + x, y, iCount, 1); + surface->reset(); + surface->setAddrWindow(r); + surface->writeDataBuffer(buffer, 0, iCount * bytesPerPixel); + surface->present(); + x += iCount; + iCount = 0; + } + // no, look for a run of transparent pixels + c = ucTransparent; + while(c == ucTransparent && s < pEnd) { + c = *s++; + if(c == ucTransparent) { + iCount++; + } else { + s--; + } + } + if(iCount != 0) { + // skip these + x += iCount; + iCount = 0; + } + } + } else { + // No transparency + s = pDraw->pPixels; + // Translate the 8-bit pixels through the RGB565 palette (already byte reversed) + for(int x = 0; x < iWidth; x++) { + usTemp[x] = __builtin_bswap16(usPalette[*s++]); + } + Graphics::convert(usTemp, Graphics::PixelFormat::RGB565, buffer.get(), surface->getPixelFormat(), iWidth); + Graphics::Rect r(pDraw->iX, y, iWidth, 1); + surface->reset(); + surface->setAddrWindow(r); + surface->writeDataBuffer(buffer, 0, iWidth * bytesPerPixel); + surface->present(); + } +} + +AnimatedGifTask::AnimatedGifTask(Graphics::Surface& surface, const void* data, size_t length, bool inFlash) + : surface(surface) +{ + auto ptr = static_cast(const_cast(data)); + if(inFlash) { + gif.openFLASH(ptr, length, draw); + } else { + gif.open(ptr, length, draw); + } +} + +void AnimatedGifTask::loop() +{ + gif.playFrame(true, nullptr, &surface); + // To slow frames down or reduce load... + // sleep(100); +} diff --git a/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.h b/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.h new file mode 100644 index 0000000000..218ed72ea7 --- /dev/null +++ b/Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.h @@ -0,0 +1,45 @@ +/**** + * AnimatedGifTask.h + * + * This library is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, version 3 or later. + * + * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this library. + * If not, see . + * + * @author: Feb 2022 - Slavey Karadzhov + * + ****/ + +#pragma once + +#include +#include +#include + +class AnimatedGifTask : public Task +{ +public: + AnimatedGifTask(Graphics::Surface& surface, const void* data, size_t length, bool inFlash); + + AnimatedGifTask(Graphics::Surface& surface, const FSTR::ObjectBase& data) + : AnimatedGifTask(surface, data.data(), data.length(), true) + { + } + + ~AnimatedGifTask() + { + gif.close(); + } + +protected: + void loop() override; + +private: + AnimatedGIF gif; + Graphics::Surface& surface; +};