Skip to content

v1.12.0

Choose a tag to compare

@github-actions github-actions released this 06 Jul 21:31

Fixed

  • Large photos (50-100 MP phone cameras) rejected with qt.gui.imageio: QImageIOHandler: Rejecting image as it exceeds the current allocation limit of 256 megabytes when opened in the viewer — Qt's default 256 MiB allocation limit is checked against the image's undecoded dimensions; a 50+ MP photo decodes to 200-400 MB as RGB32, exceeding it, which made Qt reject the read outright and fall back to a much slower full-resolution Pillow decode just to display the file. image_io.py's _make_reader() now raises the limit to 1024 MiB (_ALLOCATION_LIMIT_MIB). Thumbnail generation was never affected (it downscales via setScaledSize(), checked against the target size, not the source).
  • Batch rotation stopped at the first failing file — when rotating multiple selected files and one failed (e.g. a truncated JPEG), _rotate_images() stopped processing the rest of the selection. Now continues with the remaining files, and a single dialog lists all failures with their respective file name and message.
  • Rotation errors did not show which file caused themQMessageBox for both rotation failures and undo failures showed only the raw error text (e.g. "Premature end of JPEG file"), with no way to tell which file among the selection was at fault. Both dialogs now prefix the message with the file name.
  • Rotation on a genuinely truncated JPEG failed with the cryptic jpegtran message "Premature end of JPEG file" — some files (e.g. panorama shots) are missing a small amount of data at the end, most likely from an incomplete write by the camera; jpegtran correctly refuses to rotate them since it cannot reconstruct the missing DCT blocks losslessly. Added a user-friendly explanation shown above the raw jpegtran message in rotate_lossless()'s RuntimeError (_jpegtran_error_hints() in image_ops.py).
  • Some JPEGs triggered a permanent qt.gui.imageio.jpeg: Corrupt JPEG data: N extraneous bytes before marker 0xd9 warning — trailing garbage bytes before the EOI marker (seen on some camera/app-produced JPEGs). Unlike the SOS defect above, there is no safe byte-level fix (the exact boundary between real entropy data and garbage can't be determined without a full Huffman decode), and it also has no practical impact: Qt/Pillow decode the image fine regardless. Silenced via QLoggingCategory.setFilterRules("qt.gui.imageio.jpeg=false") in __main__.py.
  • JPEGs from some phone cameras (e.g. certain Samsung front-camera modules) triggered a permanent qt.gui.imageio.jpeg: Invalid SOS parameters for sequential JPEG warning, displayed without EXIF auto-rotation, and failed lossless rotation — these files have a malformed SOS header (Se=0 instead of the mandatory 63 for baseline JPEGs), which jpegtran rejects outright and Qt's decoder merely warns about. Added repair_jpeg_sos() in image_ops.py, a lossless header fix (no entropy-coded data touched, no-op on well-formed files) applied before both Qt decoding (image_io.py) and jpegtran invocation. Also fixed load_pixmap()'s auto_rotate=True fast path, which used a bare QPixmap(str(path)) and never actually enabled Qt's setAutoTransform, silently skipping EXIF auto-rotation in the main viewer (thumbnails were unaffected, as they already went through QImageReader with setAutoTransform).
  • Double-click on image opens viewer with spurious "Cannot display multiple files simultaneously" error — Qt's ExtendedSelection mode emits itemSelectionChanged twice during a double-click (clear then re-select), causing _on_selection_changed to see 0 rows and wrongly trigger the multi-file message. Fixed by ignoring the transient empty-selection state (len(rows) == 0) in _on_selection_changed.
  • File list selection jumps to row 0 after opening image viewerfocusInEvent called selectRow(0) whenever focus returned via a non-mouse event and the selection appeared empty, which happened when the viewer window stole focus. Fixed by skipping the auto-select-first-row logic while the image viewer is visible.
  • Mouse-wheel scrolling through a long file list could stall repeatedly — restarting the visible-thumbnails worker (on each scroll-driven debounce firing) used to block the GUI thread waiting for the previous worker's current in-flight JPEG decode to finish; with large real photos and many debounce firings over a long scroll session, this added up to noticeable stalls, especially scrolling back up through thousands of rows. Restarting on scroll/resize now cancels the previous worker without blocking (_cancel_worker_async()), since the table itself isn't being rebuilt and a stray late thumbnail from the cancelled thread simply lands on a still-valid row.
  • Wheel/keyboard scrolling a long distance still felt slow, worse the further travelled — unlike a scrollbar-handle drag (a single jump to a destination, loading only its final position), traversing the list with the mouse wheel passes through every intermediate row, and each pause between wheel notches longer than the debounce interval (previously 100 ms) triggered a batch load for that transient position — so the number of batches triggered, and the cumulative lag, scaled with the distance scrolled rather than the destination itself. Raised the debounce to 400 ms, comfortably longer than typical inter-notch gaps during continuous scrolling, so intermediate positions are skipped and only the position where scrolling actually settles gets loaded.
  • Rotation-error dialog could grow unusably tall with a large selection — rotating thousands of files with many failures joined every file's error into one long plain message, pushing the OK button off-screen. The dialog now shows a short summary with the full per-file list in a collapsible/scrollable "Show Details" section (QMessageBox.setDetailedText()) instead.

Added

  • Keyboard shortcuts F9 / F10 for "Apply EXIF orientation" and "Force EXIF orientation to 0°", previously accessible only via menu/toolbar. Follows the existing F6/F7/F8 rotation shortcut sequence.
  • Missing SVG icons (auto-rotate, object-rotate-left, object-rotate-right, object-flip-vertical, reset-exif, folder-new, folder-open) — buttons and actions with no SVG fallback file displayed no icon on systems without an icon theme (e.g. Linux bundle).

Changed

  • Thumbnail loading in the file list is now viewport-lazy — previously, opening or refreshing a directory queued thumbnail generation for every file up front, in a single background thread, one at a time. On directories with thousands of files this could take many minutes, and files near the end of the list would sit without a thumbnail the whole time. _start_worker() now only loads thumbnails for rows currently visible (plus one screenful of margin), and re-triggers on scroll/resize (debounced) so thumbnails pop in on demand as the list is scrolled. Directory load/refresh time is no longer proportional to the total file count. Dragging the scrollbar handle across a long list skips loading until the handle is released, instead of loading a batch at every intermediate position passed through during the drag (which could add up to nearly as many decodes as loading everything).
  • Thumbnails for a freshly-scrolled-to batch of rows now load in parallel (up to 4 concurrent _ThumbnailWorker threads, capped by CPU count) instead of one thread decoding the whole visible batch sequentially — cuts the delay before icons appear after landing on a new scroll position.
  • "Reset EXIF orientation" renamed to "Force EXIF orientation to 0°" — the previous wording didn't make clear what the action actually does (menu/toolbar/shortcuts window/status tip).
  • File list column header — now shows the number of selected files alongside the total (File name (sel/n)) whenever 2 or more files are selected, instead of only the total (File name (n)). A single selected file still shows just the total, since the count is redundant there.
  • _linux.pyopen_default now uses subprocess.Popen(["xdg-open", …]) instead of QDesktopServices.openUrl(), which bypassed the user's MIME association and always opened xed instead of the actual default application.
  • SVG icons — all files replaced with high-resolution (1024×1024) versions with a consistent blue rounded-square background; pbpicat.svg simplified (534 KB → 24 KB).
  • SVG file names normalised: hyphens only (no underscores), semantic names (history, duplicate, rename-template, reset-exif, auto-rotate, open-with, zoom-fit, zoom-in, zoom-out, zoom-original, zoom-width, zoom-height).
  • file_list_widget.py — direct QDesktopServices.openUrl() calls for sidecars and videos now go through open_default().

Removed

  • Obsolete SVG files: document-open-recent.svg, edit-copy.svg, template.svg, open_with.svg, zoom_fit.svg, zoom_in.svg, zoom_out.svg, zoom_original.svg, zoom_width.svg, zoom_height.svg.