Files
SimpleImageViewer/SimpleImageViewer.c
T
Ano-sys a395470d88 + image scaling on mouse scroll
+ window resize redraws surface
+ moved window loop into own function
- removed on_wheel call to resize window for now -> hyprland is not handling this
+ image is not initially drawn onto displayed surface anymore, a subsurface is used (image_surface) to allow scaling on the real surface without redrawing the image in buffer.
+ renamed free_image to free_image_buffer to be more clear about the functionallity
+ renamed draw_image to create_image_surface
+ draw_image is a new function which draws the image_surface with linear scale onto the windows surface
2026-08-15 17:44:43 +02:00

252 lines
7.6 KiB
C

#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_pixels.h>
#include <stdio.h>
#include <unistd.h>
#include <SDL3/SDL.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_mouse.h>
#include <SDL3/SDL_keyboard.h>
#include <SDL3/SDL_events.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define DESIRED_IMAGE_CHANNELS 4
#define MAX_IMAGE_SCALE 10
#define MIN_IMAGE_SCALE 0.01
typedef struct Image{
int x;
int y;
int channels;
unsigned char* pixels;
} Image;
typedef struct{
int w;
int h;
} window_size_delta;
Image load_image(const char* filepath){
Image image;
image.pixels = stbi_load(filepath, &image.x, &image.y, &image.channels, DESIRED_IMAGE_CHANNELS);
return image;
}
void free_image_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){
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);
/* Used when a larger window is wanted, image displaying is dynamically calculated
int display_count;
SDL_DisplayID* displays = SDL_GetDisplays(&display_count);
if(display_count == 0){
printf("%s\n", SDL_GetError());
printf("Could not get any displays!");
exit(5);
}
const SDL_DisplayMode* mode = SDL_GetDesktopDisplayMode(displays[0]);
if(!mode){
printf("Failed to get display information!");
exit(6);
}
*/
SDL_Window* window = SDL_CreateWindow(title, w, h, window_flags);
SDL_SetWindowAspectRatio(window, (float)w / (float)h, (float)w / (float)h);
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);
SDL_Rect pixel = { 0, 0, 1, 1 };
int center_x = ((surface->w + 1) / 2 - 1);
int center_y = ((surface->h + 1) / 2 - 1);
int dst_x = (center_x - image->x / 2);
int dst_y = (center_y - image->y / 2);
for(int x = 0; x < image->x; x++){
pixel.x = dst_x + x;
for(int y = 0; y < image->y; y++){
pixel.y = dst_y + y;
const int current_pixel_idx = (y * image->x + x) * DESIRED_IMAGE_CHANNELS;
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);
}
}
return image_surface;
}
void draw_image(SDL_Window* window, SDL_Surface* image_surface, Image* image, float pixel_size_delta){
SDL_Surface* surface = SDL_GetWindowSurface(window);
float pixel_size = pixel_size_delta;
int center_x = ((surface->w + 1) / 2 - 1);
int center_y = ((surface->h + 1) / 2 - 1);
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);
printf("Redraw time: %.3fms\n", (SDL_GetTicksNS() - start) / 1000000.0);
SDL_UpdateWindowSurface(window);
}
int clamp(const int in, const int low, const int high){
return in < low ? low : in > high ? high : in;
}
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){
int last_w, last_h;
SDL_GetWindowSize(window, &last_w, &last_h);
int multiplier = 25;
int step = event->wheel.y * multiplier;
int new_w = clamp_low(last_w + step, 50);
int new_h = new_w * last_h / last_w;
int dx = new_w - last_w;
int dy = new_h - last_h;
int x, y;
SDL_GetWindowPosition(window, &x, &y);
x -= dx / 2;
y -= dy / 2;
SDL_SetWindowPosition(window, x, y);
SDL_SetWindowSize(window, new_w, new_h);
}
void loop(SDL_Window* window, SDL_Surface* image_surface, Image* image){
bool drag_mouse = false;
int last_pointer_x = 0;
int last_pointer_y = 0;
SDL_Event event;
bool run = true;
float pixel_size_delta = 1.0f;
bool redraw = false;
while(run){
if(!SDL_WaitEvent(&event)) continue;
do{
if (event.type == SDL_EVENT_KEY_UP){
switch(event.key.key){
case SDLK_ESCAPE: case SDLK_SPACE: case SDLK_RETURN:
run = false;
break;
}
}
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;
last_pointer_x = (int)event.button.x;
last_pointer_y = (int)event.button.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){
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(event.type == SDL_EVENT_MOUSE_WHEEL){
float last_pixel_size_delta = pixel_size_delta;
pixel_size_delta += event.wheel.y * 0.1f * pixel_size_delta;
if(pixel_size_delta < MIN_IMAGE_SCALE) pixel_size_delta = MIN_IMAGE_SCALE;
else if(pixel_size_delta > MAX_IMAGE_SCALE) pixel_size_delta = MAX_IMAGE_SCALE;
if(last_pixel_size_delta != pixel_size_delta) redraw = true;
// on_wheel(&event, window);
}
if(event.type == SDL_EVENT_WINDOW_RESIZED) redraw = true;
} while(SDL_PollEvent(&event));
if(redraw){
redraw = false;
draw_image(window, image_surface, image, pixel_size_delta);
}
}
}
int main(int argc, char** argv){
if(argc < 2){
printf("Usage: %s [image filepath]\n", argv[0]);
exit(1);
}
const char* filepath = argv[1];
if(access(filepath, F_OK) != 0){
printf("File does not exist!\n");
exit(2);
}
Image image = load_image(filepath);
if(image.pixels == NULL){
printf("Failed to load image!");
exit(3);
}
SDL_Window* window = create_window("SimpleImageViewer", image.x, image.y);
if(!window){
printf("Failed to create window!");
exit(4);
}
SDL_Surface* surface = SDL_GetWindowSurface(window);
if(!surface){
printf("Failed to get surface!");
exit(5);
}
SDL_Surface* image_surface = create_image_surface(surface, &image);
free_image_buffer(&image);
draw_image(window, image_surface, &image, 1.0f);
SDL_UpdateWindowSurface(window);
loop(window, image_surface, &image);
SDL_DestroySurface(image_surface);
SDL_DestroyWindow(window);
}