Skip to content

Commit

Permalink
Add animated GIF support
Browse files Browse the repository at this point in the history
  • Loading branch information
mogemimi committed Oct 10, 2016
1 parent 4af2f77 commit aef6d7a
Show file tree
Hide file tree
Showing 7 changed files with 417 additions and 0 deletions.
8 changes: 8 additions & 0 deletions build/pomdog.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@
'../src/Utility/ScopeGuard.hpp',
'../src/Utility/StringHelper.cpp',
],
'pomdog_library_experimental_sources': [
'../include/Pomdog/Experimental/Image/GifImage.hpp',
'../include/Pomdog/Experimental/Image/GifImageLoader.hpp',
'../include/Pomdog/Experimental/Image/Image.hpp',
'../src/Experimental/Image/GifImageLoader.cpp',
'../src/Experimental/Image/Image.cpp',
],
'pomdog_library_opengl4_sources': [
'../src/RenderSystem.GL4/BlendStateGL4.cpp',
'../src/RenderSystem.GL4/BlendStateGL4.hpp',
Expand Down Expand Up @@ -537,6 +544,7 @@
],
'sources': [
'<@(pomdog_library_core_sources)',
'<@(pomdog_library_experimental_sources)',
'../include/Pomdog/Pomdog.hpp',
],
'msbuild_settings': {
Expand Down
50 changes: 50 additions & 0 deletions build/pomdog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
A997F7091CAEFC4900926392 /* Texture2DMetal.mm in Sources */ = {isa = PBXBuildFile; fileRef = A997F6F51CAEFC4800926392 /* Texture2DMetal.mm */; };
A9A822731DAB7BF60091497F /* libgiflib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9A822701DAB7B310091497F /* libgiflib.a */; };
A9A822761DAB7C060091497F /* libgiflib.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A9A822701DAB7B310091497F /* libgiflib.a */; };
A9A8228D1DAB7FB70091497F /* GifImageLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A8228B1DAB7FB70091497F /* GifImageLoader.cpp */; };
A9A8228E1DAB7FB70091497F /* GifImageLoader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A8228B1DAB7FB70091497F /* GifImageLoader.cpp */; };
A9A8228F1DAB7FB70091497F /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A8228C1DAB7FB70091497F /* Image.cpp */; };
A9A822901DAB7FB70091497F /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A8228C1DAB7FB70091497F /* Image.cpp */; };
A9D18E8D1CA427270011A6CE /* MetalCompiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9D18E8C1CA427270011A6CE /* MetalCompiler.cpp */; };
A9D18E8E1CA427270011A6CE /* MetalCompiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9D18E8C1CA427270011A6CE /* MetalCompiler.cpp */; };
A9D641861C342F59006E14E0 /* libpng.a in Frameworks */ = {isa = PBXBuildFile; fileRef = BFBE06E0DEEBFD77A7D8BBB0 /* libpng.a */; };
Expand Down Expand Up @@ -568,6 +572,11 @@
A997F6F41CAEFC4800926392 /* Texture2DMetal.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Texture2DMetal.hpp; sourceTree = "<group>"; };
A997F6F51CAEFC4800926392 /* Texture2DMetal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Texture2DMetal.mm; sourceTree = "<group>"; };
A9A8226B1DAB7B310091497F /* giflib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = giflib.xcodeproj; path = dependencies/giflib.xcodeproj; sourceTree = "<group>"; };
A9A822841DAB7F990091497F /* GifImage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = GifImage.hpp; sourceTree = "<group>"; };
A9A822851DAB7F990091497F /* GifImageLoader.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = GifImageLoader.hpp; sourceTree = "<group>"; };
A9A822861DAB7F990091497F /* Image.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Image.hpp; sourceTree = "<group>"; };
A9A8228B1DAB7FB70091497F /* GifImageLoader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GifImageLoader.cpp; sourceTree = "<group>"; };
A9A8228C1DAB7FB70091497F /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = "<group>"; };
A9AE680DD0E3FCEA11AF8F48 /* FloatingPointVector4.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FloatingPointVector4.cpp; sourceTree = "<group>"; };
A9D18E8B1CA426FA0011A6CE /* MetalCompiler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MetalCompiler.hpp; sourceTree = "<group>"; };
A9D18E8C1CA427270011A6CE /* MetalCompiler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MetalCompiler.cpp; sourceTree = "<group>"; };
Expand Down Expand Up @@ -956,6 +965,7 @@
30C4D948BA52EE418FB78842 /* Audio */,
A94ACC581C287C8100AFCC36 /* Basic */,
0A858AD16F086BA4A3B2C499 /* Content */,
A9A822811DAB7F650091497F /* Experimental */,
C75D42DC2FCD94A2C16953DC /* Graphics */,
A4530A8C4CEA403D2E7D71ED /* Input */,
34C6330F2DF6F100237B8429 /* InputSystem */,
Expand Down Expand Up @@ -1298,6 +1308,41 @@
name = Products;
sourceTree = "<group>";
};
A9A8227E1DAB7EFF0091497F /* Experimental */ = {
isa = PBXGroup;
children = (
A9A8227F1DAB7F2B0091497F /* Image */,
);
path = Experimental;
sourceTree = "<group>";
};
A9A8227F1DAB7F2B0091497F /* Image */ = {
isa = PBXGroup;
children = (
A9A822841DAB7F990091497F /* GifImage.hpp */,
A9A822851DAB7F990091497F /* GifImageLoader.hpp */,
A9A822861DAB7F990091497F /* Image.hpp */,
);
path = Image;
sourceTree = "<group>";
};
A9A822811DAB7F650091497F /* Experimental */ = {
isa = PBXGroup;
children = (
A9A822821DAB7F6B0091497F /* Image */,
);
path = Experimental;
sourceTree = "<group>";
};
A9A822821DAB7F6B0091497F /* Image */ = {
isa = PBXGroup;
children = (
A9A8228B1DAB7FB70091497F /* GifImageLoader.cpp */,
A9A8228C1DAB7FB70091497F /* Image.cpp */,
);
path = Image;
sourceTree = "<group>";
};
AAF4E64073F0BBFB4C0B0C43 /* Signals */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1482,6 +1527,7 @@
3713F7C1D9D48DC0DC2D209A /* Audio */,
70831D9D2DD4F8C879C18406 /* Basic */,
67207A51EBA7F1D865FF9800 /* Content */,
A9A8227E1DAB7EFF0091497F /* Experimental */,
D90112D019DEC9CB7B5D46EC /* Graphics */,
68CED7E1D2108A668492B923 /* Input */,
0ADCD7A9C3ACD3EAEBA70750 /* Logging */,
Expand Down Expand Up @@ -1673,9 +1719,11 @@
0E2F4D65374135E36FE1DD6D /* GameClock.cpp in Sources */,
E7BC935B83D4E596AE1FDDC6 /* Timer.cpp in Sources */,
A997F6FC1CAEFC4800926392 /* GraphicsDeviceMetal.mm in Sources */,
A9A8228D1DAB7FB70091497F /* GifImageLoader.cpp in Sources */,
4EE6EBE03E9AD9C3BEF652F2 /* AudioClip.cpp in Sources */,
A997F7041CAEFC4900926392 /* SamplerStateMetal.mm in Sources */,
B7E9DD7713CD0E5323210193 /* AudioEngine.cpp in Sources */,
A9A8228F1DAB7FB70091497F /* Image.cpp in Sources */,
A997F7001CAEFC4800926392 /* PipelineStateMetal.mm in Sources */,
B25BC69FDA812635B36D2CAC /* SoundEffect.cpp in Sources */,
5F8F4C5C0179D8889EC7B4D2 /* AssetDictionary.cpp in Sources */,
Expand Down Expand Up @@ -1787,9 +1835,11 @@
219021114F3127B13F14D421 /* GameClock.cpp in Sources */,
25FE89B66D5561E80E0EE6FE /* Timer.cpp in Sources */,
A997F6FD1CAEFC4800926392 /* GraphicsDeviceMetal.mm in Sources */,
A9A8228E1DAB7FB70091497F /* GifImageLoader.cpp in Sources */,
8F708C4B18D750ED37EFCF6B /* AudioClip.cpp in Sources */,
A997F7051CAEFC4900926392 /* SamplerStateMetal.mm in Sources */,
3EB59864165741842C8DD788 /* AudioEngine.cpp in Sources */,
A9A822901DAB7FB70091497F /* Image.cpp in Sources */,
A997F7011CAEFC4800926392 /* PipelineStateMetal.mm in Sources */,
615FEBD97D48A4D57BDA54C4 /* SoundEffect.cpp in Sources */,
E4D0DB6D0CBC0C51449C182B /* AssetDictionary.cpp in Sources */,
Expand Down
27 changes: 27 additions & 0 deletions include/Pomdog/Experimental/Image/GifImage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2013-2016 mogemimi. Distributed under the MIT license.

