PNGer - Modern PNG Editor & Resizer
A sleek, modern PNG editor and resizer with live preview, drag & drop, smart presets, keyboard shortcuts, and dark/light mode theming. Built with TypeScript and deployed as a single Docker container on Unraid.
✨ Features
🎨 Modern UI with Dark/Light Mode
- Dark Mode: Black background (#0a0a0a) with light gold (#daa520) accents
- Light Mode: White background with dark gold (#b8860b) accents
- Perfect for inspecting PNG transparency on different backgrounds
- Persistent theme preference
- Smooth color transitions
⚡ Live Preview
- Real-time preview of transformations before download
- Side-by-side comparison (original vs transformed)
- File size analysis showing savings or increase
- Instant feedback using client-side Canvas API (< 500ms)
- No server round-trip needed for preview
🖼️ Image Operations
- Resize Operations: Width, height, and aspect ratio controls
- Crop to Fit: Smart cropping with position control (9 positions)
- Format Conversion: PNG, WebP, and JPEG output
- Quality Control: Adjustable compression settings (10-100%)
- Fit Modes: Inside (resize only) or Cover (crop to fill)
🚀 Performance & Usability
- Drag & Drop: Drag images directly into the app or use file browser
- Clipboard Paste: Paste images with Ctrl+V (Cmd+V on Mac)
- Smart Presets: Quick access to common configurations:
- 📷 Web Optimized (1920x1080, 80% quality, WebP)
- 📱 Social Media (1080x1080, 85% quality, JPEG)
- 👤 Profile Picture (400x400, 90% quality, PNG)
- 📧 Email Friendly (800x600, 75% quality, JPEG)
- ⭐ Tiny Icon (64x64, 100% quality, PNG)
- 🔍 Retina 2x (2x dimensions, 90% quality)
- 💎 Icon Small (256x256, 95% quality, PNG)
- 🔶 Icon Large (512x512, 95% quality, PNG)
- Keyboard Shortcuts: Fast workflow with keyboard controls (see below)
- Direct Download: No server-side storage, immediate download
- Modern UI: Sleek, responsive TypeScript/Svelte design
- File Analysis: Original size, transformed size, savings percentage
- Debounced Updates: Smooth preview generation (300ms delay)
- Visual Feedback: Loading states, error messages, success indicators
⌨️ Keyboard Shortcuts
- Ctrl+V (Cmd+V): Paste image from clipboard
- Enter: Transform & Download (works when input not focused)
- Ctrl+Enter (Cmd+Enter): Transform & Download (works anywhere)
- ?: Show/hide keyboard shortcuts help
- Esc: Close shortcuts dialog
Tech Stack
- Frontend: Svelte 4 + Vite + TypeScript
- Backend: Node.js + Express + TypeScript
- Image Processing: Sharp (high-performance image library)
- Preview: Canvas API (client-side)
- Container: Docker (Alpine-based, multi-stage build)
- Deployment: Unraid via Docker Compose
Quick Start
Unraid Deployment (Recommended)
-
Clone or pull this repository to your Unraid server:
cd /mnt/user/appdata git clone https://git.alwisp.com/jason/pnger.git cd pnger -
Build the Docker image:
docker build -t pnger:latest . -
Run via docker-compose:
docker compose up -d -
Access the application:
- Navigate to
http://[unraid-ip]:8080
- Navigate to
Unraid Environment Variables (Configurable via UI)
When setting up in Unraid Docker UI, you can configure:
| Variable | Default | Description |
|---|---|---|
HOST_PORT |
8080 |
External port to access the app |
MAX_FILE_SIZE |
10485760 |
Max upload size in bytes (10MB default) |
MEM_LIMIT |
512m |
Memory limit for container |
CPU_LIMIT |
1.0 |
CPU limit (1.0 = 1 full core) |
NODE_ENV |
production |
Node environment |
Unraid Docker Template Example
<?xml version="1.0"?>
<Container version="2">
<Name>pnger</Name>
<Repository>pnger:latest</Repository>
<Network>bridge</Network>
<Privileged>false</Privileged>
<WebUI>http://[IP]:[PORT:8080]</WebUI>
<Config Name="WebUI Port" Target="3000" Default="8080" Mode="tcp" Description="Port for web interface" Type="Port" Display="always" Required="true" Mask="false">8080</Config>
<Config Name="Max File Size" Target="MAX_FILE_SIZE" Default="10485760" Mode="" Description="Maximum upload file size in bytes" Type="Variable" Display="advanced" Required="false" Mask="false">10485760</Config>
<Config Name="Memory Limit" Target="" Default="512m" Mode="" Description="Container memory limit" Type="Variable" Display="advanced" Required="false" Mask="false">512m</Config>
</Container>
Local Development
Prerequisites
- Node.js 20+
- npm or yarn
Setup
-
Install backend dependencies:
cd backend npm install -
Install frontend dependencies:
cd frontend npm install -
Run development servers:
Terminal 1 (Backend):
cd backend npm run devTerminal 2 (Frontend):
cd frontend npm run dev -
Access dev server:
- Frontend:
http://localhost:5173 - Backend API:
http://localhost:3000/api
- Frontend:
Building for Production
# Backend TypeScript compilation
cd backend
npm run build
# Frontend build
cd frontend
npm run build
Docker Deployment (Manual)
# Build the image (all dependencies and builds are handled internally)
docker build -t pnger:latest .
# Run the container
docker run -d \
--name pnger \
-p 8080:3000 \
-e MAX_FILE_SIZE=10485760 \
--restart unless-stopped \
pnger:latest
Project Structure
pnger/
├── frontend/ # Svelte + TypeScript application
│ ├── src/
│ │ ├── App.svelte # Main UI component (with live preview)
│ │ ├── main.ts # Entry point
│ │ ├── app.css # Design system (dark/light modes)
│ │ └── lib/
│ │ ├── api.ts # API client
│ │ ├── preview.ts # Live preview logic
│ │ ├── theme.ts # Theme management store
│ │ └── presets.ts # Smart presets configuration
│ ├── package.json
│ ├── tsconfig.json
│ └── vite.config.ts
├── backend/ # Express + TypeScript API server
│ ├── src/
│ │ ├── index.ts # Express server
│ │ ├── routes/
│ │ │ └── image.ts # Image transform endpoint
│ │ └── types/
│ │ └── image.ts # TypeScript types
│ ├── package.json
│ └── tsconfig.json
├── Dockerfile # Multi-stage build (frontend + backend)
├── docker-compose.yml # Unraid deployment config
├── ROADMAP.md # Feature roadmap
├── SPRINT1_CHANGES.md # Sprint 1 implementation details
└── UI_UPGRADE_NOTES.md # UI upgrade documentation
How It Works
- User uploads an image via drag & drop, file browser, or clipboard paste
- Live preview generates instantly using Canvas API
- User adjusts parameters (width, height, quality, format, etc.) or selects a preset
- Preview updates in real-time (debounced 300ms)
- User sees file size comparison and savings
- When satisfied, user clicks "Transform & Download" or presses Enter
- Frontend sends image + parameters to backend API
- Backend processes using Sharp (resize, crop, compress, convert)
- Processed image returned directly to browser
- Browser triggers automatic download
- No files stored on server (stateless operation)
API Endpoints
POST /api/transform
Transform an image with resize, crop, and format conversion.
Request:
- Method:
POST - Content-Type:
multipart/form-data - Body:
file: Image file (PNG/JPEG/WebP)width: Target width (optional)height: Target height (optional)quality: Quality 10-100 (optional, default: 80)format: Output formatpng|webp|jpeg(optional, default:png)fit: Resize modeinside|cover(optional, default:inside)position: Crop position whenfit=cover(optional, default:center)
Response:
- Content-Type:
image/[format] - Body: Transformed image binary
Configuration
All configuration is handled via environment variables passed through Docker/Unraid:
PORT: Server port (default:3000, internal)MAX_FILE_SIZE: Maximum upload size in bytes (default:10485760= 10MB)TEMP_DIR: Temporary directory for uploads (default:/app/temp)NODE_ENV: Node environment (default:production)
UI Features in Detail
Dark/Light Mode
- Toggle Button: Sun (☀️) / Moon (🌙) icon in header
- Persistent: Saved to localStorage
- System Detection: Uses OS preference on first visit
- Smooth Transitions: Colors fade smoothly (250ms)
- Use Case: Compare PNG transparency on black vs white backgrounds
Live Preview
- Side-by-Side: Original image on left, preview on right
- File Size: Shows before and after sizes
- Savings Indicator: Green for reduction, yellow for increase
- Instant Updates: Debounced at 300ms for smooth performance
- Canvas-Based: No server load, runs in browser
Drag & Drop Upload
- Drop Zone: Large, responsive drop target with visual feedback
- Multiple Methods: Drag & drop, click to browse, or paste from clipboard
- File Validation: Instant validation with clear error messages
- Visual States: Hover and dragging states for better UX
Smart Presets
- 8 Pre-configured Options: Common use cases ready to go
- One-Click Apply: Instantly sets all parameters
- Visual Icons: Easy identification of each preset
- Responsive Grid: Adapts to screen size
Keyboard Shortcuts
- Productivity Focused: Common operations accessible via keyboard
- Help Dialog: Press ? to see all shortcuts
- Context Aware: Enter works differently based on focus
- Cross-Platform: Supports both Ctrl and Cmd modifiers
Image Analysis
- Original file size displayed
- Preview size estimation
- Savings/increase percentage
- Visual feedback with color coding
Recent Updates (Sprint 1)
✅ Drag & Drop Upload - Drag images directly into the app ✅ Clipboard Paste - Paste images with Ctrl+V ✅ Smart Presets - 8 quick-access presets for common scenarios ✅ Keyboard Shortcuts - Fast workflow with keyboard controls ✅ Enhanced UX - Better visual feedback and error handling ✅ Theme Contrast Fixes - Improved text visibility in both themes
See SPRINT1_CHANGES.md for detailed implementation notes.
Roadmap
See ROADMAP.md for planned features including:
- Batch processing
- Advanced crop tool with visual selector
- Watermarking
- Image filters and effects
- And more!
License
MIT License - See LICENSE file for details
Repository
https://git.alwisp.com/jason/pnger
Screenshots
Light Mode
Clean white interface with dark gold accents, perfect for inspecting dark images
Dark Mode
Sleek black interface with light gold accents, ideal for viewing light/transparent PNGs
Live Preview
Side-by-side comparison showing original and transformed image with file size analysis
Smart Presets
Quick-access buttons for common image optimization scenarios
Keyboard Shortcuts
Built-in help dialog showing all available shortcuts