Compare commits

...

10 Commits

Author SHA1 Message Date
Ano-sys 16b08862fa ~ udpated 2026-10-25 00:51:15 +02:00
Ano-sys 6b876422c8 Updated README.md 2026-08-15 23:00:29 +02:00
Ano-sys c1dd386835 removed extra whitespace 2026-08-15 22:59:36 +02:00
Ano-sys 9dc32c854b + made function that are not designed to be shared static
- removed obsolete window_size_delta struct and helper func
- marked on_wheel with attrib unused -> compiler would refuse compilation
2026-08-15 22:48:42 +02:00
Ano-sys 93e400304d - fixed typo
- removed SDL_GetWindowSurface from main, because the surface is not fixed and so not valid after resize -> forgoing check for availability is redundant
2026-08-15 22:42:23 +02:00
Ano-sys b315a55dae Added better error handling 2026-08-15 22:33:34 +02:00
Ano-sys b5361d8e21 Updated readme to be more clear which binary is ment for viewer flag in script 2026-08-15 19:06:59 +02:00
Ano-sys 2a360721b6 Updated README.md 2026-08-15 18:54:26 +02:00
Ano-sys 9297b4c062 + image struct stores pointer to images surface and texture now
+ replaced surface drawing via SDL_UpdateWindowSurface with SDL_Renderer calls -> much better ram usage since before, because SDL_Texture is used to store the drawn image instead the surface itself
2026-08-15 18:22:04 +02:00
Ano-sys 3487bdfb1f added right mouse button drag to move image on surface 2026-08-15 18:01:33 +02:00
3 changed files with 140 additions and 63 deletions
+30 -9
View File
@@ -2,27 +2,48 @@
> View images fast and without any hocus pocus, sometimes less is better
## Disclaimer
The inspiration was a YouTube video, I've watched the beginning and though to myself that this cannot be that hard to implement.
Of course it is not without usage, I will prepare the app to be hooked in Hyprland to launch the current photo stored in clipboard.
## About
The inspiration was a YouTube video. I watched the beginning and thought to myself that this cannot be that hard to implement.
A Hyprland integration is included to open an image from the clipboard, either as a file path or as image data.
## Disclaimer for Wayland / Hyprland users
Under Wayland, window placement is controlled by the compositor, so moving the window with the viewer's left-button drag does not work. The compositor may also override the requested window size. See the [SDL Wayland notes](https://wiki.libsdl.org/SDL3/README-wayland) for details.
Mouse-wheel zoom and right-button dragging operate on the image inside the window. On Hyprland, use your configured compositor bindings to move or resize the window, and configure it as floating if you want free window placement.
## Requirements
- SDL3
## Installations
> As of now the only like-indented working implementation is for macOS. Wayland ignores the desired window states (resize, position).
#### macOS
### macOS
- `brew install sdl3`
- clone this repo and navigate into it
- `gcc SimpleImageViewer.c -o SimpleImageViewer -Wall -Werror $(pkg-config --cflags --libs sdl3)` or with `clang`
- additional compiler arguments for ide: `-I/opt/homebrew/include -L/opt/homebrew/lib -Wl,-rpath,/opt/homebrew/lib -lSDL3 `
- additional compiler arguments for ide: `-I/opt/homebrew/include -L/opt/homebrew/lib -Wl,-rpath,/opt/homebrew/lib -lSDL3`
### Linux
#### Linux
- `sudo pacman -S sdl3 --noconfirm`
- `gcc SimpleImageViewer.c -o SimpleImageViewer -Wall -Werror $(pkg-config --cflags --libs sdl3) -lm` or with `clang`
- when using Hyprland, move the scripts folder into your `~/.config/hypr/`
- in `simple-image-viewer-clipboard.sh`: set `viewer`, I placed my binary beside this script
- in `simple-image-viewer-clipboard.sh`: set `viewer` to the binary path gcc created, I placed my binary beside this script
- move the `hypr_simpleimageviewer.lua` file into `~/.config/hypr/`
- replace [USER] with your user
- open the file and replace [USER] with your user
- add `require(hypr_simpleimageviewer)` at the top of your Hyprland config
## Usage
If installed via Hyprland binding, `SUPER + I` is used to parse a file from clipboard (path or binary) as argument into SimpleImageViewer.
Via commandline `./SimpleImageViewer [path_to_image]`.
Mouse-/Keybinds:
- Close: `ESC, Enter, Space`
- Move image: press `right mouse button` and `drag`
- Zoom image: `scroll mouse wheel`
+105 -49
View File
@@ -20,29 +20,36 @@ typedef struct Image{
int y;
int channels;
unsigned char* pixels;
SDL_Surface* surface;
SDL_Texture* texture;
} Image;
typedef struct{
int w;
int h;
} window_size_delta;
void error_exit(const char* msg){
printf("%s\n", msg);
exit(-1);
}
Image load_image(const char* filepath){
void error_continue(const char* msg){
printf("%s\n", msg);
}
Image load_image_pixels_from_file(const char* filepath){
Image image;
image.pixels = stbi_load(filepath, &image.x, &image.y, &image.channels, DESIRED_IMAGE_CHANNELS);
if(!image.pixels) error_exit("Failed to load image from file!");
return image;
}
void free_image_buffer(Image* image){
void free_image_pixels_buffer(Image* image){
if(image->pixels) stbi_image_free(image->pixels);
image->pixels = NULL;
}
SDL_Window* create_window(const char* title, int w, int h){
static SDL_Window* create_window(const char* title, int w, int h){
const SDL_InitFlags init_flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
const SDL_WindowFlags window_flags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE;
SDL_Init(init_flags);
if(!SDL_Init(init_flags)) error_exit("Failed to initialize SDL!");
/* Used when a larger window is wanted, image displaying is dynamically calculated
int display_count;
@@ -61,13 +68,20 @@ SDL_Window* create_window(const char* title, int w, int h){
*/
SDL_Window* window = SDL_CreateWindow(title, w, h, window_flags);
SDL_SetWindowAspectRatio(window, (float)w / (float)h, (float)w / (float)h);
if(!window) error_exit("Failed to create window!");
if(SDL_SetWindowAspectRatio(window, (float)w / (float)h, (float)w / (float)h)) error_continue("Failed to set window aspect ratio! Continuing...");
return window;
}
SDL_Surface* create_image_surface(SDL_Surface* surface, Image* image){
SDL_Surface* image_surface = SDL_CreateSurface(image->x, image->y, SDL_PIXELFORMAT_RGBA32);
static bool populate_image(SDL_Window* window, Image* image){
SDL_Surface* surface = SDL_GetWindowSurface(window);
image->surface = SDL_CreateSurface(image->x, image->y, SDL_PIXELFORMAT_RGBA32);
if(!image->surface) {
error_continue("Failed to create image surface! Cleaning up and exiting...");
return false;
}
SDL_Rect pixel = { 0, 0, 1, 1 };
int center_x = ((surface->w + 1) / 2 - 1);
@@ -82,21 +96,32 @@ SDL_Surface* create_image_surface(SDL_Surface* surface, Image* image){
pixel.y = dst_y + y;
const int current_pixel_idx = (y * image->x + x) * DESIRED_IMAGE_CHANNELS;
Uint32 color = SDL_MapSurfaceRGBA(image_surface,
Uint32 color = SDL_MapSurfaceRGBA(image->surface,
image->pixels[current_pixel_idx + 0],
image->pixels[current_pixel_idx + 1],
image->pixels[current_pixel_idx + 2],
image->pixels[current_pixel_idx + 3]);
SDL_FillSurfaceRect(image_surface, &pixel, color);
if(!SDL_FillSurfaceRect(image->surface, &pixel, color)) error_continue("Failed to fill surface rectangle! Trying to continue...");
}
}
return image_surface;
image->texture = SDL_CreateTextureFromSurface(SDL_GetRenderer(window), image->surface);
if(!image->texture){
error_continue("Failed to create texture from surface! Cleaning up and exiting...");
SDL_DestroySurface(image->surface);
return false;
};
return true;
}
void draw_image(SDL_Window* window, SDL_Surface* image_surface, Image* image, float pixel_size_delta){
static bool draw_image(SDL_Window* window, Image* image, float pixel_size_delta, int image_start_x, int image_start_y){
SDL_Surface* surface = SDL_GetWindowSurface(window);
if(!surface){
error_continue("Failed to get window surface! Cleaning up and exiting...");
return false;
}
float pixel_size = pixel_size_delta;
@@ -105,13 +130,29 @@ void draw_image(SDL_Window* window, SDL_Surface* image_surface, Image* image, fl
Uint64 start = SDL_GetTicksNS();
SDL_Rect dst = { center_x - (image->x * pixel_size / 2), center_y - (image->y * pixel_size / 2), image->x * pixel_size, image->y * pixel_size };
SDL_ClearSurface(surface, 0, 0, 0, 255);
SDL_BlitSurfaceScaled(image_surface, NULL, surface, &dst, SDL_SCALEMODE_LINEAR);
SDL_FRect dst = { center_x - (image->x * pixel_size / 2) + image_start_x, center_y - (image->y * pixel_size / 2) + image_start_y, image->x * pixel_size, image->y * pixel_size };
SDL_Renderer* renderer = SDL_GetRenderer(window);
if(!renderer){
error_continue("Failed to get window renderer! Cleaning up and exiting...");
return false;
}
if(!SDL_RenderClear(renderer)){
error_continue("Failed to clear renderer! Cleaning up and exiting...");
return false;
}
if(!SDL_RenderTexture(renderer, image->texture, NULL, &dst)){
error_continue("Failed to render texture! Cleaning up and exiting...");
return false;
}
if(!SDL_RenderPresent(renderer)){
error_continue("Failed to present renderer! Cleaning up and exiting...");
return false;
}
printf("Redraw time: %.3fms\n", (SDL_GetTicksNS() - start) / 1000000.0);
SDL_UpdateWindowSurface(window);
return true;
}
int clamp(const int in, const int low, const int high){
@@ -122,15 +163,7 @@ int clamp_low(const int in, const int low){
return in < low ? low : in;
}
window_size_delta get_window_size_delta(SDL_Window* window, int new_w, int new_h){
int last_w, last_h;
SDL_GetWindowSize(window, &last_w, &last_h);
window_size_delta delta = {new_w - last_w, new_h - last_h};
return delta;
}
void on_wheel(SDL_Event* event, SDL_Window* window){
static __attribute__((unused)) void on_wheel (SDL_Event* event, SDL_Window* window){
int last_w, last_h;
SDL_GetWindowSize(window, &last_w, &last_h);
@@ -152,8 +185,9 @@ void on_wheel(SDL_Event* event, SDL_Window* window){
SDL_SetWindowSize(window, new_w, new_h);
}
void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
bool drag_mouse = false;
static void loop(SDL_Window* window, Image* image){
bool drag_left_mouse = false;
bool drag_right_mouse = false;
int last_pointer_x = 0;
int last_pointer_y = 0;
@@ -161,6 +195,8 @@ void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
bool run = true;
float pixel_size_delta = 1.0f;
int current_image_start_x = 0, current_image_start_y = 0;
int image_start_at_drag_x = 0, image_start_at_drag_y = 0;
bool redraw = false;
@@ -176,16 +212,32 @@ void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
}
}
if(event.type == SDL_EVENT_QUIT || event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) run = false;
if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT){
drag_mouse = true;
if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN){
if(event.button.button == SDL_BUTTON_LEFT) drag_left_mouse = true;
if(event.button.button == SDL_BUTTON_RIGHT) drag_right_mouse = true;
last_pointer_x = (int)event.button.x;
last_pointer_y = (int)event.button.y;
image_start_at_drag_x = current_image_start_x;
image_start_at_drag_y = current_image_start_y;
}
if(event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) drag_mouse = false;
if(event.type == SDL_EVENT_MOUSE_MOTION && drag_mouse){
if(event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) drag_left_mouse = false;
if(event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_RIGHT) drag_right_mouse = false;
if(event.type == SDL_EVENT_MOUSE_MOTION){
if(drag_left_mouse){
int last_x, last_y;
SDL_GetWindowPosition(window, &last_x, &last_y);
SDL_SetWindowPosition(window, last_x + (int)event.motion.x - last_pointer_x, last_y + (int)event.motion.y - last_pointer_y);
if(!SDL_GetWindowPosition(window, &last_x, &last_y)) error_continue("Failed to get window position!");
else if(!SDL_SetWindowPosition(window, last_x + (int)event.motion.x - last_pointer_x, last_y + (int)event.motion.y - last_pointer_y)) error_continue("Failed to set window position!");
}
if(drag_right_mouse){
int image_start_x = (int)event.motion.x - last_pointer_x + image_start_at_drag_x;
int image_start_y = (int)event.motion.y - last_pointer_y + image_start_at_drag_y;
current_image_start_x = image_start_x;
current_image_start_y = image_start_y;
redraw = true;
}
}
if(event.type == SDL_EVENT_MOUSE_WHEEL){
float last_pixel_size_delta = pixel_size_delta;
@@ -202,7 +254,7 @@ void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
if(redraw){
redraw = false;
draw_image(window, image_surface, image, pixel_size_delta);
if(!draw_image(window, image, pixel_size_delta, current_image_start_x, current_image_start_y)) error_continue("Failed to draw image! Trying to continue...");
}
}
}
@@ -220,7 +272,7 @@ int main(int argc, char** argv){
exit(2);
}
Image image = load_image(filepath);
Image image = load_image_pixels_from_file(filepath);
if(image.pixels == NULL){
printf("Failed to load image!");
exit(3);
@@ -229,24 +281,28 @@ int main(int argc, char** argv){
SDL_Window* window = create_window("SimpleImageViewer", image.x, image.y);
if(!window){
printf("Failed to create window!");
free_image_pixels_buffer(&image);
exit(4);
}
SDL_Surface* surface = SDL_GetWindowSurface(window);
if(!surface){
printf("Failed to get surface!");
exit(5);
if(!populate_image(window, &image)){
printf("Failed to populate image!");
free_image_pixels_buffer(&image);
SDL_DestroyWindow(window);
exit(6);
}
SDL_Surface* image_surface = create_image_surface(surface, &image);
free_image_buffer(&image);
free_image_pixels_buffer(&image);
draw_image(window, image_surface, &image, 1.0f);
if(!draw_image(window, &image, 1.0f, 0, 0)){
printf("Failed to draw image!");
SDL_DestroyWindow(window);
exit(7);
}
SDL_UpdateWindowSurface(window);
loop(window, &image);
loop(window, image_surface, &image);
SDL_DestroySurface(image_surface);
SDL_DestroyTexture(image.texture);
SDL_DestroySurface(image.surface);
SDL_DestroyWindow(window);
}
@@ -1,7 +1,7 @@
#!/bin/sh
set -eu
viewer=
viewer="/path/to/SimpleImageViewer"
notify() {
if [ -x /usr/bin/notify-send ]; then