#pragma once

#include "Pomdog/Basic/Export.hpp"
#include "Pomdog/Experimental/Image/Image.hpp"
#include <memory>
#include <vector>
#include <chrono>

namespace Pomdog {

using GifDuration = std::chrono::duration<long long, std::ratio<1LL, 100LL>>;

class POMDOG_EXPORT GifImageFrame final {
public:
std::shared_ptr<Image> Image;
GifDuration Delay;
};

class POMDOG_EXPORT GifImage final {
public:
std::vector<GifImageFrame> Frames;
int LoopCount;
};

} // namespace Pomdog
18 changes: 18 additions & 0 deletions include/Pomdog/Experimental/Image/GifImageLoader.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) 2013-2016 mogemimi. Distributed under the MIT license.

#pragma once

#include "Pomdog/Basic/Export.hpp"
#include "Pomdog/Experimental/Image/GifImage.hpp"
#include "Pomdog/Utility/Optional.hpp"
#include <vector>
#include <string>

namespace Pomdog {

class POMDOG_EXPORT GifLoader final {
public:
static Optional<GifImage> Open(const std::string& filePath);
};

} // namespace Pomdog
46 changes: 46 additions & 0 deletions include/Pomdog/Experimental/Image/Image.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2013-2016 mogemimi. Distributed under the MIT license.

