From bb34721dd4ccf8e1413f8949bb511307e81eb1a0 Mon Sep 17 00:00:00 2001 From: Martin Gerhardy Date: Sun, 4 Apr 2021 13:05:28 +0200 Subject: [PATCH] IMAGE: added GIFDecoder using libgif --- .github/workflows/ci.yml | 8 +- .travis.yml | 1 + configure | 35 +++++ devtools/create_project/create_project.cpp | 1 + image/gif.cpp | 145 +++++++++++++++++++++ image/gif.h | 71 ++++++++++ image/module.mk | 1 + test/.gitignore | 1 + test/image/gif.h | 41 ++++++ test/module.mk | 4 +- 10 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 image/gif.cpp create mode 100644 image/gif.h create mode 100644 test/.gitignore create mode 100644 test/image/gif.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 201f1ab8762..d851cd44ea8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,21 +15,21 @@ jobs: arch: x86 # fribidi is disabled due to timeouts when installing the package configFlags: --enable-faad --enable-mpeg2 --enable-discord --disable-fribidi - vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype glew libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib' + vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype glew libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib giflib' useNasm: 'true' - platform: x64 arch: x64 triplet: x64-windows # fribidi is disabled due to timeouts when installing the package configFlags: --enable-faad --enable-mpeg2 --enable-discord --disable-fribidi - vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype glew libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib' + vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype glew libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib giflib' - platform: arm64 arch: arm64 triplet: arm64-windows # fribidi is disabled due to https://github.com/microsoft/vcpkg/issues/11248 [fribidi] Fribidi doesn't cross-compile on x86-64 to target arm/arm64 # Note that fribidi is also disabled on arm64 in devtools/create_project/msvc.cpp configFlags: --enable-faad --enable-mpeg2 --enable-discord --disable-fribidi --disable-opengl - vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib' + vcpkgPackages: 'curl discord-rpc faad2 fluidsynth freetype libflac libjpeg-turbo libmad libmpeg2 libogg libpng libtheora libvorbis sdl2 sdl2-net zlib giflib' env: CONFIGURATION: Release PLATFORM: ${{ matrix.platform }} @@ -103,7 +103,7 @@ jobs: - platform: macosx buildFlags: -scheme ScummVM-macOS configFlags: --disable-nasm --enable-faad --enable-mpeg2 - brewPackages: a52dec faad2 flac fluid-synth freetype fribidi glew mad libmpeg2 libogg libpng libvorbis sdl2 sdl2_net theora + brewPackages: a52dec faad2 flac fluid-synth freetype fribidi glew mad libmpeg2 libogg libpng libvorbis sdl2 sdl2_net theora giflib - platform: ios7 buildFlags: -scheme ScummVM-iOS CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO configFlags: --disable-nasm --disable-opengl --disable-theora --disable-taskbar --disable-tts --disable-fribidi diff --git a/.travis.yml b/.travis.yml index e54265f378f..b6d7026908c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,6 +32,7 @@ addons: - libsndio-dev - libreadline-dev - libglew-dev + - libgif-dev branches: only: diff --git a/configure b/configure index 95ccbe92db8..8bc644ad081 100755 --- a/configure +++ b/configure @@ -154,6 +154,7 @@ _sparkle=auto _osxdockplugin=auto _jpeg=auto _png=auto +_gif=auto _theoradec=auto _faad=auto _fluidsynth=auto @@ -272,6 +273,7 @@ add_feature jpeg "JPEG" "_jpeg" add_feature mpeg2 "mpeg2" "_mpeg2" add_feature opengl_game_shaders "OpenGL with shaders" "_opengl_game_shaders" add_feature png "PNG" "_png" +add_feature png "GIF" "_gif" add_feature theoradec "libtheoradec" "_theoradec" add_feature tinygl "TinyGL" "_tinygl" add_feature vorbis "Vorbis file support" "_vorbis _tremor" @@ -1111,6 +1113,8 @@ for ac_option in $@; do --enable-jpeg) _jpeg=yes ;; --disable-png) _png=no ;; --enable-png) _png=yes ;; + --disable-gif) _gif=no ;; + --enable-gif) _gif=yes ;; --disable-theoradec) _theoradec=no ;; --enable-theoradec) _theoradec=yes ;; --disable-faad) _faad=no ;; @@ -4477,6 +4481,37 @@ fi define_in_config_if_yes "$_png" 'USE_PNG' echo "$_png" +# +# Check for GIF +# +echocheck "GIF >= 5.0.0" +if test "$_pkg_config" = "yes" && $_pkgconfig --exists libgif; then + append_var GIF_LIBS "`$_pkgconfig --libs libgif`" + append_var GIF_CFLAGS "`$_pkgconfig --cflags libgif`" +else + append_var GIF_LIBS "-lgif" +fi +if test "$_gif" = auto ; then + _gif=no + cat > $TMPC << EOF +#include +int main(void) { +#if GIFLIB_MAJOR >= 5 +#else + syntax error +#endif + return 0; +} +EOF + cc_check $GIF_CFLAGS $GIF_LIBS && _gif=yes +fi +if test "$_gif" = yes ; then + append_var LIBS "$GIF_LIBS" + append_var INCLUDES "$GIF_CFLAGS" +fi +define_in_config_if_yes "$_gif" 'USE_GIF' +echo "$_gif" + # # Check for Theora Decoder # diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp index d597233ca9d..b30be619c17 100644 --- a/devtools/create_project/create_project.cpp +++ b/devtools/create_project/create_project.cpp @@ -1043,6 +1043,7 @@ const Feature s_features[] = { { "tremor", "USE_TREMOR", true, false, "Tremor support" }, { "flac", "USE_FLAC", true, true, "FLAC support" }, { "png", "USE_PNG", true, true, "libpng support" }, + { "gif", "USE_GIF", true, false, "libgif support" }, { "faad", "USE_FAAD", true, false, "AAC support" }, { "mpeg2", "USE_MPEG2", true, false, "MPEG-2 support" }, { "theora", "USE_THEORADEC", true, true, "Theora decoding support" }, diff --git a/image/gif.cpp b/image/gif.cpp new file mode 100644 index 00000000000..0b1cec87d99 --- /dev/null +++ b/image/gif.cpp @@ -0,0 +1,145 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "image/gif.h" + +#include "common/array.h" +#include "common/stream.h" +#include "common/textconsole.h" +#include "common/util.h" +#include "graphics/surface.h" +#include "graphics/pixelformat.h" + +#ifdef USE_GIF +#include +#endif + +namespace Image { + +GIFDecoder::GIFDecoder() : _outputSurface(0), _palette(0), _colorCount(0) { +} + +GIFDecoder::~GIFDecoder() { + destroy(); +} + +#ifdef USE_GIF +static int gifReadFromStream(GifFileType *gif, GifByteType *bytes, int size) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)gif->UserData; + return stream->read(bytes, size); +} +#endif + +bool GIFDecoder::loadStream(Common::SeekableReadStream &stream) { + destroy(); + +#ifdef USE_GIF + int error = 0; + GifFileType *gif = DGifOpen(&stream, gifReadFromStream, &error); + if (!gif) { + warning("GIF open failed with error %s", GifErrorString(error)); + return false; + } + + const int errcode = DGifSlurp(gif); + if (errcode != GIF_OK) { + warning("GIF failed to load"); + DGifCloseFile(gif, 0); + return false; + } + + if (gif->ImageCount <= 0) { + warning("GIF doesn't contain valid image data"); + DGifCloseFile(gif, 0); + return false; + } + + if (gif->ImageCount > 1) { + warning("GIF contains more than one frame - only loading the first one"); + } + + const SavedImage *gifImage = gif->SavedImages; + + const int width = gif->SWidth; + const int height = gif->SHeight; + + const ColorMapObject *colorMap = gif->SColorMap; + _transparentColor = NO_TRANSPARENT_COLOR; + for (int i = 0; i < gif->ExtensionBlockCount; ++i) { + const ExtensionBlock &eb = gif->ExtensionBlocks[i]; + GraphicsControlBlock gcb; + DGifExtensionToGCB(eb.ByteCount, eb.Bytes, &gcb); + if (gcb.TransparentColor != NO_TRANSPARENT_COLOR) { + _transparentColor = gcb.TransparentColor; + break; + } + } + + _colorCount = colorMap->ColorCount; + _outputSurface = new Graphics::Surface(); + _palette = new uint8[_colorCount * 3]; + + const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + for (int i = 0; i < _colorCount; ++i) { + _palette[(i * 3) + 0] = colorMap->Colors[i].Red; + _palette[(i * 3) + 1] = colorMap->Colors[i].Green; + _palette[(i * 3) + 2] = colorMap->Colors[i].Blue; + } + + // TODO: support transparency + + _outputSurface->create(width, height, format); + const uint8 *in = (const uint8 *)gifImage->RasterBits; + uint8 *pixelPtr = (uint8 *)_outputSurface->getBasePtr(0, 0); + if (gif->Image.Interlace) { + const int interlacedOffset[] = {0, 4, 2, 1}; + const int interlacedJumps[] = {8, 8, 4, 2}; + for (int i = 0; i < 4; ++i) { + for (int row = interlacedOffset[i]; row < height; row += interlacedJumps[i]) { + memcpy(pixelPtr + width * row, in, width); + in += width; + } + } + } else { + memcpy(pixelPtr, in, width * height); + } + + DGifCloseFile(gif, 0); + return true; +#else + return false; +#endif +} + +void GIFDecoder::destroy() { + if (_outputSurface) { + _outputSurface->free(); + delete _outputSurface; + _outputSurface = 0; + } + if (_palette) { + delete[] _palette; + _palette = 0; + } +} + +} // End of namespace Image diff --git a/image/gif.h b/image/gif.h new file mode 100644 index 00000000000..9a88acf460a --- /dev/null +++ b/image/gif.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program 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; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef IMAGE_GIF_H +#define IMAGE_GIF_H + +#include "image/image_decoder.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Graphics { +struct Surface; +} + +namespace Image { + +/** + * @defgroup image_gif GIF decoder + * @ingroup image + * + * @brief Decoder for images encoded as Graphics Interchange Format (GIF). + * + * This decoder has a dependency on the libgif library. + * + * Used in engines: + * - TwinE + * @{ + */ +class GIFDecoder : public ImageDecoder { +public: + GIFDecoder(); + ~GIFDecoder(); + + bool loadStream(Common::SeekableReadStream &stream) override; + void destroy() override; + const byte *getPalette() const override { return _palette; } + uint16 getPaletteColorCount() const override { return _colorCount; } + const Graphics::Surface *getSurface() const override { return _outputSurface; } + int getTransparentColor() const { return _transparentColor; } +private: + Graphics::Surface *_outputSurface; + uint8 *_palette; + uint16 _colorCount; + int _transparentColor; +}; + +/** @} */ +} // End of namespace Image + +#endif diff --git a/image/module.mk b/image/module.mk index 23424e5cede..c3a4535b299 100644 --- a/image/module.mk +++ b/image/module.mk @@ -3,6 +3,7 @@ MODULE := image MODULE_OBJS := \ bmp.o \ cel_3do.o \ + gif.o \ iff.o \ jpeg.o \ pcx.o \ diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 00000000000..44a6cb3e886 --- /dev/null +++ b/test/.gitignore @@ -0,0 +1 @@ +/engine-data diff --git a/test/image/gif.h b/test/image/gif.h new file mode 100644 index 00000000000..f371d0f1386 --- /dev/null +++ b/test/image/gif.h @@ -0,0 +1,41 @@ +#include + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include "common/memstream.h" +#include "image/gif.h" +#include "graphics/surface.h" + +class GIFDecoderTestSuite : public CxxTest::TestSuite { +public: + void test_load_gif_2x2() { +#ifdef USE_GIF + const uint8 gifBuf[63] = { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x02, 0x00, 0x02, 0x00, 0xa1, + 0x03, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x14, 0x82, 0x31, + 0xff, 0xff, 0xff, 0x21, 0xfe, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, + 0x50, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x00, + 0x00, 0x02, 0x03, 0x54, 0x06, 0x05, 0x00, 0x3b + }; + + Image::GIFDecoder decoder; + Common::MemoryReadStream stream(gifBuf, sizeof(gifBuf)); + const bool status = decoder.loadStream(stream); + TS_ASSERT(status); + if (!status) { + return; + } + const Graphics::Surface *surface = decoder.getSurface(); + TS_ASSERT(surface != 0); + if (surface == 0) { + return; + } + TS_ASSERT_EQUALS(surface->w, 2); + TS_ASSERT_EQUALS(surface->h, 2); + TS_ASSERT_EQUALS(surface->format.bytesPerPixel, 1); +#endif + } +}; diff --git a/test/module.mk b/test/module.mk index e0fa9d3e612..db5bad8461b 100644 --- a/test/module.mk +++ b/test/module.mk @@ -5,7 +5,7 @@ # ###################################################################### -TESTS := $(srcdir)/test/common/*.h $(srcdir)/test/audio/*.h $(srcdir)/test/math/*.h +TESTS := $(srcdir)/test/common/*.h $(srcdir)/test/audio/*.h $(srcdir)/test/math/*.h $(srcdir)/test/image/*.h TEST_LIBS := ifdef POSIX @@ -27,7 +27,7 @@ TEST_LIBS += test/null_osystem.o \ backends/modular-backend.o endif -TEST_LIBS += audio/libaudio.a math/libmath.a common/libcommon.a +TEST_LIBS += audio/libaudio.a math/libmath.a common/libcommon.a image/libimage.a graphics/libgraphics.a ifeq ($(ENABLE_WINTERMUTE), STATIC_PLUGIN) TESTS += $(srcdir)/test/engines/wintermute/*.h