Skip to content

Conversation

@camjac251
Copy link

@camjac251 camjac251 commented Nov 29, 2025

Closes #636

Summary

Adds A/B image comparison with a draggable slider overlay that splits the view between two images.

How to use

  1. Open an image (this becomes image A)
  2. Enable comparison: Layout > Compare images or press Alt+M
  3. Set comparison image (B) by:
    • Ctrl+click a thumbnail in the gallery, or
    • Drag & drop a file onto the right pane, or
    • Alt+scroll over right pane to cycle through images
  4. Drag the slider to compare

Controls

  • Drag slider: reveal more of left (A) or right (B) image
  • Click anywhere: reposition slider
  • Drag handle vertically: move handle along the slider line
  • Backspace: reset slider to center
  • ` (backtick): reset zoom/pan
  • Alt+scroll on right pane: cycle comparison images

Technical notes

  • D2D rendering for raster images
  • WebView2 rendering for SVG (supports mixed SVG/raster comparison)
  • Synchronized zoom and pan between both images
  • A/B badges on gallery thumbnails

Implements a new tool for comparing two images using a draggable slider
overlay that splits the view between left (A) and right (B) images.

Core functionality:
- Slider-based split view (drag to reveal left/right images)
- Click anywhere to reposition slider
- Synchronized zoom and pan for both images
- Keyboard shortcuts: Backspace (reset slider), ` (reset zoom/pan)
- Handle can be moved vertically along the slider line

Image loading:
- Ctrl+click thumbnail in gallery to set comparison image (B)
- Mouse wheel over right pane cycles through comparison images
- Drag & drop files onto either pane with visual feedback

UI integration:
- Menu: Layout > Compare images
- Hotkey: Alt+M
- Toolbar button with new Compare icon
- A/B badges on gallery thumbnails indicate main/comparison images

Technical:
- D2D rendering for raster image comparison
- WebView2 rendering for SVG comparison (with mixed SVG/raster support)
- Dynamic switching between renderers based on image type
- Image-based slider positioning (follows zoom/pan transforms)
Display "Compare: image.png ⇄ compare.png" in the title bar when
comparison mode is active, followed by the normal image info.
@d2phap
Copy link
Owner

d2phap commented Nov 29, 2025

Thanks @camjac251 for the PR. Gimme some time to review it :)

@d2phap d2phap added this to the v9.5 milestone Nov 29, 2025
@JADERLINK
Copy link

I found a bug:

When comparing two GIF files, the second GIF file is not displayed.

Examples:
PNG and PNG works
GIF and PNG works
PNG and GIF does not work
GIF and GIF does not work

To test, you can use two animated GIF images.

- Fix PhotoCodec.ReadWithStream ignoring FirstFrameOnly option for
animated formats (GIF, GIFV, WEBP, B64), which caused comparison
image to be null
- Extend ShouldUseWeb2ForComparison to detect animated formats and
use WebView2 mode for proper animation support
- Modify ReadImageAsHtmlAsync to return null for animated formats,
allowing direct file URL usage instead of base64 overhead
@camjac251
Copy link
Author

Thanks for testing. I missed that bug, it's now rendering GIFs in WebView2 now instead, and they're animated during comparison

@JADERLINK
Copy link

https://youtu.be/zf-4c6CwndQ

English:
List of errors found:

  • The "Rotate left", "Rotate right", "Flip Horizontal", and "Flip Vertical" functions only work for the first image, and when the second image is an animation, these functions are also not applied to the first image.
  • When the second image is an animation (GIF) or an SVG, you can move the images to any position on the screen, even without zooming.
  • When zooming, the second image may get stuck in the cutout of the previous image, thus only showing part of the image on the screen, leaving most of it blank.
  • The image may have a lower resolution than it actually is, but I couldn't replicate this bug in the video.
  • When selecting color channels for visualization, it only affects the first image.
  • Other effects probably don't affect the second image either; I don't know if that was the intention.

