Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Comparer/Comparer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

#include "ComparerDoc.h"

#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")

#ifdef _DEBUG
#define new DEBUG_NEW
#endif
Expand Down Expand Up @@ -62,6 +65,9 @@ BOOL CComparerApp::InitInstance()
{
CWinApp::InitInstance();

Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&mGdiplusToken, &gdiplusStartupInput, NULL);

SetRegistryKey(_T("Chammoru"));
LoadStdProfileSettings(4); // Load standard INI file options (including MRU)

Expand Down Expand Up @@ -108,6 +114,12 @@ BOOL CComparerApp::InitInstance()
return TRUE;
}

int CComparerApp::ExitInstance()
{
Gdiplus::GdiplusShutdown(mGdiplusToken);
return CWinApp::ExitInstance();
}



// CAboutDlg dialog used for App About
Expand Down
4 changes: 4 additions & 0 deletions Comparer/Comparer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ class CComparerApp : public CWinApp
// Overrides
public:
virtual BOOL InitInstance();
virtual int ExitInstance();

// Implementation
afx_msg void OnAppAbout();
DECLARE_MESSAGE_MAP()
afx_msg void OnFileOpen();

private:
ULONG_PTR mGdiplusToken;
};

extern CComparerApp theApp;
1 change: 1 addition & 0 deletions Comparer/ComparerDoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ CComparerDoc::CComparerDoc()
, mFps(COMPARER_DEF_FPS)
, mInterpol(false)
, mDiffRes(false)
, mDiffOverlay(true)
{
BITMAPINFOHEADER &bmiHeader = mBmi.bmiHeader;
bmiHeader.biSize = (DWORD)sizeof(BITMAPINFOHEADER);
Expand Down
1 change: 1 addition & 0 deletions Comparer/ComparerDoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class CComparerDoc : public CDocument
double mFps;
bool mInterpol;
bool mDiffRes;
bool mDiffOverlay;

// Operations
public:
Expand Down
159 changes: 159 additions & 0 deletions Comparer/ComparerView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <QViewerCmn.h>
#include <QImageStr.h>

#include <gdiplus.h>

// CComparerView

CComparerView::CComparerView()
Expand All @@ -27,6 +29,7 @@ CComparerView::CComparerView()
, mHCanvas(0)
, mIsClicked(false)
, mProcessing(false)
, mShowHelp(false)
, mRgbBufSize(0)
, mRgbBuf(NULL)
{
Expand Down Expand Up @@ -242,11 +245,161 @@ void CComparerView::OnDraw(CDC *pDC)
}
}

DrawDiffOverlay(&memDC, pDoc, pane);

if (mShowHelp)
DrawHelpMenu(&memDC);

pDC->BitBlt(0, mRcControls.bottom, mWCanvas, mHCanvas, &memDC, 0, 0, SRCCOPY);

mProcessing = false;
}

void CComparerView::ToggleHelp()
{
mShowHelp = !mShowHelp;
Invalidate(FALSE);
}

