Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: AnimatedGIF library #2495

Merged
merged 6 commits into from
Feb 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Sming/Libraries/AnimatedGIF/AnimatedGIF
Submodule AnimatedGIF added at ed5802
50 changes: 50 additions & 0 deletions Sming/Libraries/AnimatedGIF/README.rst
Original file line number Diff line number Diff line change
@@ -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 <AnimatedGifTask.h>

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();
}
6 changes: 6 additions & 0 deletions Sming/Libraries/AnimatedGIF/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
COMPONENT_SUBMODULES := AnimatedGIF

COMPONENT_DEPENDS := Graphics

COMPONENT_SRCDIRS := src $(COMPONENT_SUBMODULES)/src
COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS)
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions Sming/Libraries/AnimatedGIF/samples/Basic_AnimatedGIF/README.rst
Original file line number Diff line number Diff line change
@@ -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 <https://www.animatedimages.org/>`__.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <SmingCore.h>
#include <Graphics/SampleConfig.h>
#include <AnimatedGifTask.h>

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();
}
Original file line number Diff line number Diff line change
@@ -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

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
138 changes: 138 additions & 0 deletions Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.cpp
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
* @author: Feb 2022 - Slavey Karadzhov <[email protected]>
*
****/

#include "AnimatedGifTask.h"

void draw(GIFDRAW* pDraw)
{
auto surface = static_cast<Graphics::Surface*>(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<uint8_t*>(const_cast<void*>(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);
}
45 changes: 45 additions & 0 deletions Sming/Libraries/AnimatedGIF/src/AnimatedGifTask.h
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*
* @author: Feb 2022 - Slavey Karadzhov <[email protected]>
*
****/

#pragma once

#include <Task.h>
#include <AnimatedGIF.h>
#include <Graphics/Surface.h>

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;
};