DKL9 GitList
Repositories
DKL9 home
iv
Code
Commits
Branches
Tags
Search
Tree:
63b645f
Branches
Tags
master
iv
main.c
Initial commit, v1.7
dkl9
commited
63b645f
at 2025-180 18:10:59
main.c
Blame
History
Raw
/* iv/main.c - see README */ #include <libgen.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "SDL2/SDL.h" #include "SDL2/SDL_image.h" #define ZSH(a, b) ((a) * powf(2, b)) #define CLAMP(a, x, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) #define APS 6 #define FPS 60 #define FRAMETIME (1000 / FPS) #define DZDS 0.25 /* if cond is true, exit the program as a failure, with msg as the message, along with the SDL error, if showsdl is true */ void cfail(int cond, char* msg, int showsdl) { if (cond) { fprintf(stderr, "Could not %s\n", msg); if (showsdl) { fprintf(stderr, "SDL: %s\n", SDL_GetError()); } exit(EXIT_FAILURE); } } int main(int argc, char** argv) { /* load alpha checkers, relative to iv binary (not working directory) */ char* tpath = malloc(strlen(argv[0]) + 32); cfail(!tpath, "allocate memory", 0); sprintf(tpath, "%s/alpha_checker.bmp", dirname(argv[0])); SDL_Surface* acs = SDL_LoadBMP(tpath); cfail(!acs, "load alpha checker texture", 1); int cw = acs->w; int ch = acs->h; /* require a file argument */ if (argc < 2) { fprintf(stderr, "usage: iv -h or iv FILE\n"); exit(EXIT_FAILURE); } /* setup */ SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); /* process arguments */ int oc; char* ct = NULL; while ((oc = getopt(argc, argv, "ht:")) != -1) { switch (oc) { case 'h': printf( "iv - image viewer\n" "\nUsage: iv [-t TITLE] FILES\n" "\n-t TITLE sets a custom window title\n" "\nClick and drag to pan\n" "Scroll to zoom\n" "Right-click or space key to centre image\n" "Middle-click to get coordinates at cursor\n" "Arrow keys to move between images in the list\n" "version 1.6\n" ); exit(EXIT_SUCCESS); case 't': ct = optarg; break; case ':': case '?': cfail(1, "handle invalid options", 0); } } /* load images before creating window, to determine window size */ cfail(optind >= argc, "find filename in options", 0); int fc = argc - optind; SDL_Surface** isurf = calloc(fc, sizeof (SDL_Surface*)); for (int i = 0; i < fc; i++) { char* fname = argv[optind + i]; SDL_RWops* ir; if (!strcmp(fname, "-")) { ir = SDL_RWFromFP(stdin, 0); } else { ir = SDL_RWFromFile(fname, "r"); } cfail(!ir, "open file", 1); isurf[i] = IMG_Load_RW(ir, 1); cfail(!isurf[i], "load image", 1); } int ci = 0; int iw = isurf[ci]->w; int ih = isurf[ci]->h; /* create window and renderer */ char* title; if (ct) { title = ct; } else { title = malloc(strlen(argv[optind]) + 32); cfail(!title, "allocate memory", 0); sprintf(title, "iv: %s (%dx%d)", argv[optind], iw, ih); } SDL_Window* window = SDL_CreateWindow( title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, iw, ih, SDL_WINDOW_RESIZABLE ); cfail(!window, "create window", 1); SDL_Renderer* ren = SDL_CreateRenderer(window, -1, 0); cfail(!ren, "create renderer", 1); /* make images into textures */ SDL_Texture** itex = calloc(fc, sizeof (SDL_Texture*)); for (int i = 0; i < fc; i++) { itex[i] = SDL_CreateTextureFromSurface(ren, isurf[i]); cfail(!itex[i], "create texture", 1); SDL_FreeSurface(isurf[i]); } SDL_Texture* act = SDL_CreateTextureFromSurface(ren, acs); cfail(!act, "create surface", 1); SDL_FreeSurface(acs); /* background (transparent black) */ cfail(SDL_SetRenderDrawColor(ren, 0, 0, 0, 0), "set colour", 1); SDL_Event ev; /* time measurements for framerate limitation */ uint32_t ta = SDL_GetTicks(); uint32_t tb; int r = 1; /* only bother to redraw if an event that requires it is found */ int ef; /* window size */ int ww, wh; /* zoom level */ float zoom = 0; /* pan position (centre of image) */ int px = iw / 2; int py = ih / 2; /* keep track of mouse coordinates */ int mx = 0; int my = 0; while (r) { SDL_GetWindowSize(window, &ww, &wh); ef = 0; while (SDL_PollEvent(&ev)) { switch (ev.type) { case SDL_KEYUP: int iic = 0; switch (ev.key.keysym.sym) { /* bring image back into focus when Space is pressed */ case SDLK_SPACE: px = ww / 2; py = wh / 2; ef = 1; break; /* exit when Esc is pressed */ case SDLK_ESCAPE: r = 0; break; /* navigate image list with arrow keys */ case SDLK_LEFT: case SDLK_UP: if (ci > 0) { ci--; iic = 1; } break; case SDLK_RIGHT: case SDLK_DOWN: if (ci < fc - 1) { ci++; iic = 1; } break; } if (iic) { cfail(SDL_QueryTexture(itex[ci], NULL, NULL, &iw, &ih), "query texture", 1); zoom = 0; px = iw / 2; py = ih / 2; if (!ct) { free(title); title = malloc(strlen(argv[optind + ci]) + 32); cfail(!title, "allocate memory", 0); sprintf(title, "iv: %s (%dx%d)", argv[optind + ci], iw, ih); SDL_SetWindowTitle(window, title); } ef = 1; } break; case SDL_MOUSEBUTTONDOWN: /* output coordinates of cursor in image on middle-click */ if (ev.button.button == 2) { printf( "%.0f, %.0f\n", ZSH((float)ev.button.x - (float)px + ZSH(iw, zoom - 1.0), -zoom), ZSH((float)ev.button.y - (float)py + ZSH(ih, zoom - 1.0), -zoom) ); fflush(stdout); } /* bring image back into focus on right-click */ if (ev.button.button == 3) { px = ww / 2; py = wh / 2; ef = 1; } break; case SDL_MOUSEMOTION: /* pan-dragging if mouse is down */ if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(1)) { int sw = ZSH(iw, zoom - 1.0); int sh = ZSH(ih, zoom - 1.0); /* clamp panning to edges of window, unless zoomed image is bigger than window */ if (2 * sw > ww) { px += ev.motion.xrel; } else { px = CLAMP(sw, px + ev.motion.xrel, ww - sw); } if (2 * sh > wh) { py += ev.motion.yrel; } else { py = CLAMP(sh, py + ev.motion.yrel, wh - sh); } ef = 1; } mx = ev.motion.x; my = ev.motion.y; break; case SDL_MOUSEWHEEL: /* scrolling changes zoom */ zoom += DZDS * ev.wheel.y; px = mx - ZSH((mx - px), DZDS * ev.wheel.y); py = my - ZSH((my - py), DZDS * ev.wheel.y); ef = 1; break; case SDL_QUIT: /* exit upon a quit signal */ r = 0; break; case SDL_WINDOWEVENT: switch (ev.window.event) { /* exit when window is closed */ case SDL_WINDOWEVENT_CLOSE: r = 0; break; /* redraw required */ case SDL_WINDOWEVENT_EXPOSED: ef = 1; break; } break; } } if (ef) { SDL_GetWindowSize(window, &ww, &wh); /* clear to background colour */ cfail(SDL_RenderClear(ren), "clear screen", 1); /* alpha checker background */ for (int i = 0; i < ww; i += APS * cw) { for (int j = 0; j < wh; j += APS * ch) { cfail(SDL_RenderCopy(ren, act, NULL, &(SDL_Rect){ .x = i, .y = j, .w = APS * cw, .h = APS * ch }), "draw checker background", 1); } } /* render image */ cfail(SDL_RenderCopy(ren, itex[ci], NULL, &(SDL_Rect){ .x = px - ZSH(iw, zoom - 1.0), .y = py - ZSH(ih, zoom - 1.0), .w = ZSH(iw, zoom), .h = ZSH(ih, zoom) }), "display image", 1); SDL_RenderPresent(ren); } /* limit framerate */ tb = SDL_GetTicks(); if (tb - ta < FRAMETIME) { SDL_Delay(FRAMETIME - (tb - ta)); } ta = tb; } SDL_DestroyWindow(window); return 0; }