Read this in other languages: 日本語
A fast, lightweight desktop image viewer built with C++17 and Qt 6. It decodes images asynchronously on a background thread pool, caches them in memory with an LRU policy, prefetches neighboring images for instant navigation, honors EXIF orientation, and supports a wide range of formats — including modern codecs such as HEIC/HEIF and AVIF when the optional libraries are present.
- Features
- Supported Formats
- Keyboard Shortcuts
- Menus & Actions
- Mouse Controls
- Architecture
- Building
- Running
- Wide format support. Everything Qt's image plugins can read, plus HEIC/HEIF (via libheif) and AVIF — static and animated — (via libavif) when those optional libraries are found at build time.
- Animated images. Animated GIF and WebP play back through Qt; animated AVIF plays back through libavif. Per-frame delays are respected.
- Asynchronous decoding. Images decode on a
QThreadPoolbackground worker so the UI never blocks, even on large files. - LRU memory cache. Decoded images are stored in a thread-safe least-recently-used cache (default budget 512 MB). Already-seen images redisplay instantly with no re-decode.
- Neighbor prefetch. When you view an image, the next, previous, and next-next images are decoded in the background so paging through a folder is near-instant.
- EXIF auto-orientation. Photos are rotated automatically according to their
EXIF orientation tag (
QImageReader::setAutoTransform). - Folder navigation with natural sort. Open a single file or a whole folder.
The folder is scanned for all supported images and sorted with natural
(numeric-aware) ordering, so
img2comes beforeimg10. Navigation wraps around at the ends. - Zoom, pan, rotate, fit. Smooth zooming (2%–6400%), scroll-hand panning, 90° rotation in either direction, fit-to-window, and 100% (1:1) reset.
- Slideshow. Auto-advance through the folder on a timer.
- Fullscreen. Hides the menu and status bar; restores the prior maximized/normal state on exit.
- Drag & drop. Drop a file or folder onto the window to open it.
- Save As. Export the currently displayed image to PNG, JPEG, BMP, WebP, or TIFF.
- Command-line / "Open with". A path passed as the first argument opens on launch.
- Status bar. Shows the file name, position in the folder (
[n/total]), whether the image is animated, slideshow state, and the current zoom percentage.
The exact list of "always available" formats depends on which Qt image format plugins are installed in your Qt build. The table below reflects the formats Qt 6 ships by default plus the optional native decoders.
| Format | Extensions | Decoder | Animation |
|---|---|---|---|
| JPEG | .jpg, .jpeg |
Qt | — |
| PNG | .png |
Qt | — |
| BMP | .bmp |
Qt | — |
| GIF | .gif |
Qt | Yes (animated) |
| WebP | .webp |
Qt | Yes (animated) |
| TIFF | .tif, .tiff |
Qt | — |
| TGA | .tga |
Qt | — |
| ICO | .ico |
Qt | — |
| SVG | .svg |
Qt | — |
| HEIC / HEIF | .heic, .heif |
libheif (optional) | — (primary image) |
| AVIF | .avif, .avifs |
libavif (optional) | Yes (animated) |
Notes:
- The supported-extension list is built at runtime from
QImageReader::supportedImageFormats(), so any extra Qt image plugins present on your system are picked up automatically. - HEIC/HEIF support is compiled in only when libheif is found at configure
time (
HAVE_LIBHEIF). The primary (cover) image of the file is decoded. - AVIF support is compiled in only when libavif is found at configure time
(
HAVE_LIBAVIF). Both static and animated AVIF are supported. - Without these optional libraries the app still builds and runs; HEIC/AVIF files simply fall back to Qt (and will fail to open if Qt has no plugin).
| Action | Shortcut |
|---|---|
| Open File… | Ctrl+O |
| Open Folder… | Ctrl+Shift+O |
| Save As… | Ctrl+Shift+S |
| Quit | Ctrl+Q |
| Previous image | Left |
| Next image | Right |
| First image | Home |
| Last image | End |
| Zoom In | + |
| Zoom Out | - |
| 100% (1:1) | Ctrl+0 |
| Fit to window | Ctrl+F |
| Fullscreen (toggle) | F11 |
| Slideshow (toggle) | F5 |
| Rotate clockwise | Ctrl+R |
| Rotate counter-clockwise | Ctrl+L |
The Open (
Ctrl+O), Save As (Ctrl+Shift+S), and Quit (Ctrl+Q) shortcuts are Qt's standard sequences and may map to platform conventions.
The menu bar is organized into four menus:
- File — Open File…, Open Folder…, Save As…, Quit.
- View — Zoom In, Zoom Out, 100%, Fit, Fullscreen, Slideshow (checkable).
- Navigate — Previous, Next, First, Last.
- Edit — Rotate CW, Rotate CCW.
A non-movable toolbar exposes the most common actions: Open File, Open Folder, Previous, Next, Zoom In, Zoom Out, 100%, Fit, Rotate CW, and Rotate CCW.
| Gesture | Result |
|---|---|
| Mouse wheel up / down | Zoom in / out, anchored under the cursor |
| Click & drag | Pan (scroll-hand drag) |
| Double-click | Toggle between Fit-to-window and 100% |
The codebase is small and cleanly separated into single-responsibility classes.
| File | Responsibility |
|---|---|
src/main.cpp |
Entry point; sets app/org name; opens a CLI path argument. |
src/MainWindow.{h,cpp} |
Window, menus, toolbar, status bar; wires up loading, navigation, slideshow, fullscreen, save-as, drag & drop. |
src/ImageView.{h,cpp} |
QGraphicsView-based canvas for zoom/pan/rotate/fit and animation playback. |
src/ImageLoader.{h,cpp} |
Backend dispatch (Qt / libheif / libavif), supported-extension list, and async decode via QThreadPool. |
src/ImageCache.{h,cpp} |
Thread-safe LRU cache keyed by file path with a byte budget. |
src/FolderScanner.{h,cpp} |
Lists and natural-sorts supported images in a directory. |
src/DecodedImage.h |
Frame and DecodedImage value types shared across threads. |
Asynchronous decode. ImageLoader::request() issues a monotonically
increasing token and starts a LoadTask (a QRunnable) on its internal
QThreadPool (sized to max(2, idealThreadCount())). The task decodes off the
UI thread and posts the result back via a queued loaded(token, path, image)
signal. MainWindow only displays a result whose token matches the image the
user is currently waiting for, so stale results from rapid navigation are
ignored (but still cached).
Backend dispatch. ImageLoader::decode() routes by extension:
.heic/.heif → libheif, .avif/.avifs → libavif, everything else → Qt's
QImageReader (with setAutoTransform(true) for EXIF orientation and
multi-frame reading for animations).
LRU cache + prefetch. Decoded images go into ImageCache, an LRU keyed by
path with a default 512 MB budget; the least-recently-used entries are
evicted when the budget is exceeded. After showing an image, MainWindow
prefetches index+1, index-1, and index+2 so paging is instant. Cache hits
display synchronously with no decode.
Rendering. ImageView is a QGraphicsView with a smooth-transform pixmap
item over a dark background. Zoom and rotation are applied through a combined
QTransform; zoom is clamped to 2%–6400% (0.02–64.0). Fit-to-window never
upscales beyond 100% and re-fits automatically on resize and rotation. Animated
images advance frame-by-frame on a single-shot QTimer honoring each frame's
delay.
- CMake ≥ 3.21
- Qt 6.9.1 (MSVC 2022 64-bit build) — expected at
C:\Qt\6.9.1\msvc2022_64 - Visual Studio 2022 (MSVC toolchain)
- Optional: libheif and libavif (e.g. via vcpkg) for HEIC/HEIF and AVIF support
Qt components used: Widgets and Concurrent.
cmake -S . -B build -G "Ninja" ^
-DCMAKE_PREFIX_PATH="C:/Qt/6.9.1/msvc2022_64"
cmake --build build --config ReleaseOr with the Visual Studio generator:
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 ^
-DCMAKE_PREFIX_PATH="C:/Qt/6.9.1/msvc2022_64"
cmake --build build --config ReleaseInstall the optional decoders and point CMake at both Qt and the vcpkg toolchain:
vcpkg install libheif libavif
cmake -S . -B build -G "Ninja" ^
-DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" ^
-DCMAKE_PREFIX_PATH="C:/Qt/6.9.1/msvc2022_64"
cmake --build build --config ReleaseDuring configuration CMake prints which optional decoders were enabled, e.g.:
pbImageViewer: libheif ENABLED (HEIC/HEIF support)
pbImageViewer: libavif ENABLED (AVIF support)
If a library is not found, the corresponding line reads NOT found ... disabled
and the app builds without that format.
build\pbImageViewer.exe # start empty
build\pbImageViewer.exe path\to\img.jpg # open a file (or folder) on launchYou can also register the executable as the default "Open with" handler — the first command-line argument is opened automatically.