aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHarry Jeffery <harry@exec64.co.uk>2015-11-11 15:22:41 +0000
committerHarry Jeffery <harry@exec64.co.uk>2015-11-11 15:22:41 +0000
commit8b527949b0a74a37079210a6fc8fabbd576432eb (patch)
treecfa44059678e1ec1b13a700b325249856437e182 /src
parentd1f0e07abd9bbb72ca825fe8e2fe9fa12cc6746f (diff)
downloadimv-8b527949b0a74a37079210a6fc8fabbd576432eb.tar.gz
Update makefile to use a build directory
Diffstat (limited to 'src')
-rw-r--r--src/image.c245
-rw-r--r--src/image.h49
-rw-r--r--src/main.c284
-rw-r--r--src/navigator.c188
-rw-r--r--src/navigator.h45
-rw-r--r--src/texture.c146
-rw-r--r--src/texture.h46
-rw-r--r--src/viewport.c119
-rw-r--r--src/viewport.h48
9 files changed, 1170 insertions, 0 deletions
diff --git a/src/image.c b/src/image.c
new file mode 100644
index 0000000..8516bf5
--- /dev/null
+++ b/src/image.c
@@ -0,0 +1,245 @@
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include "image.h"
+
+void imv_init_image(struct imv_image *img)
+{
+ img->mbmp = NULL;
+ img->cur_bmp = NULL;
+ img->width = 0;
+ img->height = 0;
+ img->cur_frame = 0;
+ img->next_frame = 0;
+ img->num_frames = 0;
+ img->frame_time = 0;
+ img->changed = 0;
+}
+
+void imv_destroy_image(struct imv_image *img)
+{
+ if(img->cur_bmp) {
+ FreeImage_Unload(img->cur_bmp);
+ img->cur_bmp = NULL;
+ }
+ if(img->mbmp) {
+ FreeImage_CloseMultiBitmap(img->mbmp, 0);
+ img->mbmp = NULL;
+ }
+}
+
+int imv_can_load_image(const char* path)
+{
+ if(FreeImage_GetFileType(path, 0) == FIF_UNKNOWN) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+int imv_image_load(struct imv_image *img, const char* path)
+{
+ if(img->mbmp) {
+ FreeImage_CloseMultiBitmap(img->mbmp, 0);
+ img->mbmp = NULL;
+ }
+
+ if(img->cur_bmp) {
+ FreeImage_Unload(img->cur_bmp);
+ img->cur_bmp = NULL;
+ }
+
+
+ FREE_IMAGE_FORMAT fmt = FreeImage_GetFileType(path,0);
+
+ if(fmt == FIF_UNKNOWN) {
+ return 1;
+ }
+
+ img->num_frames = 0;
+ img->cur_frame = 0;
+ img->next_frame = 0;
+ img->frame_time = 0;
+
+ if(fmt == FIF_GIF) {
+ img->mbmp = FreeImage_OpenMultiBitmap(FIF_GIF, path,
+ /* don't create file */ 0,
+ /* read only */ 1,
+ /* keep in memory */ 1,
+ /* flags */ GIF_LOAD256);
+ if(!img->mbmp) {
+ return 1;
+ }
+ img->num_frames = FreeImage_GetPageCount(img->mbmp);
+
+ /* get the dimensions from the first frame */
+ FIBITMAP *frame = FreeImage_LockPage(img->mbmp, 0);
+ img->width = FreeImage_GetWidth(frame);
+ img->height = FreeImage_GetHeight(frame);
+ FreeImage_UnlockPage(img->mbmp, frame, 0);
+
+ /* load a frame */
+ imv_image_load_next_frame(img);
+ } else {
+ FIBITMAP *image = FreeImage_Load(fmt, path, 0);
+ if(!image) {
+ return 1;
+ }
+ FreeImage_FlipVertical(image);
+ img->cur_bmp = FreeImage_ConvertTo32Bits(image);
+ img->width = FreeImage_GetWidth(img->cur_bmp);
+ img->height = FreeImage_GetHeight(img->cur_bmp);
+ }
+
+ img->changed = 1;
+ return 0;
+}
+
+void imv_image_load_next_frame(struct imv_image *img)
+{
+ if(!imv_image_is_animated(img)) {
+ return;
+ }
+
+ FITAG *tag = NULL;
+ char disposal_method = 0;
+ int frame_time = 0;
+ short top = 0;
+ short left = 0;
+
+ img->cur_frame = img->next_frame;
+ img->next_frame = (img->cur_frame + 1) % img->num_frames;
+ FIBITMAP *frame = FreeImage_LockPage(img->mbmp, img->cur_frame);
+ FIBITMAP *frame32 = FreeImage_ConvertTo32Bits(frame);
+ FreeImage_FlipVertical(frame32);
+
+ /* First frame is always going to use the raw frame */
+ if(img->cur_frame > 0) {
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "DisposalMethod", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ disposal_method = *(char*)FreeImage_GetTagValue(tag);
+ }
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameLeft", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ left = *(short*)FreeImage_GetTagValue(tag);
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTop", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ top = *(short*)FreeImage_GetTagValue(tag);
+ }
+
+ FreeImage_GetMetadata(FIMD_ANIMATION, frame, "FrameTime", &tag);
+ if(FreeImage_GetTagValue(tag)) {
+ frame_time = *(int*)FreeImage_GetTagValue(tag);
+ }
+
+ img->frame_time += frame_time * 0.001;
+
+ FreeImage_UnlockPage(img->mbmp, frame, 0);
+
+ /* If this frame is inset, we need to expand it for compositing */
+ if(left != 0 || top != 0) {
+ RGBQUAD color = {0,0,0,0};
+ FIBITMAP *expanded = FreeImage_EnlargeCanvas(frame32,
+ left,
+ img->height - FreeImage_GetHeight(frame32) - top,
+ img->width - FreeImage_GetWidth(frame32) - left,
+ top,
+ &color,
+ 0);
+ FreeImage_Unload(frame32);
+ frame32 = expanded;
+ }
+
+ /* If the frame is still too small, enlarge it to fit */
+ if(img->width != (int)FreeImage_GetWidth(frame32) ||
+ img->height != (int)FreeImage_GetHeight(frame32)) {
+ RGBQUAD color = {0,0,0,0};
+ FIBITMAP *expanded = FreeImage_EnlargeCanvas(frame32,
+ 0,
+ img->height - FreeImage_GetHeight(frame32),
+ img->width - FreeImage_GetWidth(frame32),
+ 0,
+ &color,
+ 0);
+ FreeImage_Unload(frame32);
+ frame32 = expanded;
+ }
+
+ switch(disposal_method) {
+ case 0: /* nothing specified, just use the raw frame */
+ if(img->cur_bmp) {
+ FreeImage_Unload(img->cur_bmp);
+ }
+ img->cur_bmp = frame32;
+ break;
+ case 1: /* composite over previous frame */
+ if(img->cur_bmp && img->cur_frame > 0) {
+ FIBITMAP *bg_frame = FreeImage_ConvertTo24Bits(img->cur_bmp);
+ FreeImage_Unload(img->cur_bmp);
+ FIBITMAP *comp = FreeImage_Composite(frame32, 1, NULL, bg_frame);
+ FreeImage_Unload(bg_frame);
+ FreeImage_Unload(frame32);
+ img->cur_bmp = comp;
+ } else {
+ /* No previous frame, just render directly */
+ if(img->cur_bmp) {
+ FreeImage_Unload(img->cur_bmp);
+ }
+ img->cur_bmp = frame32;
+ }
+ break;
+ case 2: /* TODO - set to background, composite over that */
+ break;
+ case 3: /* TODO - restore to previous content */
+ break;
+ }
+ img->changed = 1;
+}
+
+int imv_image_is_animated(struct imv_image *img)
+{
+ return img->num_frames > 1;
+}
+
+void imv_image_play(struct imv_image *img, double time)
+{
+ if(!imv_image_is_animated(img)) {
+ return;
+ }
+
+ img->frame_time -= time;
+ if(img->frame_time < 0) {
+ img->frame_time = 0;
+ imv_image_load_next_frame(img);
+ }
+}
+
+int imv_image_has_changed(struct imv_image *img)
+{
+ if(img->changed) {
+ img->changed = 0;
+ return 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/image.h b/src/image.h
new file mode 100644
index 0000000..d36dab4
--- /dev/null
+++ b/src/image.h
@@ -0,0 +1,49 @@
+#ifndef IMV_IMAGE_H
+#define IMV_IMAGE_H
+
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include <FreeImage.h>
+
+struct imv_image {
+ FIMULTIBITMAP *mbmp;
+ FIBITMAP *cur_bmp;
+ int width;
+ int height;
+ int cur_frame;
+ int next_frame;
+ int num_frames;
+ int changed;
+ double frame_time;
+};
+
+void imv_init_image(struct imv_image *img);
+void imv_destroy_image(struct imv_image *img);
+
+int imv_can_load_image(const char* path);
+int imv_image_load(struct imv_image *img, const char* path);
+void imv_image_load_next_frame(struct imv_image *img);
+
+int imv_image_is_animated(struct imv_image *img);
+void imv_image_play(struct imv_image *img, double time);
+
+int imv_image_has_changed(struct imv_image *img);
+
+#endif
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..2efeec3
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,284 @@
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <SDL2/SDL.h>
+#include <FreeImage.h>
+
+#include "image.h"
+#include "texture.h"
+#include "navigator.h"
+#include "viewport.h"
+
+struct {
+ int autoscale;
+ int fullscreen;
+ int stdin;
+ int center;
+ int recursive;
+} g_options = {0,0,0,0,0};
+
+void print_usage(const char* name)
+{
+ fprintf(stdout,
+ "Usage: %s [-ifsh] [images...]\n"
+ "\n"
+ "Flags:\n"
+ " -i: Read paths from stdin. One path per line.\n"
+ " -r: Recursively search input paths.\n"
+ " -f: Start in fullscreen mode\n"
+ " -s: Auto scale images to fit window\n"
+ " -c: Center images in the window\n"
+ " -h: Print this help\n"
+ "\n"
+ "Mouse:\n"
+ " Click+Drag to Pan\n"
+ " MouseWheel to Zoom\n"
+ "\n"
+ "Hotkeys:\n"
+ " 'q': Quit\n"
+ " '[',LArrow: Previous image\n"
+ " ']',RArrow: Next image\n"
+ " 'i','+': Zoom in\n"
+ " 'o','=': Zoom out\n"
+ " 'h': Pan left\n"
+ " 'j': Pan down\n"
+ " 'k': Pan up\n"
+ " 'l': Pan right\n"
+ " 'r': Reset view\n"
+ " 'c': Center view\n"
+ " 's': Scale image to fit window\n"
+ " 'x': Close current image\n"
+ " 'f': Toggle fullscreen\n"
+ " ' ': Toggle gif playback\n"
+ " '.': Step a frame of gif playback\n"
+ ,name);
+}
+
+void parse_arg(const char* name, const char* arg)
+{
+ for(const char *o = arg; *o != 0; ++o) {
+ switch(*o) {
+ case 'f': g_options.fullscreen = 1; break;
+ case 's': g_options.autoscale = 1; break;
+ case 'c': g_options.center = 1; break;
+ case 'i': g_options.stdin = 1; break;
+ case 'r': g_options.recursive = 1; break;
+ case 'h': print_usage(name); exit(0); break;
+ default:
+ fprintf(stderr, "Unknown argument '%c'. Aborting.\n", *o);
+ exit(1);
+ }
+ }
+}
+
+int main(int argc, char** argv)
+{
+ if(argc < 2) {
+ print_usage(argv[0]);
+ exit(1);
+ }
+
+ struct imv_navigator nav;
+ imv_init_navigator(&nav);
+
+ for(int i = 1; i < argc; ++i) {
+ if(argv[i][0] == '-') {
+ parse_arg(argv[0], &argv[i][1]);
+ } else {
+ if(g_options.recursive) {
+ imv_navigator_add_path_recursive(&nav, argv[i]);
+ } else {
+ imv_navigator_add_path(&nav, argv[i]);
+ }
+ }
+ }
+
+ if(g_options.stdin) {
+ char buf[512];
+ while(fgets(buf, sizeof(buf), stdin)) {
+ size_t len = strlen(buf);
+ if(buf[len-1] == '\n') {
+ buf[--len] = 0;
+ }
+ if(len > 0) {
+ if(g_options.recursive) {
+ imv_navigator_add_path_recursive(&nav, buf);
+ } else {
+ imv_navigator_add_path(&nav, buf);
+ }
+ }
+ }
+ }
+
+ if(!imv_navigator_get_current_path(&nav)) {
+ fprintf(stderr, "No input files. Exiting.\n");
+ exit(1);
+ }
+
+ if(SDL_Init(SDL_INIT_VIDEO) != 0) {
+ fprintf(stderr, "SDL Failed to Init: %s\n", SDL_GetError());
+ exit(1);
+ }
+
+ const int width = 1280;
+ const int height = 720;
+
+ SDL_Window *window = SDL_CreateWindow(
+ "imv",
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ width, height,
+ SDL_WINDOW_RESIZABLE);
+
+ SDL_Renderer *renderer =
+ SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
+
+ /* Use linear sampling for scaling */
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
+
+ struct imv_image img;
+ imv_init_image(&img);
+
+ struct imv_texture tex;
+ imv_init_texture(&tex, renderer);
+
+ struct imv_viewport view;
+ imv_init_viewport(&view, window);
+
+ /* Put us in fullscren by default if requested */
+ if(g_options.fullscreen) {
+ imv_viewport_toggle_fullscreen(&view);
+ }
+
+ double last_time = SDL_GetTicks() / 1000.0;
+
+ int quit = 0;
+ while(!quit) {
+ SDL_Event e;
+ while(!quit && SDL_PollEvent(&e)) {
+ switch(e.type) {
+ case SDL_QUIT:
+ quit = 1;
+ break;
+ case SDL_KEYDOWN:
+ switch (e.key.keysym.sym) {
+ case SDLK_q: quit = 1; break;
+ case SDLK_LEFTBRACKET:
+ case SDLK_LEFT: imv_navigator_prev_path(&nav); break;
+ case SDLK_RIGHTBRACKET:
+ case SDLK_RIGHT: imv_navigator_next_path(&nav); break;
+ case SDLK_EQUALS:
+ case SDLK_i:
+ case SDLK_UP: imv_viewport_zoom(&view, 1); break;
+ case SDLK_MINUS:
+ case SDLK_o:
+ case SDLK_DOWN: imv_viewport_zoom(&view, -1); break;
+ case SDLK_r: imv_viewport_reset(&view); break;
+ case SDLK_c: imv_viewport_center(&view, &img); break;
+ case SDLK_j: imv_viewport_move(&view, 0, -50); break;
+ case SDLK_k: imv_viewport_move(&view, 0, 50); break;
+ case SDLK_h: imv_viewport_move(&view, 50, 0); break;
+ case SDLK_l: imv_viewport_move(&view, -50, 0); break;
+ case SDLK_x: imv_navigator_remove_current_path(&nav); break;
+ case SDLK_f: imv_viewport_toggle_fullscreen(&view); break;
+ case SDLK_PERIOD: imv_image_load_next_frame(&img); break;
+ case SDLK_SPACE: imv_viewport_toggle_playing(&view, &img);break;
+ case SDLK_s: imv_viewport_scale_to_window(&view, &img);break;
+ }
+ break;
+ case SDL_MOUSEWHEEL:
+ imv_viewport_zoom(&view, e.wheel.y);
+ break;
+ case SDL_MOUSEMOTION:
+ if(e.motion.state & SDL_BUTTON_LMASK) {
+ imv_viewport_move(&view, e.motion.xrel, e.motion.yrel);
+ }
+ break;
+ case SDL_WINDOWEVENT:
+ imv_viewport_set_redraw(&view);
+ break;
+ }
+ }
+
+ if(quit) {
+ break;
+ }
+
+ while(imv_navigator_has_changed(&nav)) {
+ const char* current_path = imv_navigator_get_current_path(&nav);
+ char title[256];
+ snprintf(&title[0], sizeof(title), "imv - [%i/%i] [LOADING] %s",
+ nav.cur_path + 1, nav.num_paths, current_path);
+ imv_viewport_set_title(&view, title);
+
+ if(!current_path) {
+ fprintf(stderr, "No input files left. Exiting.\n");
+ exit(1);
+ }
+
+ if(imv_image_load(&img, current_path) != 0) {
+ imv_navigator_remove_current_path(&nav);
+ } else {
+ snprintf(&title[0], sizeof(title), "imv - [%i/%i] [%ix%i] %s",
+ nav.cur_path + 1, nav.num_paths,
+ img.width, img.height, current_path);
+ imv_viewport_set_title(&view, title);
+ imv_viewport_reset(&view);
+ }
+ /* Autoscale if requested */
+ if(g_options.autoscale) {
+ imv_viewport_scale_to_window(&view, &img);
+ }
+ if(g_options.center) {
+ imv_viewport_center(&view, &img);
+ }
+ }
+
+ if(view.playing) {
+ double cur_time = SDL_GetTicks() / 1000.0;
+ double dt = cur_time - last_time;
+ imv_image_play(&img, dt);
+ }
+
+ if(imv_image_has_changed(&img)) {
+ imv_texture_set_image(&tex, img.cur_bmp);
+ imv_viewport_set_redraw(&view);
+ }
+
+ if(view.redraw) {
+ imv_texture_draw(&tex, view.x, view.y, view.scale);
+ view.redraw = 0;
+ }
+ last_time = SDL_GetTicks() / 1000.0;
+ SDL_Delay(10);
+ }
+
+ imv_destroy_image(&img);
+ imv_destroy_texture(&tex);
+ imv_destroy_navigator(&nav);
+ imv_destroy_viewport(&view);
+
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/src/navigator.c b/src/navigator.c
new file mode 100644
index 0000000..408c53c
--- /dev/null
+++ b/src/navigator.c
@@ -0,0 +1,188 @@
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include "navigator.h"
+
+#include <sys/stat.h>
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+void imv_init_navigator(struct imv_navigator *nav)
+{
+ nav->buf_size = 512;
+ nav->paths = (char **)malloc(sizeof(char*) * nav->buf_size);
+ nav->num_paths = 0;
+ nav->cur_path = 0;
+ nav->last_move_direction = 1;
+ nav->changed = 0;
+}
+
+void imv_destroy_navigator(struct imv_navigator *nav)
+{
+ if(nav->buf_size > 0) {
+ free(nav->paths);
+ nav->paths = NULL;
+ nav->buf_size = 0;
+ }
+ nav->num_paths = 0;
+}
+
+int imv_can_load_image(const char* path);
+
+static void add_item(struct imv_navigator *nav, const char *path)
+{
+ if(!imv_can_load_image(path)) {
+ return;
+ }
+
+ if(nav->buf_size == nav->num_paths) {
+ int new_buf_size = nav->buf_size * 2;
+ char **new_paths = malloc(sizeof(char*) * new_buf_size);
+ memcpy(new_paths, nav->paths, sizeof(char*) * nav->buf_size);
+ free(nav->paths);
+ nav->paths = new_paths;
+ nav->buf_size = new_buf_size;
+ }
+ nav->paths[nav->num_paths] = strdup(path);
+ nav->num_paths += 1;
+ if(nav->num_paths == 1) {
+ nav->changed = 1;
+ }
+}
+
+void imv_navigator_add_path(struct imv_navigator *nav, const char *path)
+{
+ char path_buf[512];
+ struct stat path_info;
+ stat(path, &path_info);
+ if(S_ISDIR(path_info.st_mode)) {
+ DIR *d = opendir(path);
+ if(d) {
+ struct dirent *dir;
+ while((dir = readdir(d)) != NULL) {
+ if(strcmp(dir->d_name, "..") == 0 ||
+ strcmp(dir->d_name, ".") == 0) {
+ continue;
+ }
+ snprintf(path_buf, sizeof(path_buf), "%s/%s", path, dir->d_name);
+ add_item(nav, path_buf);
+ }
+ closedir(d);
+ }
+ } else {
+ add_item(nav, path);
+ }
+}
+
+void imv_navigator_add_path_recursive(struct imv_navigator *nav, const char *path)
+{
+ char path_buf[512];
+ struct stat path_info;
+ stat(path, &path_info);
+ if(S_ISDIR(path_info.st_mode)) {
+ DIR *d = opendir(path);
+ if(d) {
+ struct dirent *dir;
+ while((dir = readdir(d)) != NULL) {
+ if(strcmp(dir->d_name, "..") == 0 ||
+ strcmp(dir->d_name, ".") == 0) {
+ continue;
+ }
+ snprintf(path_buf, sizeof(path_buf), "%s/%s", path, dir->d_name);
+ imv_navigator_add_path_recursive(nav, path_buf);
+ }
+ closedir(d);
+ }
+ } else {
+ add_item(nav, path);
+ }
+}
+
+const char *imv_navigator_get_current_path(struct imv_navigator *nav)
+{
+ if(nav->num_paths == 0) {
+ return NULL;
+ }
+ return nav->paths[nav->cur_path];
+}
+
+void imv_navigator_next_path(struct imv_navigator *nav)
+{
+ int prev_path = nav->cur_path;
+ if(nav->num_paths == 0) {
+ return;
+ }
+ nav->cur_path += 1;
+ if(nav->cur_path == nav->num_paths) {
+ nav->cur_path = 0;
+ }
+ nav->last_move_direction = 1;
+ nav->changed = prev_path != nav->cur_path;
+}
+
+void imv_navigator_prev_path(struct imv_navigator *nav)
+{
+ int prev_path = nav->cur_path;
+ if(nav->num_paths == 0) {
+ return;
+ }
+ nav->cur_path -= 1;
+ if(nav->cur_path < 0) {
+ nav->cur_path = nav->num_paths - 1;
+ }
+ nav->last_move_direction = -1;
+ nav->changed = prev_path != nav->cur_path;
+}
+
+void imv_navigator_remove_current_path(struct imv_navigator *nav)
+{
+ if(nav->num_paths == 0) {
+ return;
+ }
+
+ free(nav->paths[nav->cur_path]);
+ for(int i = nav->cur_path; i < nav->num_paths - 1; ++i) {
+ nav->paths[i] = nav->paths[i+1];
+ }
+ nav->num_paths -= 1;
+
+ if(nav->last_move_direction < 0) {
+ /* Move left */
+ imv_navigator_prev_path(nav);
+ } else {
+ /* Try to stay where we are, unless we ran out of room */
+ if(nav->cur_path == nav->num_paths) {
+ nav->cur_path = 0;
+ }
+ }
+
+ nav->changed = 1;
+}
+
+int imv_navigator_has_changed(struct imv_navigator *nav)
+{
+ if(nav->changed) {
+ nav->changed = 0;
+ return 1;
+ } else {
+ return 0;
+ }
+}
diff --git a/src/navigator.h b/src/navigator.h
new file mode 100644
index 0000000..1bccff6
--- /dev/null
+++ b/src/navigator.h
@@ -0,0 +1,45 @@
+#ifndef IMV_NAVIGATOR_H
+#define IMV_NAVIGATOR_H
+
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+struct imv_navigator {
+ int num_paths;
+ int buf_size;
+ int cur_path;
+ char **paths;
+ int last_move_direction;
+ int changed;
+};
+
+void imv_init_navigator(struct imv_navigator *nav);
+void imv_destroy_navigator(struct imv_navigator *nav);
+
+void imv_navigator_add_path(struct imv_navigator *nav, const char *path);
+void imv_navigator_add_path_recursive(struct imv_navigator *nav, const char *path);
+
+const char *imv_navigator_get_current_path(struct imv_navigator *nav);
+void imv_navigator_next_path(struct imv_navigator *nav);
+void imv_navigator_prev_path(struct imv_navigator *nav);
+void imv_navigator_remove_current_path(struct imv_navigator *nav);
+
+int imv_navigator_has_changed(struct imv_navigator *nav);
+
+#endif
diff --git a/src/texture.c b/src/texture.c
new file mode 100644
index 0000000..7dd6157
--- /dev/null
+++ b/src/texture.c
@@ -0,0 +1,146 @@
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include "texture.h"
+
+void imv_init_texture(struct imv_texture *tex, SDL_Renderer *r)
+{
+ tex->renderer = r;
+ tex->num_chunks = 0;
+ tex->chunks = NULL;
+
+ SDL_RendererInfo ri;
+ SDL_GetRendererInfo(r, &ri);
+ tex->chunk_width = ri.max_texture_width;
+ tex->chunk_height = ri.max_texture_height;
+}
+
+void imv_destroy_texture(struct imv_texture *tex)
+{
+ if(tex->num_chunks > 0) {
+ for(int i = 0; i < tex->num_chunks; ++i) {
+ SDL_DestroyTexture(tex->chunks[i]);
+ }
+ free(tex->chunks);
+ tex->num_chunks = 0;
+ tex->chunks = NULL;
+ tex->renderer = NULL;
+ }
+}
+
+int imv_texture_set_image(struct imv_texture *tex, FIBITMAP *image)
+{
+ FIBITMAP *frame = FreeImage_ConvertTo32Bits(image);
+ tex->width = FreeImage_GetWidth(frame);
+ tex->height = FreeImage_GetHeight(frame);
+
+ char* pixels = (char*)FreeImage_GetBits(frame);
+
+ /* figure out how many chunks are needed, and create them */
+ if(tex->num_chunks > 0) {
+ for(int i = 0; i < tex->num_chunks; ++i) {
+ SDL_DestroyTexture(tex->chunks[i]);
+ }
+ free(tex->chunks);
+ }
+
+ tex->num_chunks_wide = 1 + tex->width / tex->chunk_width;
+ tex->num_chunks_tall = 1 + tex->height / tex->chunk_height;
+
+ tex->last_chunk_width = tex->width % tex->chunk_width;
+ tex->last_chunk_height = tex->height % tex->chunk_height;
+
+ if(tex->last_chunk_width == 0) {
+ tex->last_chunk_width = tex->chunk_width;
+ }
+ if(tex->last_chunk_height == 0) {
+ tex->last_chunk_height = tex->chunk_height;
+ }
+
+ tex->num_chunks = tex->num_chunks_wide * tex->num_chunks_tall;
+ tex->chunks = (SDL_Texture**)malloc(sizeof(SDL_Texture*) * tex->num_chunks);
+
+ int failed_at = -1;
+ for(int y = 0; y < tex->num_chunks_tall; ++y) {
+ for(int x = 0; x < tex->num_chunks_wide; ++x) {
+ const int is_last_h_chunk = (x == tex->num_chunks_wide - 1);
+ const int is_last_v_chunk = (y == tex->num_chunks_tall - 1);
+ tex->chunks[x + y * tex->num_chunks_wide] =
+ SDL_CreateTexture(tex->renderer,
+ SDL_PIXELFORMAT_RGB888,
+ SDL_TEXTUREACCESS_STATIC,
+ is_last_h_chunk ? tex->last_chunk_width : tex->chunk_width,
+ is_last_v_chunk ? tex->last_chunk_height : tex->chunk_height);
+ if(tex->chunks[x + y * tex->num_chunks_wide] == NULL) {
+ failed_at = x + y * tex->num_chunks_wide;
+ break;
+ }
+ }
+ }
+
+ if(failed_at != -1) {
+ for(int i = 0; i <= failed_at; ++i) {
+ SDL_DestroyTexture(tex->chunks[i]);
+ }
+ free(tex->chunks);
+ tex->num_chunks = 0;
+ tex->chunks = NULL;
+ return 1;
+ }
+
+ for(int y = 0; y < tex->num_chunks_tall; ++y) {
+ for(int x = 0; x < tex->num_chunks_wide; ++x) {
+ ptrdiff_t offset = 4 * x * tex->chunk_width +
+ y * 4 * tex->width * tex->chunk_height;
+ char* addr = pixels + offset;
+ SDL_UpdateTexture(tex->chunks[x + y * tex->num_chunks_wide],
+ NULL, addr, 4 * tex->width);
+ }
+ }
+
+ return 0;
+}
+
+void imv_texture_draw(struct imv_texture *tex, int bx, int by, double scale)
+{
+ int offset_x = 0;
+ int offset_y = 0;
+
+ SDL_RenderClear(tex->renderer);
+ for(int y = 0; y < tex->num_chunks_tall; ++y) {
+ for(int x = 0; x < tex->num_chunks_wide; ++x) {
+ int img_w, img_h, img_access;
+ unsigned int img_format;
+ SDL_QueryTexture(tex->chunks[x + y * tex->num_chunks_wide],
+ &img_format, &img_access, &img_w, &img_h);
+ SDL_Rect view_area = {
+ bx + offset_x,
+ by + offset_y,
+ img_w * scale,
+ img_h * scale
+ };
+ SDL_RenderCopy(tex->renderer,
+ tex->chunks[x + y * tex->num_chunks_wide], NULL, &view_area);
+ offset_x += tex->chunk_width * scale;
+ }
+ offset_x = 0;
+ offset_y += tex->chunk_height * scale;
+ }
+ SDL_RenderPresent(tex->renderer);
+}
diff --git a/src/texture.h b/src/texture.h
new file mode 100644
index 0000000..4f805a2
--- /dev/null
+++ b/src/texture.h
@@ -0,0 +1,46 @@
+#ifndef IMV_TEXTURE_H
+#define IMV_TEXTURE_H
+
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include <SDL2/SDL.h>
+#include <FreeImage.h>
+
+struct imv_texture {
+ int width; /* width of the texture overall */
+ int height; /* height of the texture overall */
+ int num_chunks; /* number of chunks allocated */
+ SDL_Texture **chunks; /* array of chunks */
+ int num_chunks_wide; /* number of chunks per row of the image */
+ int num_chunks_tall; /* number of chunks per column of the image */
+ int chunk_width; /* chunk width */
+ int chunk_height; /* chunk height */
+ int last_chunk_width; /* width of rightmost chunk */
+ int last_chunk_height; /* height of bottommost chunk */
+ SDL_Renderer *renderer; /* SDL renderer to draw to */
+};
+
+void imv_init_texture(struct imv_texture *tex, SDL_Renderer *r);
+void imv_destroy_texture(struct imv_texture *tex);
+
+int imv_texture_set_image(struct imv_texture *tex, FIBITMAP *image);
+void imv_texture_draw(struct imv_texture *tex, int x, int y, double scale);
+
+#endif
diff --git a/src/viewport.c b/src/viewport.c
new file mode 100644
index 0000000..1630604
--- /dev/null
+++ b/src/viewport.c
@@ -0,0 +1,119 @@
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include "viewport.h"
+#include "image.h"
+
+void imv_init_viewport(struct imv_viewport *view, SDL_Window *window)
+{
+ view->window = window;
+ view->scale = 1;
+ view->x = view->y = view->fullscreen = view->redraw = 0;
+ view->playing = 1;
+}
+
+void imv_destroy_viewport(struct imv_viewport *view)
+{
+ view->window = NULL;
+ return;
+}
+
+void imv_viewport_toggle_fullscreen(struct imv_viewport *view)
+{
+ if(view->fullscreen) {
+ SDL_SetWindowFullscreen(view->window, 0);
+ view->fullscreen = 0;
+ } else {
+ SDL_SetWindowFullscreen(view->window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+ view->fullscreen = 1;
+ }
+}
+
+void imv_viewport_toggle_playing(struct imv_viewport *view, struct imv_image *img)
+{
+ if(view->playing) {
+ view->playing = 0;
+ } else if(imv_image_is_animated(img)) {
+ view->playing = 1;
+ }
+}
+
+void imv_viewport_reset(struct imv_viewport *view)
+{
+ view->scale = 1;
+ view->x = view->y = 0;
+ view->redraw = 1;
+}
+
+void imv_viewport_move(struct imv_viewport *view, int x, int y)
+{
+ view->x += x;
+ view->y += y;
+ view->redraw = 1;
+}
+
+void imv_viewport_zoom(struct imv_viewport *view, int amount)
+{
+ view->scale += amount * 0.1;
+ if(view->scale > 100)
+ view->scale = 10;
+ else if (view->scale < 0.01)
+ view->scale = 0.1;
+ view->redraw = 1;
+}
+
+void imv_viewport_center(struct imv_viewport *view, const struct imv_image* img)
+{
+ int ww, wh;
+ SDL_GetWindowSize(view->window, &ww, &wh);
+
+ view->x = (ww - img->width * view->scale) / 2;
+ view->y = (wh - img->height * view->scale) / 2;
+
+ view->redraw = 1;
+}
+
+void imv_viewport_scale_to_window(struct imv_viewport *view, const struct imv_image* img)
+{
+ int ww, wh;
+ SDL_GetWindowSize(view->window, &ww, &wh);
+
+ double window_aspect = (double)ww / (double)wh;
+ double image_aspect = (double)img->width / (double)img->height;
+
+ if(window_aspect > image_aspect) {
+ /* Image will become too tall before it becomes too wide */
+ view->scale = (double)wh / (double)img->height;
+ } else {
+ /* Image will become too wide before it becomes too tall */
+ view->scale = (double)ww / (double)img->width;
+ }
+
+ imv_viewport_center(view, img);
+}
+
+void imv_viewport_set_redraw(struct imv_viewport *view)
+{
+ view->redraw = 1;
+}
+
+void imv_viewport_set_title(struct imv_viewport *view, char* title)
+{
+ SDL_SetWindowTitle(view->window, title);
+}
diff --git a/src/viewport.h b/src/viewport.h
new file mode 100644
index 0000000..2cff15c
--- /dev/null
+++ b/src/viewport.h
@@ -0,0 +1,48 @@
+#ifndef IMV_VIEWPORT_H
+#define IMV_VIEWPORT_H
+
+/* Copyright (c) 2015 Harry Jeffery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+
+#include <SDL2/SDL.h>
+#include "image.h"
+
+struct imv_viewport {
+ SDL_Window *window;
+ double scale;
+ int x, y;
+ int fullscreen;
+ int redraw;
+ int playing;
+};
+
+void imv_init_viewport(struct imv_viewport *view, SDL_Window *window);
+void imv_destroy_viewport(struct imv_viewport *view);
+
+void imv_viewport_toggle_fullscreen(struct imv_viewport*);
+void imv_viewport_toggle_playing(struct imv_viewport*, struct imv_image*);
+void imv_viewport_reset(struct imv_viewport*);
+void imv_viewport_move(struct imv_viewport*, int, int);
+void imv_viewport_zoom(struct imv_viewport*, int);
+void imv_viewport_center(struct imv_viewport*, const struct imv_image*);
+void imv_viewport_scale_to_window(struct imv_viewport*, const struct imv_image*);
+void imv_viewport_set_redraw(struct imv_viewport*);
+void imv_viewport_set_title(struct imv_viewport*, char*);
+
+#endif