Skip to content

Speedup image comparision slider during WipeEffect#33

Open
Edi61 wants to merge 9 commits intoWinMerge:masterfrom
Edi61:master
Open

Speedup image comparision slider during WipeEffect#33
Edi61 wants to merge 9 commits intoWinMerge:masterfrom
Edi61:master

Conversation

@Edi61
Copy link

@Edi61 Edi61 commented Apr 9, 2024

As also marked in
WinMerge/winmerge#1596
the slider is very slow.

Here maybe one solution:

  • WipeEffect only if position changed (no refreshimage on everywipe -> not sensefull, since during wiping with the mouse
    in any case you are not be able to change any parameters which could lead to an image change)
  • Difference based wipeeffect (Only part between old and new position is updated)

Principially it works very fine and smooth, but if the image is changed before wiping with e.g. adjust offset,
the draw events are lost/not perfmored, maybe you have an idea why?

Regards,

- WipeEffect only if position changed
- Difference based wipeeffect (Only part between old and new position is updated)
@sdottaka
Copy link
Member

sdottaka commented Apr 9, 2024

Thank you for PR. However, it does not seem to work well as shown in the attached GIF animation.

Principially it works very fine and smooth, but if the image is changed before wiping with e.g. adjust offset, the draw events are lost/not perfmored, maybe you have an idea why?

I don't know so far.

winimergePR#33

@Edi61
Copy link
Author

Edi61 commented Apr 10, 2024

Aah, I have only checked with heic files, did not jet tessted with other image formats.
Then its probably related to something which is special for heic files.
I'll recheck it.

@Edi61
Copy link
Author

Edi61 commented Apr 14, 2024

I have found the problem, it's generally not the image format,
but any transperancy in the image which leads to not working wiping.
It also explains why adjusting/moving image is also not working,
because here we get also some transperancy parts.
Don't know jet how to solve...

@sdottaka
Copy link
Member

That certainly seems like it. As of now, I don't know why either.

@Edi61
Copy link
Author

Edi61 commented Apr 17, 2024

I have found the root cause.

It's the function call FreeImage_Composite inside fipWinImage::drawEx:
https://github.com/WinMerge/freeimage/blob/78364ed94b4cb2d830805a26cb65bf5e6f523d58/Wrapper/FreeImagePlus/src/fipWinImage.cpp#L407
in case of the image transparent:
https://github.com/WinMerge/freeimage/blob/78364ed94b4cb2d830805a26cb65bf5e6f523d58/Wrapper/FreeImagePlus/src/fipWinImage.cpp#L399

