Compare commits
10 Commits
a395470d88
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 16b08862fa | |||
| 6b876422c8 | |||
| c1dd386835 | |||
| 9dc32c854b | |||
| 93e400304d | |||
| b315a55dae | |||
| b5361d8e21 | |||
| 2a360721b6 | |||
| 9297b4c062 | |||
| 3487bdfb1f |
@@ -2,27 +2,48 @@
|
|||||||
|
|
||||||
> View images fast and without any hocus pocus, sometimes less is better
|
> View images fast and without any hocus pocus, sometimes less is better
|
||||||
|
|
||||||
## Disclaimer
|
## About
|
||||||
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.
|
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
|
## Requirements
|
||||||
|
|
||||||
- SDL3
|
- SDL3
|
||||||
|
|
||||||
## Installations
|
## 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`
|
- `brew install sdl3`
|
||||||
- clone this repo and navigate into it
|
- clone this repo and navigate into it
|
||||||
- `gcc SimpleImageViewer.c -o SimpleImageViewer -Wall -Werror $(pkg-config --cflags --libs sdl3)` or with `clang`
|
- `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`
|
- `sudo pacman -S sdl3 --noconfirm`
|
||||||
- `gcc SimpleImageViewer.c -o SimpleImageViewer -Wall -Werror $(pkg-config --cflags --libs sdl3) -lm` or with `clang`
|
- `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/`
|
- 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/`
|
- 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
|
- 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`
|
||||||
|
|||||||
+109
-53
@@ -20,29 +20,36 @@ typedef struct Image{
|
|||||||
int y;
|
int y;
|
||||||
int channels;
|
int channels;
|
||||||
unsigned char* pixels;
|
unsigned char* pixels;
|
||||||
|
SDL_Surface* surface;
|
||||||
|
SDL_Texture* texture;
|
||||||
} Image;
|
} Image;
|
||||||
|
|
||||||
typedef struct{
|
void error_exit(const char* msg){
|
||||||
int w;
|
printf("%s\n", msg);
|
||||||
int h;
|
exit(-1);
|
||||||
} window_size_delta;
|
}
|
||||||
|
|
||||||
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 image;
|
||||||
image.pixels = stbi_load(filepath, &image.x, &image.y, &image.channels, DESIRED_IMAGE_CHANNELS);
|
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;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void free_image_buffer(Image* image){
|
void free_image_pixels_buffer(Image* image){
|
||||||
if(image->pixels) stbi_image_free(image->pixels);
|
if(image->pixels) stbi_image_free(image->pixels);
|
||||||
image->pixels = NULL;
|
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_InitFlags init_flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS;
|
||||||
const SDL_WindowFlags window_flags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE;
|
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
|
/* Used when a larger window is wanted, image displaying is dynamically calculated
|
||||||
int display_count;
|
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_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;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* create_image_surface(SDL_Surface* surface, Image* image){
|
static bool populate_image(SDL_Window* window, Image* image){
|
||||||
SDL_Surface* image_surface = SDL_CreateSurface(image->x, image->y, SDL_PIXELFORMAT_RGBA32);
|
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 };
|
SDL_Rect pixel = { 0, 0, 1, 1 };
|
||||||
|
|
||||||
int center_x = ((surface->w + 1) / 2 - 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;
|
pixel.y = dst_y + y;
|
||||||
const int current_pixel_idx = (y * image->x + x) * DESIRED_IMAGE_CHANNELS;
|
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 + 0],
|
||||||
image->pixels[current_pixel_idx + 1],
|
image->pixels[current_pixel_idx + 1],
|
||||||
image->pixels[current_pixel_idx + 2],
|
image->pixels[current_pixel_idx + 2],
|
||||||
image->pixels[current_pixel_idx + 3]);
|
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);
|
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;
|
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();
|
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_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_ClearSurface(surface, 0, 0, 0, 255);
|
SDL_Renderer* renderer = SDL_GetRenderer(window);
|
||||||
SDL_BlitSurfaceScaled(image_surface, NULL, surface, &dst, SDL_SCALEMODE_LINEAR);
|
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);
|
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){
|
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;
|
return in < low ? low : in;
|
||||||
}
|
}
|
||||||
|
|
||||||
window_size_delta get_window_size_delta(SDL_Window* window, int new_w, int new_h){
|
static __attribute__((unused)) void on_wheel (SDL_Event* event, SDL_Window* window){
|
||||||
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){
|
|
||||||
int last_w, last_h;
|
int last_w, last_h;
|
||||||
SDL_GetWindowSize(window, &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);
|
SDL_SetWindowSize(window, new_w, new_h);
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
|
static void loop(SDL_Window* window, Image* image){
|
||||||
bool drag_mouse = false;
|
bool drag_left_mouse = false;
|
||||||
|
bool drag_right_mouse = false;
|
||||||
int last_pointer_x = 0;
|
int last_pointer_x = 0;
|
||||||
int last_pointer_y = 0;
|
int last_pointer_y = 0;
|
||||||
|
|
||||||
@@ -161,6 +195,8 @@ void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
|
|||||||
bool run = true;
|
bool run = true;
|
||||||
|
|
||||||
float pixel_size_delta = 1.0f;
|
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;
|
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_QUIT || event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED) run = false;
|
||||||
if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT){
|
if(event.type == SDL_EVENT_MOUSE_BUTTON_DOWN){
|
||||||
drag_mouse = true;
|
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_x = (int)event.button.x;
|
||||||
last_pointer_y = (int)event.button.y;
|
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_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT) drag_left_mouse = false;
|
||||||
if(event.type == SDL_EVENT_MOUSE_MOTION && drag_mouse){
|
if(event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_RIGHT) drag_right_mouse = false;
|
||||||
int last_x, last_y;
|
if(event.type == SDL_EVENT_MOUSE_MOTION){
|
||||||
SDL_GetWindowPosition(window, &last_x, &last_y);
|
if(drag_left_mouse){
|
||||||
SDL_SetWindowPosition(window, last_x + (int)event.motion.x - last_pointer_x, last_y + (int)event.motion.y - last_pointer_y);
|
int last_x, last_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){
|
if(event.type == SDL_EVENT_MOUSE_WHEEL){
|
||||||
float last_pixel_size_delta = pixel_size_delta;
|
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){
|
if(redraw){
|
||||||
redraw = false;
|
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);
|
exit(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
Image image = load_image(filepath);
|
Image image = load_image_pixels_from_file(filepath);
|
||||||
if(image.pixels == NULL){
|
if(image.pixels == NULL){
|
||||||
printf("Failed to load image!");
|
printf("Failed to load image!");
|
||||||
exit(3);
|
exit(3);
|
||||||
@@ -229,24 +281,28 @@ int main(int argc, char** argv){
|
|||||||
SDL_Window* window = create_window("SimpleImageViewer", image.x, image.y);
|
SDL_Window* window = create_window("SimpleImageViewer", image.x, image.y);
|
||||||
if(!window){
|
if(!window){
|
||||||
printf("Failed to create window!");
|
printf("Failed to create window!");
|
||||||
|
free_image_pixels_buffer(&image);
|
||||||
exit(4);
|
exit(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* surface = SDL_GetWindowSurface(window);
|
if(!populate_image(window, &image)){
|
||||||
if(!surface){
|
printf("Failed to populate image!");
|
||||||
printf("Failed to get surface!");
|
free_image_pixels_buffer(&image);
|
||||||
exit(5);
|
SDL_DestroyWindow(window);
|
||||||
|
exit(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_image_pixels_buffer(&image);
|
||||||
|
|
||||||
|
if(!draw_image(window, &image, 1.0f, 0, 0)){
|
||||||
|
printf("Failed to draw image!");
|
||||||
|
SDL_DestroyWindow(window);
|
||||||
|
exit(7);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* image_surface = create_image_surface(surface, &image);
|
loop(window, &image);
|
||||||
free_image_buffer(&image);
|
|
||||||
|
|
||||||
draw_image(window, image_surface, &image, 1.0f);
|
|
||||||
|
|
||||||
SDL_UpdateWindowSurface(window);
|
SDL_DestroyTexture(image.texture);
|
||||||
|
SDL_DestroySurface(image.surface);
|
||||||
loop(window, image_surface, &image);
|
|
||||||
|
|
||||||
SDL_DestroySurface(image_surface);
|
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
viewer=
|
viewer="/path/to/SimpleImageViewer"
|
||||||
|
|
||||||
notify() {
|
notify() {
|
||||||
if [ -x /usr/bin/notify-send ]; then
|
if [ -x /usr/bin/notify-send ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user