Portuguese:
Lista de erros encontrados:

  • As funções "Rotate left", "Rotate right" , "Flip Horisontal" e "Flip Vertical" somente funcionam para a primeira imagem, e quando a segunda imagem é uma animação, para a primeira imagem essas funções também não são aplicadas.
  • Quando a segunda imagem é uma animação (GIF) ou um SVG, pode mover as imagens para qualquer posição da tela, mesmo sem zoom.
  • Ao dar zoom, a segunda imagem pode ficar presa ao recorte da imagem anterior, ficando assim somente com uma parte da imagem na tela, ficando a maior parte em tela vazia.
  • A imagem pode ficar com uma resolução inferior ao que ela realmente é, porém não consegui replicar esse bug no vídeo.
  • Ao selecionar os canais de cores para visualização, somente faz efeito na primeira imagem.
  • Outros efeitos provavelmente também não afetam a segunda imagem, não sei se essa era a intenção.

Move duplicated animatedExtensions array to Const.ANIMATED_FORMATS
for single source of truth across comparison mode code paths.
- Disable rotate/flip/invert/color channel features during comparison
  mode with user-friendly message (features would only affect one image)
- Fix pan without zoom bug in WebView2 comparison mode - images can no
  longer be dragged when at 1x zoom, panning constrained to image bounds
- Fix zoom clipping bug - reset zoom/pan when comparison images change
  to prevent new image from being "stuck" in previous transform state

Addresses feedback from PR d2phap#2238 review.
@camjac251 camjac251 force-pushed the feat/image-comparison-tool branch from d58bff1 to 4daa2ee Compare January 4, 2026 03:34
@camjac251
Copy link
Author

camjac251 commented Jan 4, 2026

Thanks @JADERLINK for the detailed bug report and video! I've pushed fixes for the issues you identified:

Fixed:

  • Rotate/Flip/Color channels - Now disabled during comparison mode with a user-friendly message (these would only affect one image, so disabling is cleaner)
  • Pan without zoom (GIF/SVG) - Images can no longer be dragged freely at 1x zoom; panning is now constrained to image bounds
  • Zoom clipping - Reset zoom/pan when comparison images change, so new images don't get "stuck" in the previous transform state
  • Color channel/effects - Same as above, disabled during comparison mode

Not addressed:

  • Resolution degradation - Couldn't reproduce this. If you can provide specific repro steps, happy to investigate further.

Note: I wasn't able to build and test these changes due to an antivirus false positive deleting a NuGet package (microsoft.windows.sdk.win32metadata). The fixes should work based on code review, but I'll confirm once I resolve the local build issue.

@d2phap
Copy link
Owner

d2phap commented Jan 4, 2026

Thanks for update @camjac251
I haven't looked into the code, but here are some UI/UX issues I found

  1. DPI Issue: The resize indicator is not scaled on high DPI screen (my DPI is 200%), it should be just a circle/dot similar to Crop tool resizers for consistency
  2. Split screen issue: The separator line should not be in the middle of the pixel of pic A. It should be similar to the selection function in the Crop tool (for native renderer Direct2D only)
  3. Should allow user to hover on nearby area of the splitter to resize, not only the indicator (similar to Crop tool). You can set EnableDebug: true to see (Pic 2)
  4. I think Crop tool should be disabled in the Compare mode, too
image

Selection in Crop tool with EnableDebug: true

image

- Fix DPI scaling for comparison slider (handle, line, hit areas)
- Round slider position to pixel boundary to avoid cutting through pixels
- Add hover area along full separator line height (not just handle)
- Disable crop tool during comparison mode
- Style handle like crop tool resizers (double ellipse with shadow)
@camjac251
Copy link
Author

Thanks for the detailed feedback @d2phap

Pushed fixes for the UI/UX issues:

  • DPI scaling: All slider measurements now use DpiApi.Scale() - handle size, line width, hit areas, and padding should scale correctly at 200% DPI
  • Split line: Now rounds to pixel boundary using MathF.Round() instead of truncating, so the line won't cut through pixels
  • Hover area: Added rectangular hit region along the full separator line height (similar to crop tool), not just the circular handle area
  • Crop tool: Now disabled during comparison mode with an error message

The handle is also now styled like crop tool resizers (double ellipse with shadow effect for consistency).

Note: Still unable to build/test locally due to the antivirus issue with the NuGet package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A/B Image Compare

3 participants