#include #include #include #include #include #include #include #include #include #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; SDL_Surface* surface; SDL_Texture* texture; } Image; typedef struct{ int w; int h; } window_size_delta; static void error_exit(const char* msg){ printf("%s\n", msg); exit(-1); } static 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_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){ const SDL_InitFlags init_flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS; const SDL_WindowFlags window_flags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE; 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; 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); 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; } 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); 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]); if(!SDL_FillSurfaceRect(image->surface, &pixel, color)) error_continue("Failed to fill surface rectangle! Trying to continue..."); } } 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; } 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; int center_x = ((surface->w + 1) / 2 - 1); int center_y = ((surface->h + 1) / 2 - 1); Uint64 start = SDL_GetTicksNS(); 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); return true; } 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, Image* image){ bool drag_left_mouse = false; bool drag_right_mouse = false; int last_pointer_x = 0; int last_pointer_y = 0; SDL_Event event; 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; 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){ 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_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; 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; 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; 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..."); } } } 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_pixels_from_file(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!"); free_image_pixels_buffer(&image); exit(4); } if(!populate_image(window, &image)){ printf("Failed to populate image!"); free_image_pixels_buffer(&image); 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); } loop(window, &image); SDL_DestroyTexture(image.texture); SDL_DestroySurface(image.surface); SDL_DestroyWindow(window); }