void CComparerView::DrawHelpMenu(CDC *pDC)
{
const int W_HELP = 460;
const int H_HELP = 280;
const int W_MARGIN = 18;
const int H_MARGIN = 14;
const int X_HELP = (mWCanvas - W_HELP) / 2;
const int Y_HELP = (mHCanvas - H_HELP) / 2;
CRect bgRect(X_HELP, Y_HELP, X_HELP + W_HELP, Y_HELP + H_HELP);
pDC->FillSolidRect(bgRect, Q1UI_COLOR_SURFACE);
CPen borderPen(PS_SOLID, 1, Q1UI_COLOR_BORDER);
CPen *prevPen = pDC->SelectObject(&borderPen);
pDC->SelectStockObject(NULL_BRUSH);
pDC->Rectangle(bgRect);
pDC->SelectObject(prevPen);

CRect manualRect(bgRect.left + W_MARGIN, bgRect.top + H_MARGIN,
bgRect.right - W_MARGIN, bgRect.bottom - H_MARGIN);
LOGFONT lf;
CFont manualFont;
mDefPixelTextFont.GetLogFont(&lf);
lf.lfHeight = 14;
lf.lfWeight = FW_NORMAL;
manualFont.CreateFontIndirect(&lf);
pDC->SetBkMode(TRANSPARENT);
CFont *prevFont = pDC->SelectObject(&manualFont);
pDC->SetTextColor(Q1UI_COLOR_TEXT);
CString manual(
"Comparer shortcuts\n"
"\n"
"? Show or hide this panel\n"
"Drag && Drop Open a source into a pane\n"
"Mouse Wheel Zoom in or out; high zoom shows pixel values\n"
"Left/Right Previous or next video frame\n"
"Space Play or pause\n"
"H Toggle hex pixel values\n"
"I Interpolate pixels\n"
"D Toggle pink diff overlay (grid + dots)\n"
"Click timeline Pick a video frame (left/right pane)\n"
);
pDC->DrawText(manual, &manualRect, DT_LEFT | DT_TOP);
pDC->SelectObject(prevFont);
}

// Pilot: draw a semi-transparent pink cell outline + center dot in every
// grid cell that contains at least one pixel that differs from the reference
// pane. Cell size is fixed in display pixels, so zooming in implicitly
// subdivides the source region each cell covers — at maximum zoom each dot
// resolves to a single differing source pixel.
void CComparerView::DrawDiffOverlay(CDC *pDC, CComparerDoc *pDoc, ComparerPane *pane)
{
if (!pDoc->mDiffOverlay)
return;
// At high zoom the per-pixel value labels already convey the diff
// information; the grid and dots would just clutter the view.
if (pDoc->mN > ZOOM_TEXT_START)
return;
if (!pane || !pane->isAvail() || !pane->rgbBuf)
return;

// Find any other available pane to compare against (pilot assumes 2 panes).
ComparerPane *other = nullptr;
for (int i = 0; i < CComparerDoc::IMG_VIEW_MAX; i++) {
ComparerPane *p = &pDoc->mPane[i];
if (p == pane)
continue;
if (p->isAvail() && p->rgbBuf) {
other = p;
break;
}
}
if (!other || pDoc->mW <= 0 || pDoc->mH <= 0)
return;

const int rowBytes = ROUNDUP_DWORD(pDoc->mW) * QIMG_DST_RGB_BYTES;
const BYTE *bufA = pane->rgbBuf;
const BYTE *bufB = other->rgbBuf;

const int cellPx = 48; // grid cell size in display pixels
const int dotR = 3; // center dot radius

Gdiplus::Graphics g(pDC->m_hDC);
g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
g.SetCompositingMode(Gdiplus::CompositingModeSourceOver);

const Gdiplus::Color cellColor(200, 0xff, 0x3d, 0x8a); // ~78% alpha pink
const Gdiplus::Color dotColor (235, 0xff, 0x3d, 0x8a); // ~92% alpha pink
Gdiplus::Pen cellPen(cellColor, 1.0f);
cellPen.SetAlignment(Gdiplus::PenAlignmentInset);
Gdiplus::SolidBrush dotBrush(dotColor);

const float invN = (pDoc->mN > 0.0f) ? (1.0f / pDoc->mN) : 1.0f;

for (int cy = 0; cy < mHCanvas; cy += cellPx) {
int cellBot = cy + cellPx;
if (cellBot > mHCanvas) cellBot = mHCanvas;

int sy0 = (int)floorf((cy - mYDst) * invN);
int sy1 = (int)ceilf ((cellBot - mYDst) * invN);
if (sy0 < 0) sy0 = 0;
if (sy1 > pDoc->mH) sy1 = pDoc->mH;
if (sy0 >= sy1) continue;

for (int cx = 0; cx < mWCanvas; cx += cellPx) {
int cellRight = cx + cellPx;
if (cellRight > mWCanvas) cellRight = mWCanvas;

int sx0 = (int)floorf((cx - mXDst) * invN);
int sx1 = (int)ceilf ((cellRight - mXDst) * invN);
if (sx0 < 0) sx0 = 0;
if (sx1 > pDoc->mW) sx1 = pDoc->mW;
if (sx0 >= sx1) continue;

bool hasDiff = false;
const int byteStart = sx0 * QIMG_DST_RGB_BYTES;
const int byteLen = (sx1 - sx0) * QIMG_DST_RGB_BYTES;
for (int sy = sy0; sy < sy1; sy++) {
const BYTE *ra = bufA + sy * rowBytes + byteStart;
const BYTE *rb = bufB + sy * rowBytes + byteStart;
if (memcmp(ra, rb, byteLen) != 0) {
hasDiff = true;
break;
}
}

if (hasDiff) {
g.DrawRectangle(&cellPen,
(float)cx, (float)cy,
(float)(cellRight - cx - 1), (float)(cellBot - cy - 1));

float dotX = (cx + cellRight) * 0.5f;
float dotY = (cy + cellBot) * 0.5f;
g.FillEllipse(&dotBrush,
dotX - dotR, dotY - dotR, 2.0f * dotR, 2.0f * dotR);
}
}
}
}

