Initial commit, v1.7
dkl9

dkl9 commited on 2025-180 18:10:59
Showing 7 changed files, with 337 additions and 0 deletions.

... ...
@@ -0,0 +1,4 @@
1
+main
2
+*.o
3
+*.exe
4
+tags
... ...
@@ -0,0 +1,8 @@
1
+main : main.o
2
+	cc -O2 -o main main.o -lSDL2 -lSDL2_image
3
+
4
+main.o : main.c
5
+	cc -O2 -c -Wall main.c
6
+
7
+clean :
8
+	rm -f main.o main
... ...
@@ -0,0 +1,8 @@
1
+main : main.o
2
+	x86_64-w64-mingw32-gcc -O2 -mwindows -o main main.o -lmingw32 -lSDL2main -lSDL2 -lSDL2_image
3
+
4
+main.o : main.c
5
+	x86_64-w64-mingw32-gcc -O2 -mwindows -c -Wall main.c
6
+
7
+clean :
8
+	rm -f main.o main
... ...
@@ -0,0 +1,10 @@
1
+iv is an SDL-based image viewer.
2
+
3
+Changes on and after 2025-181 are tracked in Git.
4
+Older change history:
5
+- 2021-248 and before: main development (up thru v1.2)
6
+- 2022-233 -t TITLE option, getopts cleanup, smaller zoom steps (v1.3)
7
+- 2022-294 output coordinates from middle-click (v1.4)
8
+- 2022-324 better zooming (v1.5)
9
+- 2022-361 handle multiple files (v1.6)
10
+- 2023-227 accept SIGTERM to kill/quit (v1.7)
... ...
@@ -0,0 +1,9 @@
1
+[Desktop Entry]
2
+Type=Application
3
+Name=iv (Image Viewer)
4
+GenericName=Image viewer
5
+Comment=Look into images
6
+TryExec=iv
7
+Exec=/usr/local/bin/iv %U
8
+Terminal=false
9
+MimeType=image/bmp;image/jpeg;image/png;image/gif;image/svg+xml;image/tiff
... ...
@@ -0,0 +1,298 @@
1
+/* iv/main.c - see README */
2
+
3
+#include <libgen.h>
4
+#include <stdio.h>
5
+#include <stdlib.h>
6
+#include <string.h>
7
+#include <unistd.h>
8
+
9
+#include "SDL2/SDL.h"
10
+#include "SDL2/SDL_image.h"
11
+
12
+#define ZSH(a, b) ((a) * powf(2, b))
13
+#define CLAMP(a, x, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x)))
14
+#define APS 6
15
+#define FPS 60
16
+#define FRAMETIME (1000 / FPS)
17
+#define DZDS 0.25
18
+
19
+/* if cond is true, exit the program as a failure, with msg as the message,
20
+along with the SDL error, if showsdl is true */
21
+void cfail(int cond, char* msg, int showsdl) {
22
+    if (cond) {
23
+        fprintf(stderr, "Could not %s\n", msg);
24
+        if (showsdl) {
25
+            fprintf(stderr, "SDL: %s\n", SDL_GetError());
26
+        }
27
+        exit(EXIT_FAILURE);
28
+    }
29
+}
30
+
31
+int main(int argc, char** argv) {
32
+    /* load alpha checkers, relative to iv binary (not working directory) */
33
+    char* tpath = malloc(strlen(argv[0]) + 32);
34
+    cfail(!tpath, "allocate memory", 0);
35
+    sprintf(tpath, "%s/alpha_checker.bmp", dirname(argv[0]));
36
+    SDL_Surface* acs = SDL_LoadBMP(tpath);
37
+    cfail(!acs, "load alpha checker texture", 1);
38
+    int cw = acs->w;
39
+    int ch = acs->h;
40
+
41
+    /* require a file argument */
42
+    if (argc < 2) {
43
+        fprintf(stderr, "usage: iv -h or iv FILE\n");
44
+        exit(EXIT_FAILURE);
45
+    }
46
+
47
+    /* setup */
48
+    SDL_Init(SDL_INIT_VIDEO);
49
+    atexit(SDL_Quit);
50
+
51
+    /* process arguments */
52
+    int oc;
53
+    char* ct = NULL;
54
+    while ((oc = getopt(argc, argv, "ht:")) != -1) {
55
+        switch (oc) {
56
+            case 'h':
57
+                printf(
58
+                    "iv - image viewer\n"
59
+                    "\nUsage: iv [-t TITLE] FILES\n"
60
+                    "\n-t TITLE sets a custom window title\n"
61
+                    "\nClick and drag to pan\n"
62
+                    "Scroll to zoom\n"
63
+                    "Right-click or space key to centre image\n"
64
+                    "Middle-click to get coordinates at cursor\n"
65
+                    "Arrow keys to move between images in the list\n"
66
+                    "version 1.6\n"
67
+                );
68
+                exit(EXIT_SUCCESS);
69
+            case 't':
70
+                ct = optarg;
71
+                break;
72
+            case ':':
73
+            case '?':
74
+                cfail(1, "handle invalid options", 0);
75
+        }
76
+    }
77
+
78
+    /* load images before creating window, to determine window size */
79
+    cfail(optind >= argc, "find filename in options", 0);
80
+    int fc = argc - optind;
81
+    SDL_Surface** isurf = calloc(fc, sizeof (SDL_Surface*));
82
+    for (int i = 0; i < fc; i++) {
83
+        char* fname = argv[optind + i];
84
+        SDL_RWops* ir;
85
+        if (!strcmp(fname, "-")) {
86
+            ir = SDL_RWFromFP(stdin, 0);
87
+        } else {
88
+            ir = SDL_RWFromFile(fname, "r");
89
+        }
90
+        cfail(!ir, "open file", 1);
91
+        isurf[i] = IMG_Load_RW(ir, 1);
92
+        cfail(!isurf[i], "load image", 1);
93
+    }
94
+    int ci = 0;
95
+    int iw = isurf[ci]->w;
96
+    int ih = isurf[ci]->h;
97
+
98
+    /* create window and renderer */
99
+    char* title;
100
+    if (ct) {
101
+        title = ct;
102
+    } else {
103
+        title = malloc(strlen(argv[optind]) + 32);
104
+        cfail(!title, "allocate memory", 0);
105
+        sprintf(title, "iv: %s (%dx%d)", argv[optind], iw, ih);
106
+    }
107
+    SDL_Window* window = SDL_CreateWindow(
108
+        title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, iw, ih,
109
+        SDL_WINDOW_RESIZABLE
110
+    );
111
+    cfail(!window, "create window", 1);
112
+    SDL_Renderer* ren = SDL_CreateRenderer(window, -1, 0);
113
+    cfail(!ren, "create renderer", 1);
114
+
115
+    /* make images into textures */
116
+    SDL_Texture** itex = calloc(fc, sizeof (SDL_Texture*));
117
+    for (int i = 0; i < fc; i++) {
118
+        itex[i] = SDL_CreateTextureFromSurface(ren, isurf[i]);
119
+        cfail(!itex[i], "create texture", 1);
120
+        SDL_FreeSurface(isurf[i]);
121
+    }
122
+    SDL_Texture* act = SDL_CreateTextureFromSurface(ren, acs);
123
+    cfail(!act, "create surface", 1);
124
+    SDL_FreeSurface(acs);
125
+
126
+    /* background (transparent black) */
127
+    cfail(SDL_SetRenderDrawColor(ren, 0, 0, 0, 0), "set colour", 1);
128
+
129
+    SDL_Event ev;
130
+    /* time measurements for framerate limitation */
131
+    uint32_t ta = SDL_GetTicks();
132
+    uint32_t tb;
133
+    int r = 1;
134
+    /* only bother to redraw if an event that requires it is found */
135
+    int ef;
136
+    /* window size */
137
+    int ww, wh;
138
+    /* zoom level */
139
+    float zoom = 0;
140
+    /* pan position (centre of image) */
141
+    int px = iw / 2;
142
+    int py = ih / 2;
143
+    /* keep track of mouse coordinates */
144
+    int mx = 0;
145
+    int my = 0;
146
+
147
+    while (r) {
148
+        SDL_GetWindowSize(window, &ww, &wh);
149
+        ef = 0;
150
+        while (SDL_PollEvent(&ev)) {
151
+            switch (ev.type) {
152
+
153
+                case SDL_KEYUP:
154
+                    int iic = 0;
155
+                    switch (ev.key.keysym.sym) {
156
+                        /* bring image back into focus when Space is pressed */
157
+                        case SDLK_SPACE:
158
+                            px = ww / 2;
159
+                            py = wh / 2;
160
+                            ef = 1;
161
+                            break;
162
+                        /* exit when Esc is pressed */
163
+                        case SDLK_ESCAPE:
164
+                            r = 0;
165
+                            break;
166
+                        /* navigate image list with arrow keys */
167
+                        case SDLK_LEFT:
168
+                        case SDLK_UP:
169
+                            if (ci > 0) {
170
+                                ci--;
171
+                                iic = 1;
172
+                            }
173
+                            break;
174
+                        case SDLK_RIGHT:
175
+                        case SDLK_DOWN:
176
+                            if (ci < fc - 1) {
177
+                                ci++;
178
+                                iic = 1;
179
+                            }
180
+                            break;
181
+                    }
182
+                    if (iic) {
183
+                        cfail(SDL_QueryTexture(itex[ci], NULL, NULL, &iw, &ih), "query texture", 1);
184
+                        zoom = 0;
185
+                        px = iw / 2;
186
+                        py = ih / 2;
187
+                        if (!ct) {
188
+                            free(title);
189
+                            title = malloc(strlen(argv[optind + ci]) + 32);
190
+                            cfail(!title, "allocate memory", 0);
191
+                            sprintf(title, "iv: %s (%dx%d)", argv[optind + ci], iw, ih);
192
+                            SDL_SetWindowTitle(window, title);
193
+                        }
194
+                        ef = 1;
195
+                    }
196
+                    break;
197
+
198
+                case SDL_MOUSEBUTTONDOWN:
199
+                    /* output coordinates of cursor in image on middle-click */
200
+                    if (ev.button.button == 2) {
201
+                        printf(
202
+                            "%.0f, %.0f\n",
203
+                            ZSH((float)ev.button.x - (float)px + ZSH(iw, zoom - 1.0), -zoom),
204
+                            ZSH((float)ev.button.y - (float)py + ZSH(ih, zoom - 1.0), -zoom)
205
+                        );
206
+                        fflush(stdout);
207
+                    }
208
+                    /* bring image back into focus on right-click */
209
+                    if (ev.button.button == 3) {
210
+                        px = ww / 2;
211
+                        py = wh / 2;
212
+                        ef = 1;
213
+                    }
214
+                    break;
215
+
216
+                case SDL_MOUSEMOTION:
217
+                    /* pan-dragging if mouse is down */
218
+                    if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(1)) {
219
+                        int sw = ZSH(iw, zoom - 1.0);
220
+                        int sh = ZSH(ih, zoom - 1.0);
221
+                        /* clamp panning to edges of window,
222
+                        unless zoomed image is bigger than window */
223
+                        if (2 * sw > ww) {
224
+                            px += ev.motion.xrel;
225
+                        } else {
226
+                            px = CLAMP(sw, px + ev.motion.xrel, ww - sw);
227
+                        }
228
+                        if (2 * sh > wh) {
229
+                            py += ev.motion.yrel;
230
+                        } else {
231
+                            py = CLAMP(sh, py + ev.motion.yrel, wh - sh);
232
+                        }
233
+                        ef = 1;
234
+                    }
235
+                    mx = ev.motion.x;
236
+                    my = ev.motion.y;
237
+                    break;
238
+
239
+                case SDL_MOUSEWHEEL:
240
+                    /* scrolling changes zoom */
241
+                    zoom += DZDS * ev.wheel.y;
242
+                    px = mx - ZSH((mx - px), DZDS * ev.wheel.y);
243
+                    py = my - ZSH((my - py), DZDS * ev.wheel.y);
244
+                    ef = 1;
245
+                    break;
246
+
247
+                case SDL_QUIT:
248
+                    /* exit upon a quit signal */
249
+                    r = 0;
250
+                    break;
251
+
252
+                case SDL_WINDOWEVENT:
253
+                    switch (ev.window.event) {
254
+                        /* exit when window is closed */
255
+                        case SDL_WINDOWEVENT_CLOSE:
256
+                            r = 0;
257
+                            break;
258
+                        /* redraw required */
259
+                        case SDL_WINDOWEVENT_EXPOSED:
260
+                            ef = 1;
261
+                            break;
262
+                    }
263
+                    break;
264
+
265
+            }
266
+        }
267
+
268
+        if (ef) {
269
+            SDL_GetWindowSize(window, &ww, &wh);
270
+            /* clear to background colour */
271
+            cfail(SDL_RenderClear(ren), "clear screen", 1);
272
+            /* alpha checker background */
273
+            for (int i = 0; i < ww; i += APS * cw) {
274
+                for (int j = 0; j < wh; j += APS * ch) {
275
+                    cfail(SDL_RenderCopy(ren, act, NULL, &(SDL_Rect){
276
+                        .x = i, .y = j, .w = APS * cw, .h = APS * ch
277
+                    }), "draw checker background", 1);
278
+                }
279
+            }
280
+            /* render image */
281
+            cfail(SDL_RenderCopy(ren, itex[ci], NULL, &(SDL_Rect){
282
+                .x = px - ZSH(iw, zoom - 1.0), .y = py - ZSH(ih, zoom - 1.0),
283
+                .w = ZSH(iw, zoom), .h = ZSH(ih, zoom)
284
+            }), "display image", 1);
285
+            SDL_RenderPresent(ren);
286
+        }
287
+
288
+        /* limit framerate */
289
+        tb = SDL_GetTicks();
290
+        if (tb - ta < FRAMETIME) {
291
+            SDL_Delay(FRAMETIME - (tb - ta));
292
+        }
293
+        ta = tb;
294
+    }
295
+
296
+    SDL_DestroyWindow(window);
297
+    return 0;
298
+}
0 299