feat: added simple image viewer

This commit is contained in:
Krzysztof kuhy Rudnicki 2025-07-09 18:05:59 +02:00
parent 56187cc96c
commit 3f41e5c821
3 changed files with 537 additions and 0 deletions

72
C/imageViewer/Makefile Normal file
View File

@ -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) <image_file.jpg>"; \
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) <image_file.jpg>"
@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"

148
C/imageViewer/README.md Normal file
View File

@ -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 <image_file>
```
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.

317
C/imageViewer/main.c Normal file
View File

@ -0,0 +1,317 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#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 <image_file.jpg>\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;
}