Problem is that a copy of a new display including transparency is created and this copy
is not updated anymore. Even if it would be updated it would slow down the wipe effect due to recreation of the transparency each time :-(.

Transparency detection and triggering of a redraw in case of image wiping
@Edi61
Copy link
Author

Edi61 commented Apr 20, 2024

I have found one solution, maybe not the best, but seems principially working.
If there is transparency speed up is a little lower.

@Edi61 Edi61 marked this pull request as ready for review January 12, 2026 14:06
@sdottaka
Copy link
Member

sdottaka commented Mar 8, 2026

Sorry for the late response.
It looks like several issues have been fixed. Is this PR ready to be merged now?

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to speed up the image comparison wipe/slider interaction by avoiding full image refreshes on every mouse move and by applying the wipe effect incrementally between old/new slider positions.

Changes:

  • Only trigger redraw/invalidations during wipe dragging when the wipe position actually changes.
  • Change wipe updates from “rebuild diff images every time” to an incremental WipeEffect() based on old/new wipe positions.
  • Add transparency tracking to handle redraw behavior with transparent panes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/WinIMergeLib/ImgMergeWindow.hpp Reduces redraw work during wipe dragging by only invalidating when the wipe position changes, with special handling for transparency.
src/WinIMergeLib/ImgDiffBuffer.hpp Implements incremental wipe updates using m_wipePosition_old, adds pane transparency helpers, and changes wipe positioning to call WipeEffect() instead of RefreshImages().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +813 to +817
// Dummy operation with less performance to retrigger redrawing
// setPixelColor leads to _bHasChanged = TRUE which will enable redrawing
RGBQUAD color;
m_imgDiff[i].getFipImage()->getPixelColor(0, 0, &color);
m_imgDiff[i].getFipImage()->setPixelColor(0, 0, &color);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetWipePosition() does a get/set of pixel (0,0) to force FreeImage to mark the bitmap changed. This will crash or corrupt memory if a diff image ever has zero width/height. Please guard the dummy operation with a size check (and/or prefer a safer API like setModified(true) if it has the desired effect for transparent panes too).

Suggested change
// Dummy operation with less performance to retrigger redrawing
// setPixelColor leads to _bHasChanged = TRUE which will enable redrawing
RGBQUAD color;
m_imgDiff[i].getFipImage()->getPixelColor(0, 0, &color);
m_imgDiff[i].getFipImage()->setPixelColor(0, 0, &color);
// Dummy operation with less performance to retrigger redrawing.
// setPixelColor leads to _bHasChanged = TRUE which will enable redrawing.
// Guard against zero-sized images to avoid out-of-bounds pixel access.
auto* img = m_imgDiff[i].getFipImage();
if (img != nullptr && img->getWidth() > 0 && img->getHeight() > 0)
{
RGBQUAD color;
img->getPixelColor(0, 0, &color);
img->setPixelColor(0, 0, &color);
}
else if (img != nullptr)
{
// Fall back to the safer API for zero-sized images.
img->setModified(true);
}

Copilot uses AI. Check for mistakes.
MarkDiff(i, m_diff);
}
}
m_wipePosition_old = INT_MAX;
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RefreshImages() now resets m_wipePosition_old but no longer reapplies WipeEffect() when m_wipeMode != WIPE_NONE. Since RefreshImages() is triggered by the blink/overlay animation timer, this can rebuild m_imgDiff into a non-wiped state while leaving m_wipePosition unchanged; subsequent SetWipePosition() calls may early-return (position unchanged) and the wipe can appear to “get lost” until the mouse moves. Reapply WipeEffect() as part of RefreshImages() when wipe mode is active (or otherwise ensure the wiped state is restored whenever m_imgDiff is regenerated).

Suggested change
m_wipePosition_old = INT_MAX;
m_wipePosition_old = INT_MAX;
if (m_wipeMode != WIPE_NONE)
{
WipeEffect();
}

Copilot uses AI. Check for mistakes.
Comment on lines +1961 to +1963
const size_t lineBytes = w * 4;
auto tmp = new unsigned char[lineBytes];
if (m_wipePosition <= m_wipePosition_old)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WipeEffect() allocates/free's a temporary line buffer with new[]/delete[] on every call. With mouse-driven wiping this can become a hot path and the allocator churn can offset some of the intended speedup. Consider using a std::vector<unsigned char> (or a reusable member buffer) to avoid repeated heap allocations and make the code exception-safe.

Copilot uses AI. Check for mistakes.
{
for (int i = 0; i < 3; ++i)
m_currentPage[i] = 0;
for (int i = 0; i < m_nImages; ++i)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_imgDiffIsTransparent isn't initialized because the loop runs i < m_nImages, but m_nImages is 0 in the constructor initializer list. This leaves the array with indeterminate values until SetWipeMode* is called, and methods like IsAnyPaneTransparent()/IsPaneTransparent() can read garbage. Initialize all 3 entries explicitly (or default-initialize the member) regardless of m_nImages.

Suggested change
for (int i = 0; i < m_nImages; ++i)
for (int i = 0; i < 3; ++i)

Copilot uses AI. Check for mistakes.
Comment on lines 783 to +795
void SetWipeMode(WIPE_MODE wipeMode)
{
if (m_wipeMode == wipeMode)
return;
m_wipeMode = wipeMode;
RefreshImages();
for (int i = 0; i < m_nImages; ++i)
{
if (m_imgDiff[i].getFipImage()->isTransparent())
m_imgDiffIsTransparent[i] = true;
else
m_imgDiffIsTransparent[i] = false;
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_imgDiffIsTransparent is only refreshed in SetWipeMode()/SetWipeModePosition(), but RefreshImages() is called from several other code paths (including the animation/blink timer) and can rebuild m_imgDiff such that transparency changes. If this cache becomes stale, SetWipePosition() and the window code will make redraw decisions using incorrect transparency info. Consider recomputing transparency inside RefreshImages() (or extracting a helper used by all code paths that rebuild m_imgDiff).

Copilot uses AI. Check for mistakes.
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.

3 participants