void CComparerView::DrawEmptyPane(CDC *pDC, CComparerDoc *pDoc)
{
CRect canvas(0, mRcControls.bottom, mWClient, mHClient);
Expand Down Expand Up @@ -678,6 +831,12 @@ void CComparerView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
pDoc->mInterpol = !pDoc->mInterpol;
Invalidate(FALSE);
break;
case 'D':
pDoc->mDiffOverlay = !pDoc->mDiffOverlay;
break;
case VK_OEM_2: // '?' / '/' on US keyboards.
ToggleHelp();
break;
}

// Most shortcuts affect shared document/view state, so refresh once at the end.
Expand Down
4 changes: 4 additions & 0 deletions Comparer/ComparerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class CComparerView : public CScrollView
CQMenuItem mNameQMenu;
CQMenuItem mCsQMenu;
bool mProcessing;
bool mShowHelp;
int mRgbBufSize;
BYTE *mRgbBuf;
CFont mDefPixelTextFont;
Expand All @@ -57,6 +58,9 @@ class CComparerView : public CScrollView
void ScaleNearestNeighbor(CComparerDoc *pDoc, BYTE *src, BYTE *dst, int sDst,
q1::GridInfo &gi);
void DrawEmptyPane(CDC *pDC, CComparerDoc *pDoc);
void DrawDiffOverlay(CDC *pDC, CComparerDoc *pDoc, ComparerPane *pane);
void DrawHelpMenu(CDC *pDC);
void ToggleHelp();

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDropFiles(HDROP hDropInfo);
Expand Down
42 changes: 14 additions & 28 deletions Comparer/PosInfoView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,6 @@ void CPosInfoView::OnInitialUpdate()
CScrollView::OnInitialUpdate();
}

static void DrawDiffPosLines(CDC *pDC, CRect *frameRect, bool *flags, int n)
{
int top = frameRect->top + 1;
int left = frameRect->left + 1;
int right = frameRect->right - 1;

for (int i = 0; i < n; i++) {
if (flags[i] == false)
continue;

pDC->MoveTo(left, top + i);
pDC->LineTo(right, top + i);
}
}

