Skip to content

Commit

Permalink
Animated GIF support
Browse files Browse the repository at this point in the history
  • Loading branch information
fuelsoft committed May 18, 2020
1 parent 8d37c4c commit 6ad8ab7
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 57 deletions.
18 changes: 12 additions & 6 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ namespace IVC {
const std::string BUILD_DATE = __DATE__;
const std::string BUILD_TIME = __TIME__;
std::string VERSION_ABOUT;
const std::string IVVERSION = "0.2.3";
const std::string IVVERSION = "0.3.0";

/* WINDOW MANAGEMENT */
const int WIN_DEFAULT_W = 1600;
Expand Down Expand Up @@ -209,10 +209,13 @@ int loadTextureFromFile(SDL_Renderer* renderer, std::filesystem::path filePath)
//load animated image
IVG::IMAGE_CURRENT.reset(new IVAnimatedImage(renderer, filePath));
}
else {
else if (filetype > -1) {
//load static image
IVG::IMAGE_CURRENT.reset(new IVStaticImage(renderer, filePath));
}
else {
return 1;
}
}
catch (IVUTIL::IVEXCEPT except) {
switch (except) {
Expand Down Expand Up @@ -298,7 +301,6 @@ int main(int argc, char* argv[]) {

SDL_DisplayMode displayMode;
SDL_Event sdlEvent;
// SDL_Texture* imageTexture = nullptr;

char EXE_PATH[MAX_PATH];
GetModuleFileNameA(NULL, EXE_PATH, MAX_PATH); //Windows system call to get executable's path
Expand Down Expand Up @@ -439,9 +441,8 @@ int main(int argc, char* argv[]) {
break;
case SDL_KEYDOWN:
switch (sdlEvent.key.keysym.sym) {
case SDLK_t:
IVG::IMAGE_CURRENT->next();
redraw = true;
case SDLK_SPACE: //if gif, toggle pause/play
if (IVG::IMAGE_CURRENT->animated) IVG::IMAGE_CURRENT->set_status(IVImage::STATE_TOGGLE);
break;
case SDLK_EQUALS: //equals with plus secondary
case SDLK_KP_PLUS: //or keypad plus, zoom in
Expand Down Expand Up @@ -590,6 +591,11 @@ int main(int argc, char* argv[]) {
}
}

if (IVG::IMAGE_CURRENT->animated && IVG::IMAGE_CURRENT->ready) {
IVG::IMAGE_CURRENT->prepare();
redraw = true;
}

// If something happened that requires a redraw, process it
if (redraw) {
redraw = false;
Expand Down
8 changes: 4 additions & 4 deletions meta/meta.rc
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
1 VERSIONINFO
FILEVERSION 0,2,3,0
PRODUCTVERSION 0,2,3,0
FILEVERSION 0,3,0,0
PRODUCTVERSION 0,3,0,0
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904E4"
BEGIN
VALUE "CompanyName", "Fuelsoft Development"
VALUE "FileDescription", "Image Viewer"
VALUE "FileVersion", "0.2.3"
VALUE "FileVersion", "0.3.0"
VALUE "InternalName", "viewer"
VALUE "LegalCopyright", "Fuelsoft Development"
VALUE "OriginalFilename", "Viewer.exe"
VALUE "ProductName", "Image Viewer"
VALUE "ProductVersion", "0.2.3"
VALUE "ProductVersion", "0.3.0"
END
END
BLOCK "VarFileInfo"
Expand Down
147 changes: 114 additions & 33 deletions subclasses/IVAnimatedImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ NICK WILSON

/* PRIVATE */

/* Routine to convert giflib colour palette to SDL colour palette */
/**
* setPalette - Routine to convert giflib colour palette to SDL colour palette
* colorMap > giflib ColorMapObject containing colour count and palette
* surface > SDL Surface to apply converted palette to
*/
void IVAnimatedImage::setPalette(ColorMapObject* colorMap, SDL_Surface* surface) {
// start gif colour at the start
uint8_t* gc = (uint8_t*) colorMap->Colors;
Expand All @@ -24,10 +28,60 @@ void IVAnimatedImage::setPalette(ColorMapObject* colorMap, SDL_Surface* surface)
}
}

/**
* setIndex - Set frame index record to provided index with boundaries checked
* index > New frame index
*/
void IVAnimatedImage::setIndex(uint16_t index) {
this->frame_index = index;
this->frame_index %= this->frame_count;
}

/**
* getDelay - Load extension chunks related to provided image index and search for a delay value
* index > Target frame index
*/
uint16_t IVAnimatedImage::getDelay(uint16_t index) {
index %= frame_count;
// iterate and look for graphics extension with timing data
for (int i = 0; i < gif_data->SavedImages[index].ExtensionBlockCount; i++) {
if (gif_data->SavedImages[index].ExtensionBlocks[i].Function == GRAPHICS_EXT_FUNC_CODE) { // found it
this->delay_val = (uint16_t) (gif_data->SavedImages[index].ExtensionBlocks[i].Bytes[1]); // see [1]
break;
}
}
return this->delay_val;
}

/**
* getDelay - Shortcut for getDelay() defaulting to current frame index
*/
uint16_t IVAnimatedImage::getDelay() {
return getDelay(frame_index);
}

/**
* animate - The function is called as a thread and will advance the index and manage timing for the animation automatically.
* If the status is set to paused, it will wait before continuing to animate.
*/
void IVAnimatedImage::animate() {
while(!this->quit) {
while (!this->play) {
if (this->quit) return; //make it possible to break out for quitting while paused
SDL_Delay(30);
}
setIndex(this->frame_index + 1); //advance by a frame
this->ready = true; //mark frame as ready to be prepare()'d
getDelay();
SDL_Delay(this->delay_val * 10); //wait (gif resolution is only 1/100 of a sec, mult. by 10 for millis)
}
return;
}

/* PUBLIC */

IVAnimatedImage::IVAnimatedImage(SDL_Renderer* renderer, std::filesystem::path path) {
this->gif_data = DGifOpenFileName(path.string().c_str(), nullptr);
gif_data = DGifOpenFileName(path.string().c_str(), nullptr);

// Will be null if image metadata could not be read
if (!gif_data) {
Expand All @@ -39,11 +93,12 @@ IVAnimatedImage::IVAnimatedImage(SDL_Renderer* renderer, std::filesystem::path p
throw IVUTIL::EXCEPT_IMG_LOAD_FAIL;
}

this->animated = (gif_data->ImageCount > 1);
this->renderer = renderer;
this->w = gif_data->SWidth;
this->h = gif_data->SHeight;

this->frame_count = gif_data->ImageCount;
this->animated = (gif_data->ImageCount > 1);
this->renderer = renderer;

// NOTE: IN BITS
this->depth = gif_data->SColorMap->BitsPerPixel;
Expand All @@ -56,59 +111,85 @@ IVAnimatedImage::IVAnimatedImage(SDL_Renderer* renderer, std::filesystem::path p
}

// Create surface with existing gif data. 8 bit depth will trigger automatic creation of palette to be filled next
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void *) gif_data->SavedImages[0].RasterBits, this->w, this->h, depth, this->w * (depth >> 3), 0, 0, 0, 0);
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void *) gif_data->SavedImages[0].RasterBits, this->w, this->h, this->depth, this->w * (this->depth >> 3), 0, 0, 0, 0);

// If local colour palette defined
if (gif_data->SavedImages[0].ImageDesc.ColorMap) {
// Convert from local giflib colour to SDL colur and populate palette
setPalette(gif_data->SavedImages[0].ImageDesc.ColorMap, surface);
}
// If global colour palette defined
else if (gif_data->SColorMap) {
// Convert from global giflib colour to SDL colur and populate palette
if (gif_data->SColorMap) { // if global colour palette defined
// convert from global giflib colour to SDL colour and populate palette
setPalette(gif_data->SColorMap, surface);
}
else if (gif_data->SavedImages[this->frame_index].ImageDesc.ColorMap) { // local colour palette
// convert from local giflib colour to SDL colour and populate palette
setPalette(gif_data->SavedImages[this->frame_index].ImageDesc.ColorMap, surface);
}

this->texture = SDL_CreateTextureFromSurface(this->renderer, surface);

SDL_FreeSurface(surface);

if (this->animated) animationThread = std::thread(&IVAnimatedImage::animate, this);
}

IVAnimatedImage::~IVAnimatedImage() {
if (this->animated) {
this->quit = true;
animationThread.join();
}
DGifCloseFile(this->gif_data, nullptr);
SDL_DestroyTexture(this->texture);
}

void IVAnimatedImage::printDetails() {
std::cout << this->w << " x " << this->h << std::endl;
std::cout << this->frame_count << " frames" << std::endl;
}

int IVAnimatedImage::next() {
this->frame_index++;
this->frame_index %= this->frame_count;

/**
* prepare - Create surface from current index, apply palette and create texture.
* This should be called as infrequently as possible - static images don't need refreshing.
*/
void IVAnimatedImage::prepare() {
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void *) this->gif_data->SavedImages[frame_index].RasterBits, this->w, this->h, this->depth, this->w * (this->depth >> 3), 0, 0, 0, 0);

// If local colour palette defined
if (gif_data->SavedImages[frame_index].ImageDesc.ColorMap) {
// Convert from local giflib colour to SDL colur and populate palette
setPalette(gif_data->SavedImages[frame_index].ImageDesc.ColorMap, surface);
}
// If global colour palette defined
else if (gif_data->SColorMap) {
// Convert from global giflib colour to SDL colur and populate palette
if (gif_data->SColorMap) { // if global colour palette defined
// convert from global giflib colour to SDL colour and populate palette
setPalette(gif_data->SColorMap, surface);
}
else if (gif_data->SavedImages[this->frame_index].ImageDesc.ColorMap) { // local colour palette
// convert from local giflib colour to SDL colour and populate palette
setPalette(gif_data->SavedImages[this->frame_index].ImageDesc.ColorMap, surface);
}

SDL_DestroyTexture(this->texture); //delete old texture

SDL_DestroyTexture(this->texture);
this->texture = SDL_CreateTextureFromSurface(this->renderer, surface);

SDL_FreeSurface(surface);

return 100; //TODO: Time in millis to next frame from ExtensionBlock
this->ready = false; // mark current frame as already requested
}

/**
* set_status - Set image state - play or pause (or toggle to swap)
* s > New IVImage::state state
*/
void IVAnimatedImage::set_status(state s) {
switch (s) {
case STATE_PLAY:
this->play = true;
break;
case STATE_PAUSE:
this->play = false;
break;
case STATE_TOGGLE:
this->play = !this->play;
break;
default:
break;
}
}

/*
TODO: File type check
TODO:
- Transparency palette index support
*/

/*
[1]: Byte index is 1 because giflib cuts off the first 3 bytes of extension chunk.
Payload is then [Packed Byte], [Upper Delay], [Lower Delay], [Transparency Index].
Cast to 16 bit int then catches both bytes of the delay value.
*/
28 changes: 23 additions & 5 deletions subclasses/IVAnimatedImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ NICK WILSON
2020
*/

#include <thread>

#include "IVUtil.hpp" //utilities
#include "IVImage.hpp" //base class

Expand All @@ -15,13 +17,29 @@ NICK WILSON
class IVAnimatedImage : public IVImage{
private:
GifFileType* gif_data;
int frame_index = 0;
int depth = 0;
uint8_t depth = 0;
uint16_t frame_index = 0;
uint16_t delay_val = 0;

bool play = true;
bool quit = false;

std::thread animationThread;

void setPalette(ColorMapObject* colorMap, SDL_Surface* surface);

void setIndex(uint16_t index);

void next();

uint16_t getDelay(uint16_t index);

uint16_t getDelay();

void animate();

public:
int frame_count = 0;
uint16_t frame_count = 0;
bool playable;

IVAnimatedImage() {}
Expand All @@ -30,9 +48,9 @@ class IVAnimatedImage : public IVImage{

~IVAnimatedImage();

void printDetails();
void prepare();

int next();
void set_status(IVImage::state s);
};

#endif
15 changes: 12 additions & 3 deletions subclasses/IVImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ class IVImage {
SDL_Renderer* renderer = nullptr;

public:
enum state {
STATE_PLAY,
STATE_PAUSE,
STATE_TOGGLE,
};

uint16_t w, h;
bool animated;
bool animated = false;
bool ready = false;
SDL_Texture* texture = nullptr;

virtual void printDetails() {}
virtual void prepare() {};

virtual void set_status(state s) {};

virtual int next() { return 0; }
virtual ~IVImage() {};
};

#endif
4 changes: 0 additions & 4 deletions subclasses/IVStaticImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ IVStaticImage::~IVStaticImage() {
SDL_DestroyTexture(this->texture);
}

void IVStaticImage::printDetails() {
std::cout << this->w << " x " << this->h << std::endl;
}

/*
TODO: File type check
*/
2 changes: 0 additions & 2 deletions subclasses/IVStaticImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ class IVStaticImage : public IVImage {
IVStaticImage(SDL_Renderer* renderer, std::filesystem::path path);

~IVStaticImage();

void printDetails();
};

#endif

0 comments on commit 6ad8ab7

Please sign in to comment.