#pragma once

#include "Pomdog/Basic/Export.hpp"
#include "Pomdog/Math/Color.hpp"
#include <vector>

namespace Pomdog {

class POMDOG_EXPORT Image final {
public:
Image(int width, int height);

Image() = delete;
Image(const Image&) = delete;
Image(Image &&);

Image & operator=(const Image&) = delete;
Image & operator=(Image &&);

int GetWidth() const noexcept;

int GetHeight() const noexcept;

const Color* GetData() const;

void SetData(const Color* pixelData);

void SetData(std::vector<Color> && pixelData);

const Color& GetPixel(int x, int y) const;

void SetPixel(int x, int y, const Color& color);

void Fill(const Color& color);

void PremultiplyAlpha();

private:
std::vector<Color> data;
int width;
int height;
};

} // namespace Pomdog
167 changes: 167 additions & 0 deletions src/Experimental/Image/GifImageLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) 2013-2016 mogemimi. Distributed under the MIT license.

#include "Pomdog/Experimental/Image/GifImageLoader.hpp"
#include "Pomdog/Utility/Assert.hpp"
#include <gif_lib.h>
#include <functional>

namespace Pomdog {
namespace {

void DumpExtensions(
int extensionBlockCount,
ExtensionBlock* extensionBlocks,
const std::function<void(int)>& findLoopCount)
{
const auto extensionBlockEnd = extensionBlocks + extensionBlockCount;

for (auto extensionBlock = extensionBlocks; extensionBlock != extensionBlockEnd; ++extensionBlock) {
POMDOG_ASSERT(extensionBlock < extensionBlockEnd);
if (extensionBlock->Function == APPLICATION_EXT_FUNC_CODE &&
(std::memcmp(extensionBlock->Bytes, "NETSCAPE2.0", 11) == 0) &&
(extensionBlock->Bytes[10] == 0x01)) {

++extensionBlock;
GifByteType x1 = extensionBlock->Bytes[1];
GifByteType x2 = extensionBlock->Bytes[2];
int loopCount = (static_cast<int>(x2) << 8) | static_cast<int>(x1);
findLoopCount(loopCount);
}
}
}

} // unnamed namespace

Optional<GifImage> GifLoader::Open(const std::string& filePath)
{
if (filePath.empty()) {
// error
return NullOpt;
}

int gifError = 0;
auto gifFileIn = std::shared_ptr<GifFileType>(
DGifOpenFileName(filePath.c_str(), &gifError),
[](GifFileType* p) {
if (p != nullptr) {
DGifCloseFile(p, nullptr);
}
});

if (gifError != 0) {
//throw CannotOpenGifFileException{};
return NullOpt;
}

if (DGifSlurp(gifFileIn.get()) == GIF_ERROR) {
//throw CannotOpenGifFileException{};
return NullOpt;
}

GifImage result;
result.LoopCount = 0;

DumpExtensions(
gifFileIn->ExtensionBlockCount,
gifFileIn->ExtensionBlocks,
[&](int loopCountIn) {
result.LoopCount = loopCountIn;
});

std::shared_ptr<Pomdog::Image const> prevImage;

for (int index = 0; index < gifFileIn->ImageCount; ++index) {
auto& saveImage = gifFileIn->SavedImages[index];

const int imageNumber = ((index + 1) % gifFileIn->ImageCount);
GraphicsControlBlock gcb;
bool hasGCB = false;
if (DGifSavedExtensionToGCB(gifFileIn.get(), imageNumber, &gcb) == GIF_OK) {
hasGCB = true;
}
else {
hasGCB = false;
}

const auto colorMap = saveImage.ImageDesc.ColorMap
? saveImage.ImageDesc.ColorMap
: gifFileIn->SColorMap;

auto img = std::make_shared<Pomdog::Image>(gifFileIn->SWidth, gifFileIn->SHeight);

if (hasGCB && gcb.DisposalMode == DISPOSE_BACKGROUND) {
auto fillColor = [&]() -> Color {
if (hasGCB && (gcb.TransparentColor != NO_TRANSPARENT_COLOR)) {
GifColorType& color = colorMap->Colors[gcb.TransparentColor];
return Color{color.Red, color.Green, color.Blue, 0};
}
return Color{255, 255, 255, 255};
}();
img->Fill(fillColor);
}
else if (hasGCB && gcb.DisposalMode == DISPOSE_DO_NOT) {
if (index > 0) {
// Fill color
POMDOG_ASSERT(prevImage);
POMDOG_ASSERT(img->GetWidth() == prevImage->GetWidth());
POMDOG_ASSERT(img->GetHeight() == prevImage->GetHeight());
img->SetData(prevImage->GetData());
}
}

const int width = saveImage.ImageDesc.Width;
const int height = saveImage.ImageDesc.Height;

for (int x = 0; x < width; ++x) {
for (int y = 0; y < height; ++y) {
int colorIndex = saveImage.RasterBits[x + width * y];
assert(colorIndex < colorMap->ColorCount);
GifColorType& colorFromMap =
colorMap->Colors[colorIndex];

bool isTransparent = [&]{
if (!hasGCB) {
return false;
}
return gcb.TransparentColor == colorIndex;
}();

GifByteType alpha = isTransparent ? 0x00 : 0xFF;

auto rgba = Color{
colorFromMap.Red,
colorFromMap.Green,
colorFromMap.Blue,
alpha
};

if (!isTransparent) {
img->SetPixel(
saveImage.ImageDesc.Left + x,
saveImage.ImageDesc.Top + y,
rgba);
}
}
}

DumpExtensions(
gifFileIn->ExtensionBlockCount,
gifFileIn->ExtensionBlocks,
[&](int loopCountIn) {
result.LoopCount = loopCountIn;
});

const GifDuration sourceDelay{hasGCB ? gcb.DelayTime : 0};

GifImageFrame frame;
frame.Image = img;
frame.Delay = sourceDelay;
result.Frames.push_back(std::move(frame));

prevImage = img;
}

return result;
}

} // namespace Pomdog
Loading

0 comments on commit aef6d7a

Please sign in to comment.