void CPosInfoView::DrawEachRect(CDC* pDC,
ComparerPane *pane,
CRect *frameRect,
Expand All @@ -98,17 +83,9 @@ void CPosInfoView::DrawEachRect(CDC* pDC,
pDC->FillSolidRect(frameRect, frameColor);
pDC->Draw3dRect(frameRect, Q1UI_COLOR_SURFACE, Q1UI_COLOR_SURFACE);

if (parseDone) {
CPen *prev = pDC->SelectObject(mDiffPen);
IFrmCmpStrategy *frmCmpStrategy = pDoc->mFrmCmpStrategy;
list<RLC> diffRLC[QPLANES];

if (mFileScanThread->copyDiffRLC(i, diffRLC)) {
frmCmpStrategy->FlagTotalDiffLine(diffRLC, mDiffFlags, mPosLinesPerFrame);
DrawDiffPosLines(pDC, frameRect, mDiffFlags, mPosLinesPerFrame);
}
pDC->SelectObject(prev);
}
// Per-row red diff lines have been replaced by the pink grid overlay in
// ComparerView. Pilot keeps the frame tiles for video-frame selection but
// no longer draws the per-row indicators.

const COLORREF curIdColor = Q1UI_COLOR_ACCENT;
COLORREF preColor;
Expand Down Expand Up @@ -183,7 +160,13 @@ void CPosInfoView::OnDraw(CDC* pDC)

memDC.FillSolidRect(CRect(0, 0, mWClient, h), Q1UI_COLOR_APP_BG);

if (!paneL->isAvail() && !paneR->isAvail()) {
// Pilot: hide the timeline entirely when neither pane is a video. The pink
// grid overlay in ComparerView already conveys diff information; the
// timeline is only useful for picking a frame in a video source.
bool hasVideo = (paneL->isAvail() && paneL->frames > 1)
|| (paneR->isAvail() && paneR->frames > 1);

if (!paneL->isAvail() && !paneR->isAvail() || !hasVideo) {
LOGFONT lf;
mPosNumFont.GetLogFont(&lf);
::lstrcpy(lf.lfFaceName, Q1UI_FONT_TEXT);
Expand All @@ -195,7 +178,10 @@ void CPosInfoView::OnDraw(CDC* pDC)
memDC.SetBkMode(TRANSPARENT);
memDC.SetTextColor(Q1UI_COLOR_TEXT_MUTED);
CRect msgRect(0, 0, mWClient, h);
memDC.DrawText(_T("Timeline"), &msgRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
LPCTSTR msg = (paneL->isAvail() || paneR->isAvail())
? _T("Timeline\n(video only)") : _T("Timeline");
memDC.DrawText(msg, &msgRect,
DT_CENTER | DT_VCENTER | DT_WORDBREAK | DT_NOCLIP);
memDC.SelectObject(prevFont);
goto OnDrawExit;
}
Expand Down
28 changes: 28 additions & 0 deletions docs/USER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ Viewer includes a built-in control panel, opened with `?`.
| Toggle box info display | `B` |
| Toggle pixel interpolation | `I` |
| Next color space | `N` |
| Mute or unmute video audio | `M` |
| Toggle selection mode | `S` |
| Capture view or selected region | `Ctrl+C` |

Expand Down Expand Up @@ -88,6 +89,33 @@ The same reference and encoded image below are shown with SSIM selected:

![Comparer showing SSIM for the same sources](images/comparer-ssim.webp)

### Pixel-Level Diff Overlay

When two sources are loaded, Comparer highlights every region where the pixels
differ from the reference pane. The image area is divided into a fixed-size
grid in display pixels; each cell that contains any differing pixel gets a
translucent pink rectangle outline plus a center dot. Because the cell size is
fixed on screen, zooming in implicitly subdivides the source area each cell
covers — at maximum zoom each dot resolves to a single differing source pixel.
The overlay is hidden automatically when zoom is high enough to show per-pixel
values, since the pixel labels already convey the diff.

### Controls

Comparer also includes a built-in shortcut panel, opened with `?`.

| Action | Control |
| --- | --- |
| Show or hide the shortcut panel | `?` |
| Open a source into a pane | Drag and drop |
| Zoom | Mouse wheel |
| Previous or next video frame | Left / Right |
| Play or pause | `Space` |
| Toggle hex pixel values | `H` |
| Toggle pixel interpolation | `I` |
| Toggle pink diff overlay (grid + dots) | `D` |
| Pick a frame in the timeline (video) | Click left or right side |

## Input Notes

- HEIF/HEIC/HIF and AVIF still images are supported by both applications.
Expand Down