From 3f41e5c821921bab907742c6039be0aaf2705562 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Wed, 9 Jul 2025 18:05:59 +0200 Subject: [PATCH] feat: added simple image viewer --- C/imageViewer/Makefile | 72 +++++++++ C/imageViewer/README.md | 148 +++++++++++++++++++ C/imageViewer/main.c | 317 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 537 insertions(+) create mode 100644 C/imageViewer/Makefile create mode 100644 C/imageViewer/README.md create mode 100644 C/imageViewer/main.c diff --git a/C/imageViewer/Makefile b/C/imageViewer/Makefile new file mode 100644 index 0000000..a26df9c --- /dev/null +++ b/C/imageViewer/Makefile @@ -0,0 +1,72 @@ +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -O2 +LIBS = -lSDL2 -lSDL2_image -lm +TARGET = imageviewer +SOURCE = main.c + +# Check if pkg-config is available for SDL2 +SDL2_CFLAGS := $(shell pkg-config --cflags sdl2 2>/dev/null) +SDL2_LIBS := $(shell pkg-config --libs sdl2 2>/dev/null) + +# If pkg-config found SDL2, use it +ifneq ($(SDL2_CFLAGS),) + CFLAGS += $(SDL2_CFLAGS) + LIBS = $(SDL2_LIBS) -lSDL2_image -lm +endif + +.PHONY: all clean install deps help + +all: $(TARGET) + +$(TARGET): $(SOURCE) + $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(LIBS) + +clean: + rm -f $(TARGET) + +# Install dependencies on Ubuntu/Debian +deps-debian: + sudo apt-get update + sudo apt-get install libsdl2-dev libsdl2-image-dev + +# Install dependencies on Fedora/RHEL/CentOS +deps-fedora: + sudo dnf install SDL2-devel SDL2_image-devel + +# Install dependencies on Arch Linux +deps-arch: + sudo pacman -S sdl2 sdl2_image + +# Run with a test image +test: + @if [ -f $(TARGET) ]; then \ + echo "Looking for test images..."; \ + if [ -d "../misc/randomJPG/14k" ] && [ -n "$$(ls ../misc/randomJPG/14k/*.jpg 2>/dev/null | head -1)" ]; then \ + echo "Running with test image from randomJPG folder..."; \ + ./$(TARGET) $$(ls ../misc/randomJPG/14k/*.jpg | head -1); \ + else \ + echo "No test images found. Please run: ./$(TARGET) "; \ + fi \ + else \ + echo "Build the project first: make"; \ + fi + +help: + @echo "JPG Image Viewer - Makefile targets:" + @echo " all - Build the image viewer" + @echo " clean - Remove built files" + @echo " test - Run with a test image (if available)" + @echo " deps-debian - Install dependencies on Ubuntu/Debian" + @echo " deps-fedora - Install dependencies on Fedora/RHEL/CentOS" + @echo " deps-arch - Install dependencies on Arch Linux" + @echo " help - Show this help" + @echo "" + @echo "Usage: ./$(TARGET) " + @echo "" + @echo "Controls:" + @echo " Mouse wheel / +/- : Zoom in/out" + @echo " Mouse drag : Pan image" + @echo " R : Reset zoom and position" + @echo " F : Fit image to window" + @echo " H : Show help" + @echo " ESC/Q : Quit" diff --git a/C/imageViewer/README.md b/C/imageViewer/README.md new file mode 100644 index 0000000..ecff20f --- /dev/null +++ b/C/imageViewer/README.md @@ -0,0 +1,148 @@ +# JPG Image Viewer + +A simple, lightweight image viewer written in C using SDL2. Supports JPG, PNG, BMP, GIF, and TIF formats with zooming, panning, and basic image navigation features. + +## Features + +- **Multi-format support**: JPG, JPEG, PNG, BMP, GIF, TIF +- **Zoom functionality**: Mouse wheel or keyboard controls +- **Pan support**: Click and drag to move around zoomed images +- **Auto-fit**: Automatically fits large images to window +- **Responsive**: Resizable window with real-time updates +- **Keyboard shortcuts**: Quick controls for common operations + +## Dependencies + +The image viewer requires SDL2 and SDL2_image libraries. + +### Ubuntu/Debian +```bash +sudo apt-get update +sudo apt-get install libsdl2-dev libsdl2-image-dev +``` + +### Fedora/RHEL/CentOS +```bash +sudo dnf install SDL2-devel SDL2_image-devel +``` + +### Arch Linux +```bash +sudo pacman -S sdl2 sdl2_image +``` + +### macOS (with Homebrew) +```bash +brew install sdl2 sdl2_image +``` + +## Building + +1. Install dependencies (see above) +2. Build the project: +```bash +make +``` + +Or use the dependency helper: +```bash +make deps-debian # For Ubuntu/Debian +make deps-fedora # For Fedora/RHEL/CentOS +make deps-arch # For Arch Linux +make +``` + +## Usage + +```bash +./imageviewer +``` + +Example: +```bash +./imageviewer photo.jpg +./imageviewer ../misc/randomJPG/14k/bloated_image_1.jpg +``` + +## Controls + +| Control | Action | +|---------|--------| +| **Mouse wheel** | Zoom in/out | +| **+ / -** | Zoom in/out (keyboard) | +| **Mouse drag** | Pan around the image | +| **R** | Reset zoom and position to default | +| **F** | Fit image to window | +| **H** | Show help in console | +| **ESC / Q** | Quit the application | + +## Features in Detail + +### Zooming +- Use mouse wheel to zoom in/out at the mouse cursor position +- Keyboard shortcuts: `+` to zoom in, `-` to zoom out +- Zoom range: 0.1x to 10x +- Smart zoom behavior focuses on mouse position + +### Panning +- Click and drag with left mouse button to move around zoomed images +- Smooth panning for precise positioning +- Works at any zoom level + +### Auto-fit +- Large images are automatically scaled to fit the window when first loaded +- Press `F` to manually fit the current image to window +- Maintains aspect ratio + +### Window Management +- Resizable window that adapts to content +- Real-time rendering updates +- Dark background for better image contrast + +## Technical Details + +- **Language**: C (C99 standard) +- **Graphics**: SDL2 for window management and rendering +- **Image loading**: SDL2_image for multi-format support +- **Performance**: Hardware-accelerated rendering when available +- **Memory**: Efficient texture management with proper cleanup + +## Makefile Targets + +- `make` or `make all` - Build the image viewer +- `make clean` - Remove built files +- `make test` - Run with a test image (if available in randomJPG folder) +- `make deps-debian` - Install dependencies on Ubuntu/Debian +- `make deps-fedora` - Install dependencies on Fedora/RHEL/CentOS +- `make deps-arch` - Install dependencies on Arch Linux +- `make help` - Show available targets and usage + +## Troubleshooting + +### "SDL could not initialize" Error +Make sure SDL2 development libraries are installed: +```bash +# Ubuntu/Debian +sudo apt-get install libsdl2-dev libsdl2-image-dev + +# Check if libraries are found +pkg-config --libs sdl2 +``` + +### "Unable to load image" Error +- Check that the image file exists and is readable +- Verify the image format is supported (JPG, PNG, BMP, GIF, TIF) +- Try with a different image file to isolate the issue + +### Compilation Errors +- Ensure you have a C compiler installed (gcc or clang) +- Check that SDL2 headers are available +- Try rebuilding with `make clean && make` + +## License + +This project is open source. See the LICENSE file for details. + +## Contributing + +Feel free to submit issues and enhancement requests. The code is designed to be simple and educational while being fully functional. diff --git a/C/imageViewer/main.c b/C/imageViewer/main.c new file mode 100644 index 0000000..ca8f158 --- /dev/null +++ b/C/imageViewer/main.c @@ -0,0 +1,317 @@ +#include +#include +#include +#include +#include + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 +#define MAX_PATH_LEN 512 + +typedef struct { + SDL_Window* window; + SDL_Renderer* renderer; + SDL_Texture* texture; + char current_file[MAX_PATH_LEN]; + int image_width; + int image_height; + float zoom_factor; + int offset_x; + int offset_y; + int dragging; + int last_mouse_x; + int last_mouse_y; +} ImageViewer; + +int init_viewer(ImageViewer* viewer) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); + return 0; + } + + int img_flags = IMG_INIT_JPG | IMG_INIT_PNG; + if (!(IMG_Init(img_flags) & img_flags)) { + printf("SDL_image could not initialize! SDL_image Error: %s\n", IMG_GetError()); + SDL_Quit(); + return 0; + } + + viewer->window = SDL_CreateWindow("JPG Image Viewer", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + WINDOW_WIDTH, + WINDOW_HEIGHT, + SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + + if (!viewer->window) { + printf("Window could not be created! SDL_Error: %s\n", SDL_GetError()); + IMG_Quit(); + SDL_Quit(); + return 0; + } + + viewer->renderer = SDL_CreateRenderer(viewer->window, -1, SDL_RENDERER_ACCELERATED); + if (!viewer->renderer) { + printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError()); + SDL_DestroyWindow(viewer->window); + IMG_Quit(); + SDL_Quit(); + return 0; + } + + viewer->texture = NULL; + viewer->current_file[0] = '\0'; + viewer->zoom_factor = 1.0f; + viewer->offset_x = 0; + viewer->offset_y = 0; + viewer->dragging = 0; + viewer->image_width = 0; + viewer->image_height = 0; + + return 1; +} + +int load_image(ImageViewer* viewer, const char* filename) { + if (viewer->texture) { + SDL_DestroyTexture(viewer->texture); + viewer->texture = NULL; + } + + SDL_Surface* surface = IMG_Load(filename); + if (!surface) { + printf("Unable to load image %s! SDL_image Error: %s\n", filename, IMG_GetError()); + return 0; + } + + viewer->texture = SDL_CreateTextureFromSurface(viewer->renderer, surface); + if (!viewer->texture) { + printf("Unable to create texture from %s! SDL_Error: %s\n", filename, SDL_GetError()); + SDL_FreeSurface(surface); + return 0; + } + + viewer->image_width = surface->w; + viewer->image_height = surface->h; + + SDL_FreeSurface(surface); + + strncpy(viewer->current_file, filename, MAX_PATH_LEN - 1); + viewer->current_file[MAX_PATH_LEN - 1] = '\0'; + + viewer->zoom_factor = 1.0f; + viewer->offset_x = 0; + viewer->offset_y = 0; + + int window_w, window_h; + SDL_GetWindowSize(viewer->window, &window_w, &window_h); + + float scale_x = (float)window_w / viewer->image_width; + float scale_y = (float)window_h / viewer->image_height; + float auto_scale = (scale_x < scale_y) ? scale_x : scale_y; + + if (auto_scale < 1.0f) { + viewer->zoom_factor = auto_scale * 0.9f; + } + + printf("Loaded image: %s (%dx%d)\n", filename, viewer->image_width, viewer->image_height); + return 1; +} + +void render_image(ImageViewer* viewer) { + SDL_SetRenderDrawColor(viewer->renderer, 32, 32, 32, 255); + SDL_RenderClear(viewer->renderer); + + if (!viewer->texture) { + SDL_RenderPresent(viewer->renderer); + return; + } + + int scaled_width = (int)(viewer->image_width * viewer->zoom_factor); + int scaled_height = (int)(viewer->image_height * viewer->zoom_factor); + + int window_w, window_h; + SDL_GetWindowSize(viewer->window, &window_w, &window_h); + + int x = (window_w - scaled_width) / 2 + viewer->offset_x; + int y = (window_h - scaled_height) / 2 + viewer->offset_y; + + SDL_Rect dest_rect = {x, y, scaled_width, scaled_height}; + SDL_RenderCopy(viewer->renderer, viewer->texture, NULL, &dest_rect); + + SDL_RenderPresent(viewer->renderer); +} + +void handle_zoom(ImageViewer* viewer, float zoom_delta, int mouse_x, int mouse_y) { + float old_zoom = viewer->zoom_factor; + viewer->zoom_factor += zoom_delta; + + if (viewer->zoom_factor < 0.1f) viewer->zoom_factor = 0.1f; + if (viewer->zoom_factor > 10.0f) viewer->zoom_factor = 10.0f; + + float zoom_ratio = viewer->zoom_factor / old_zoom; + + int window_w, window_h; + SDL_GetWindowSize(viewer->window, &window_w, &window_h); + + int center_x = window_w / 2; + int center_y = window_h / 2; + + viewer->offset_x = (viewer->offset_x - (mouse_x - center_x)) * zoom_ratio + (mouse_x - center_x); + viewer->offset_y = (viewer->offset_y - (mouse_y - center_y)) * zoom_ratio + (mouse_y - center_y); +} + +void print_help() { + printf("\n=== JPG Image Viewer Controls ===\n"); + printf("Mouse wheel / +/-: Zoom in/out\n"); + printf("Mouse drag: Pan image\n"); + printf("R: Reset zoom and position\n"); + printf("F: Fit image to window\n"); + printf("H: Show this help\n"); + printf("ESC/Q: Quit\n"); + printf("==================================\n\n"); +} + +void cleanup_viewer(ImageViewer* viewer) { + if (viewer->texture) { + SDL_DestroyTexture(viewer->texture); + } + if (viewer->renderer) { + SDL_DestroyRenderer(viewer->renderer); + } + if (viewer->window) { + SDL_DestroyWindow(viewer->window); + } + IMG_Quit(); + SDL_Quit(); +} + +int main(int argc, char* argv[]) { + if (argc != 2) { + printf("Usage: %s \n", argv[0]); + printf("Supported formats: JPG, JPEG, PNG, BMP, GIF, TIF\n"); + return 1; + } + + ImageViewer viewer; + + if (!init_viewer(&viewer)) { + printf("Failed to initialize image viewer!\n"); + return 1; + } + + if (!load_image(&viewer, argv[1])) { + printf("Failed to load image: %s\n", argv[1]); + cleanup_viewer(&viewer); + return 1; + } + + print_help(); + + int quit = 0; + SDL_Event e; + + while (!quit) { + while (SDL_PollEvent(&e)) { + switch (e.type) { + case SDL_QUIT: + quit = 1; + break; + + case SDL_KEYDOWN: + switch (e.key.keysym.sym) { + case SDLK_ESCAPE: + case SDLK_q: + quit = 1; + break; + + case SDLK_r: + viewer.zoom_factor = 1.0f; + viewer.offset_x = 0; + viewer.offset_y = 0; + printf("Reset view\n"); + break; + + case SDLK_f: + { + int window_w, window_h; + SDL_GetWindowSize(viewer.window, &window_w, &window_h); + + float scale_x = (float)window_w / viewer.image_width; + float scale_y = (float)window_h / viewer.image_height; + viewer.zoom_factor = ((scale_x < scale_y) ? scale_x : scale_y) * 0.9f; + viewer.offset_x = 0; + viewer.offset_y = 0; + printf("Fit to window (zoom: %.2f)\n", viewer.zoom_factor); + } + break; + + case SDLK_PLUS: + case SDLK_EQUALS: + handle_zoom(&viewer, 0.1f, WINDOW_WIDTH/2, WINDOW_HEIGHT/2); + printf("Zoom: %.2f\n", viewer.zoom_factor); + break; + + case SDLK_MINUS: + handle_zoom(&viewer, -0.1f, WINDOW_WIDTH/2, WINDOW_HEIGHT/2); + printf("Zoom: %.2f\n", viewer.zoom_factor); + break; + + case SDLK_h: + print_help(); + break; + } + break; + + case SDL_MOUSEWHEEL: + { + int mouse_x, mouse_y; + SDL_GetMouseState(&mouse_x, &mouse_y); + float zoom_delta = e.wheel.y * 0.1f; + handle_zoom(&viewer, zoom_delta, mouse_x, mouse_y); + printf("Zoom: %.2f\n", viewer.zoom_factor); + } + break; + + case SDL_MOUSEBUTTONDOWN: + if (e.button.button == SDL_BUTTON_LEFT) { + viewer.dragging = 1; + viewer.last_mouse_x = e.button.x; + viewer.last_mouse_y = e.button.y; + } + break; + + case SDL_MOUSEBUTTONUP: + if (e.button.button == SDL_BUTTON_LEFT) { + viewer.dragging = 0; + } + break; + + case SDL_MOUSEMOTION: + if (viewer.dragging) { + int dx = e.motion.x - viewer.last_mouse_x; + int dy = e.motion.y - viewer.last_mouse_y; + + viewer.offset_x += dx; + viewer.offset_y += dy; + + viewer.last_mouse_x = e.motion.x; + viewer.last_mouse_y = e.motion.y; + } + break; + + case SDL_WINDOWEVENT: + if (e.window.event == SDL_WINDOWEVENT_RESIZED) { + printf("Window resized to %dx%d\n", e.window.data1, e.window.data2); + } + break; + } + } + + render_image(&viewer); + SDL_Delay(16); + } + + cleanup_viewer(&viewer); + printf("Image viewer closed.\n"); + return 0; +} \ No newline at end of file