diff --git a/main.cpp b/main.cpp index 28746c2..f66ebab 100644 --- a/main.cpp +++ b/main.cpp @@ -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; @@ -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) { @@ -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 @@ -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 @@ -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; diff --git a/meta/meta.rc b/meta/meta.rc index 927fdf2..301bcb0 100644 --- a/meta/meta.rc +++ b/meta/meta.rc @@ -1,6 +1,6 @@ 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 @@ -8,12 +8,12 @@ BEGIN 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" diff --git a/subclasses/IVAnimatedImage.cpp b/subclasses/IVAnimatedImage.cpp index 41315f3..84ff9e4 100644 --- a/subclasses/IVAnimatedImage.cpp +++ b/subclasses/IVAnimatedImage.cpp @@ -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; @@ -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) { @@ -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; @@ -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. */ diff --git a/subclasses/IVAnimatedImage.hpp b/subclasses/IVAnimatedImage.hpp index b960ca6..1bfd42d 100644 --- a/subclasses/IVAnimatedImage.hpp +++ b/subclasses/IVAnimatedImage.hpp @@ -4,6 +4,8 @@ NICK WILSON 2020 */ +#include + #include "IVUtil.hpp" //utilities #include "IVImage.hpp" //base class @@ -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() {} @@ -30,9 +48,9 @@ class IVAnimatedImage : public IVImage{ ~IVAnimatedImage(); - void printDetails(); + void prepare(); - int next(); + void set_status(IVImage::state s); }; #endif diff --git a/subclasses/IVImage.hpp b/subclasses/IVImage.hpp index 5945e95..dc89249 100644 --- a/subclasses/IVImage.hpp +++ b/subclasses/IVImage.hpp @@ -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 diff --git a/subclasses/IVStaticImage.cpp b/subclasses/IVStaticImage.cpp index 7a0d3f0..e750fe7 100644 --- a/subclasses/IVStaticImage.cpp +++ b/subclasses/IVStaticImage.cpp @@ -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 */ diff --git a/subclasses/IVStaticImage.hpp b/subclasses/IVStaticImage.hpp index 300495d..d21ebe0 100644 --- a/subclasses/IVStaticImage.hpp +++ b/subclasses/IVStaticImage.hpp @@ -19,8 +19,6 @@ class IVStaticImage : public IVImage { IVStaticImage(SDL_Renderer* renderer, std::filesystem::path path); ~IVStaticImage(); - - void printDetails(); }; #endif