DKL9 GitList
Repositories
DKL9 home
iv
Code
Commits
Branches
Tags
Search
Tree:
d476d64
Branches
Tags
master
iv
main.c
Fix panning and pedantries
dkl9
commited
d476d64
at 2026-126 01:07:20
main.c
Blame
History
Raw
/* iv/main.c - see README */ #include <libgen.h> #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "SDL2/SDL.h" #include "SDL2/SDL_image.h" #define TWICE(x) for (int j = 0; j < 2; j++) { x; } #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 struct ViewState { SDL_Texture* tex; /* image dimensions */ unsigned int idim[2]; /* zoom level */ float zoom; /* pan position (centre of image) */ int pan[2]; }; /* 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(const int cond, const char* msg, const int showsdl) { if (cond) { fprintf(stderr, "iv: failed to %s\n", msg); if (showsdl) { fprintf(stderr, "SDL: %s\n", SDL_GetError()); } exit(EXIT_FAILURE); } } /* make alpha checker pattern */ SDL_Texture* checker(SDL_Renderer* ren, int w, int h) { SDL_Texture* tex = SDL_CreateTexture( ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, w, h ); cfail(!tex, "make background texture", 1); void* pixels; int pitch; cfail( SDL_LockTexture(tex, NULL, &pixels, &pitch), "lock background texture", 1 ); for (int y = 0; y < h; ++y) { Uint32* row = (Uint32*)((Uint8*)pixels + y * pitch); for (int x = 0; x < w; ++x) { row[x] = 0xB0B0B0FF + 0x40404000 * (x + y & 1); } } SDL_UnlockTexture(tex); return tex; } /* process command-line options, setting pointed strings */ void handleargs(int argc, char** argv, char** title) { int oc; 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.8-dev\n" ); exit(EXIT_SUCCESS); case 't': *title = optarg; break; case ':': case '?': cfail(1, "parse options", 0); } } } /* load images from arguments */ SDL_Surface** loadimages(char** argv, const int fc) { SDL_Surface** isurf = calloc(fc, sizeof (SDL_Surface*)); for (int i = 0; i < fc; i++) { const 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); } return isurf; } void updatetitle(SDL_Window* window, const char* ct, char** title, const char* fname, const struct ViewState vs) { if (!ct) { free(*title); *title = malloc(strlen(fname) + 32); cfail(!*title, "allocate memory", 0); sprintf(*title, "iv: %s (%dx%d)", fname, vs.idim[0], vs.idim[1]); SDL_SetWindowTitle(window, *title); } } void showcoords(const SDL_MouseButtonEvent button, const struct ViewState vs) { float cc[2]; int bpos[2] = {button.x, button.y}; TWICE(cc[j] = ZSH((float)bpos[j] - (float)vs.pan[j] + ZSH(vs.idim[j], vs.zoom - 1.0), -vs.zoom)) printf("%.0f, %.0f\n", cc[0], cc[1]); fflush(stdout); } int handlekey(SDL_Keycode sym, struct ViewState* vs, int ci, int fc, const unsigned int wdim[2]) { switch (sym) { /* bring image back into focus when Space is pressed */ case SDLK_SPACE: TWICE(vs->pan[j] = wdim[j] / 2) return 2; break; /* exit when Esc is pressed */ case SDLK_ESCAPE: return 4; break; /* navigate image list with arrow keys */ case SDLK_LEFT: case SDLK_UP: if (ci > 0) { return -1; } break; case SDLK_RIGHT: case SDLK_DOWN: if (ci < fc - 1) { return 1; } break; } return 0; } void drawbg(SDL_Renderer* ren, const unsigned int wdim[2], const int cw, const int ch, SDL_Texture* act) { /* clear to background colour */ cfail(SDL_RenderClear(ren), "clear screen", 1); /* alpha checker background */ for (int i = 0; i < wdim[0]; i += APS * cw) { for (int j = 0; j < wdim[1]; 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); } } } void drawfg(SDL_Renderer* ren, const struct ViewState vs) { int ic[2]; TWICE(ic[j] = vs.pan[j] - ZSH(vs.idim[j], vs.zoom - 1.0)) /* render image */ cfail(SDL_RenderCopy(ren, vs.tex, NULL, &(SDL_Rect){ .x = ic[0], .y = ic[1], .w = ZSH(vs.idim[0], vs.zoom), .h = ZSH(vs.idim[1], vs.zoom) }), "display image", 1); } int main(int argc, char** argv) { /* setup */ SDL_Init(SDL_INIT_VIDEO); atexit(SDL_Quit); char* ct = NULL; handleargs(argc, argv, &ct); /* load images before creating window, to determine window size */ cfail(optind >= argc, "find filename(s) in options", 0); int fc = argc - optind; SDL_Surface** isurf = loadimages(argv, fc); int ci = 0; int iw = isurf[ci]->w; int ih = isurf[ci]->h; /* create window and renderer */ char* title = NULL; 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 */ int cw = 64, ch = 64; SDL_Texture* act = checker(ren, cw, ch); struct ViewState* sl = calloc(fc, sizeof(struct ViewState)); for (int i = 0; i < fc; i++) { sl[i].tex = SDL_CreateTextureFromSurface(ren, isurf[i]); cfail(!sl[i].tex, "create texture", 1); sl[i].idim[0] = isurf[i]->w; sl[i].idim[1] = isurf[i]->h; TWICE(sl[i].pan[j] = sl[i].idim[j] / 2) SDL_FreeSurface(isurf[i]); } /* 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 */ unsigned int wdim[2] = {iw, ih}; /* mouse position */ int mpos[2]; int iic; float dz; while (r) { ef = 0; while (SDL_PollEvent(&ev)) { switch (ev.type) { case SDL_KEYUP: iic = 0; switch (handlekey(ev.key.keysym.sym, &sl[ci], ci, fc, wdim)) { case 4: r = 0; break; case 2: ef = 1; break; case 1: iic = 1; ci++; break; case -1: iic = 1; ci--; break; } if (iic) { ef = 1; updatetitle(window, ct, &title, argv[optind + ci], sl[ci]); } break; case SDL_MOUSEBUTTONDOWN: /* output coordinates of cursor in image on middle-click */ if (ev.button.button == 2) { showcoords(ev.button, sl[ci]); } /* bring image back into focus on right-click */ if (ev.button.button == 3) { TWICE(sl[ci].pan[j] = wdim[j] / 2) ef = 1; } break; case SDL_MOUSEMOTION: /* pan-dragging if mouse is down */ if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(1)) { int hid, rhid; int rel[2] = {ev.motion.xrel, ev.motion.yrel}; /* clamp panning to edges of window */ TWICE( /* scaled (onscreen) image size, halved */ hid = ZSH(sl[ci].idim[j], sl[ci].zoom - 1.0); rhid = wdim[j] - hid; if (2 * hid > wdim[j]) { int tmp = hid; hid = rhid; rhid = tmp; } sl[ci].pan[j] = CLAMP( hid, sl[ci].pan[j] + rel[j], rhid ); ) ef = 1; } mpos[0] = ev.motion.x; mpos[1] = ev.motion.y; break; case SDL_MOUSEWHEEL: /* scrolling changes zoom */ dz = DZDS * ev.wheel.y; sl[ci].zoom += dz; TWICE(sl[ci].pan[j] = mpos[j] - ZSH((mpos[j] - sl[ci].pan[j]), dz)) 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; case SDL_WINDOWEVENT_SIZE_CHANGED: wdim[0] = ev.window.data1; wdim[1] = ev.window.data2; break; } break; } } if (ef) { drawbg(ren, wdim, cw, ch, act); drawfg(ren, sl[ci]); SDL_RenderPresent(ren); } /* limit framerate */ tb = SDL_GetTicks(); if (tb - ta < FRAMETIME) { SDL_Delay(FRAMETIME - (tb - ta)); } ta = tb; } SDL_DestroyWindow(window); return 0; }