Skip to content

Commit 40fb091

Browse files
committed
Comparer: pink-dot grid overlay for pixel-level diffs (#13)
The image area in each pane is divided into a fixed-size grid in display pixels. Every cell that contains at least one pixel that differs from the reference pane is highlighted with a translucent pink rectangle outline plus a center dot, drawn with GDI+ alpha blending so the underlying image stays visible. Because the cell size is fixed in display pixels, zooming in implicitly subdivides the source region each cell covers — at maximum zoom each dot resolves to a single differing source pixel. 'D' toggles the overlay on and off (default on). Per-row red diff lines in PosInfoView are removed (the grid overlay covers the same information). The timeline is hidden when neither pane is a video; frame picking is still available for video sources.
1 parent 82ef8e0 commit 40fb091

7 files changed

Lines changed: 131 additions & 28 deletions

File tree

Comparer/Comparer.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
#include "ComparerDoc.h"
1111

12+
#include <gdiplus.h>
13+
#pragma comment(lib, "gdiplus.lib")
14+
1215
#ifdef _DEBUG
1316
#define new DEBUG_NEW
1417
#endif
@@ -62,6 +65,9 @@ BOOL CComparerApp::InitInstance()
6265
{
6366
CWinApp::InitInstance();
6467

68+
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
69+
Gdiplus::GdiplusStartup(&mGdiplusToken, &gdiplusStartupInput, NULL);
70+
6571
SetRegistryKey(_T("Chammoru"));
6672
LoadStdProfileSettings(4); // Load standard INI file options (including MRU)
6773

@@ -108,6 +114,12 @@ BOOL CComparerApp::InitInstance()
108114
return TRUE;
109115
}
110116

117+
int CComparerApp::ExitInstance()
118+
{
119+
Gdiplus::GdiplusShutdown(mGdiplusToken);
120+
return CWinApp::ExitInstance();
121+
}
122+
111123

112124

113125
// CAboutDlg dialog used for App About

Comparer/Comparer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ class CComparerApp : public CWinApp
2424
// Overrides
2525
public:
2626
virtual BOOL InitInstance();
27+
virtual int ExitInstance();
2728

2829
// Implementation
2930
afx_msg void OnAppAbout();
3031
DECLARE_MESSAGE_MAP()
3132
afx_msg void OnFileOpen();
33+
34+
private:
35+
ULONG_PTR mGdiplusToken;
3236
};
3337

3438
extern CComparerApp theApp;

Comparer/ComparerDoc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ CComparerDoc::CComparerDoc()
6464
, mFps(COMPARER_DEF_FPS)
6565
, mInterpol(false)
6666
, mDiffRes(false)
67+
, mDiffOverlay(true)
6768
{
6869
BITMAPINFOHEADER &bmiHeader = mBmi.bmiHeader;
6970
bmiHeader.biSize = (DWORD)sizeof(BITMAPINFOHEADER);

Comparer/ComparerDoc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class CComparerDoc : public CDocument
7979
double mFps;
8080
bool mInterpol;
8181
bool mDiffRes;
82+
bool mDiffOverlay;
8283

8384
// Operations
8485
public:

Comparer/ComparerView.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <QViewerCmn.h>
1717
#include <QImageStr.h>
1818

19+
#include <gdiplus.h>
20+
1921
// CComparerView
2022

2123
CComparerView::CComparerView()
@@ -242,11 +244,104 @@ void CComparerView::OnDraw(CDC *pDC)
242244
}
243245
}
244246

247+
DrawDiffOverlay(&memDC, pDoc, pane);
248+
245249
pDC->BitBlt(0, mRcControls.bottom, mWCanvas, mHCanvas, &memDC, 0, 0, SRCCOPY);
246250

247251
mProcessing = false;
248252
}
249253

254+
// Pilot: draw a semi-transparent pink cell outline + center dot in every
255+
// grid cell that contains at least one pixel that differs from the reference
256+
// pane. Cell size is fixed in display pixels, so zooming in implicitly
257+
// subdivides the source region each cell covers — at maximum zoom each dot
258+
// resolves to a single differing source pixel.
259+
void CComparerView::DrawDiffOverlay(CDC *pDC, CComparerDoc *pDoc, ComparerPane *pane)
260+
{
261+
if (!pDoc->mDiffOverlay)
262+
return;
263+
if (!pane || !pane->isAvail() || !pane->rgbBuf)
264+
return;
265+
266+
// Find any other available pane to compare against (pilot assumes 2 panes).
267+
ComparerPane *other = nullptr;
268+
for (int i = 0; i < CComparerDoc::IMG_VIEW_MAX; i++) {
269+
ComparerPane *p = &pDoc->mPane[i];
270+
if (p == pane)
271+
continue;
272+
if (p->isAvail() && p->rgbBuf) {
273+
other = p;
274+
break;
275+
}
276+
}
277+
if (!other || pDoc->mW <= 0 || pDoc->mH <= 0)
278+
return;
279+
280+
const int rowBytes = ROUNDUP_DWORD(pDoc->mW) * QIMG_DST_RGB_BYTES;
281+
const BYTE *bufA = pane->rgbBuf;
282+
const BYTE *bufB = other->rgbBuf;
283+
284+
const int cellPx = 48; // grid cell size in display pixels
285+
const int dotR = 3; // center dot radius
286+
287+
Gdiplus::Graphics g(pDC->m_hDC);
288+
g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
289+
g.SetCompositingMode(Gdiplus::CompositingModeSourceOver);
290+
291+
const Gdiplus::Color cellColor(200, 0xff, 0x3d, 0x8a); // ~78% alpha pink
292+
const Gdiplus::Color dotColor (235, 0xff, 0x3d, 0x8a); // ~92% alpha pink
293+
Gdiplus::Pen cellPen(cellColor, 1.0f);
294+
cellPen.SetAlignment(Gdiplus::PenAlignmentInset);
295+
Gdiplus::SolidBrush dotBrush(dotColor);
296+
297+
const float invN = (pDoc->mN > 0.0f) ? (1.0f / pDoc->mN) : 1.0f;
298+
299+
for (int cy = 0; cy < mHCanvas; cy += cellPx) {
300+
int cellBot = cy + cellPx;
301+
if (cellBot > mHCanvas) cellBot = mHCanvas;
302+
303+
int sy0 = (int)floorf((cy - mYDst) * invN);
304+
int sy1 = (int)ceilf ((cellBot - mYDst) * invN);
305+
if (sy0 < 0) sy0 = 0;
306+
if (sy1 > pDoc->mH) sy1 = pDoc->mH;
307+
if (sy0 >= sy1) continue;
308+
309+
for (int cx = 0; cx < mWCanvas; cx += cellPx) {
310+
int cellRight = cx + cellPx;
311+
if (cellRight > mWCanvas) cellRight = mWCanvas;
312+
313+
int sx0 = (int)floorf((cx - mXDst) * invN);
314+
int sx1 = (int)ceilf ((cellRight - mXDst) * invN);
315+
if (sx0 < 0) sx0 = 0;
316+
if (sx1 > pDoc->mW) sx1 = pDoc->mW;
317+
if (sx0 >= sx1) continue;
318+
319+
bool hasDiff = false;
320+
const int byteStart = sx0 * QIMG_DST_RGB_BYTES;
321+
const int byteLen = (sx1 - sx0) * QIMG_DST_RGB_BYTES;
322+
for (int sy = sy0; sy < sy1; sy++) {
323+
const BYTE *ra = bufA + sy * rowBytes + byteStart;
324+
const BYTE *rb = bufB + sy * rowBytes + byteStart;
325+
if (memcmp(ra, rb, byteLen) != 0) {
326+
hasDiff = true;
327+
break;
328+
}
329+
}
330+
331+
if (hasDiff) {
332+
g.DrawRectangle(&cellPen,
333+
(float)cx, (float)cy,
334+
(float)(cellRight - cx - 1), (float)(cellBot - cy - 1));
335+
336+
float dotX = (cx + cellRight) * 0.5f;
337+
float dotY = (cy + cellBot) * 0.5f;
338+
g.FillEllipse(&dotBrush,
339+
dotX - dotR, dotY - dotR, 2.0f * dotR, 2.0f * dotR);
340+
}
341+
}
342+
}
343+
}
344+
250345
void CComparerView::DrawEmptyPane(CDC *pDC, CComparerDoc *pDoc)
251346
{
252347
CRect canvas(0, mRcControls.bottom, mWClient, mHClient);
@@ -678,6 +773,9 @@ void CComparerView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
678773
pDoc->mInterpol = !pDoc->mInterpol;
679774
Invalidate(FALSE);
680775
break;
776+
case 'D':
777+
pDoc->mDiffOverlay = !pDoc->mDiffOverlay;
778+
break;
681779
}
682780

683781
// Most shortcuts affect shared document/view state, so refresh once at the end.

Comparer/ComparerView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class CComparerView : public CScrollView
5757
void ScaleNearestNeighbor(CComparerDoc *pDoc, BYTE *src, BYTE *dst, int sDst,
5858
q1::GridInfo &gi);
5959
void DrawEmptyPane(CDC *pDC, CComparerDoc *pDoc);
60+
void DrawDiffOverlay(CDC *pDC, CComparerDoc *pDoc, ComparerPane *pane);
6061

6162
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
6263
afx_msg void OnDropFiles(HDROP hDropInfo);

Comparer/PosInfoView.cpp

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,6 @@ void CPosInfoView::OnInitialUpdate()
5959
CScrollView::OnInitialUpdate();
6060
}
6161

62-
static void DrawDiffPosLines(CDC *pDC, CRect *frameRect, bool *flags, int n)
63-
{
64-
int top = frameRect->top + 1;
65-
int left = frameRect->left + 1;
66-
int right = frameRect->right - 1;
67-
68-
for (int i = 0; i < n; i++) {
69-
if (flags[i] == false)
70-
continue;
71-
72-
pDC->MoveTo(left, top + i);
73-
pDC->LineTo(right, top + i);
74-
}
75-
}
76-
7762
void CPosInfoView::DrawEachRect(CDC* pDC,
7863
ComparerPane *pane,
7964
CRect *frameRect,
@@ -98,17 +83,9 @@ void CPosInfoView::DrawEachRect(CDC* pDC,
9883
pDC->FillSolidRect(frameRect, frameColor);
9984
pDC->Draw3dRect(frameRect, Q1UI_COLOR_SURFACE, Q1UI_COLOR_SURFACE);
10085

101-
if (parseDone) {
102-
CPen *prev = pDC->SelectObject(mDiffPen);
103-
IFrmCmpStrategy *frmCmpStrategy = pDoc->mFrmCmpStrategy;
104-
list<RLC> diffRLC[QPLANES];
105-
106-
if (mFileScanThread->copyDiffRLC(i, diffRLC)) {
107-
frmCmpStrategy->FlagTotalDiffLine(diffRLC, mDiffFlags, mPosLinesPerFrame);
108-
DrawDiffPosLines(pDC, frameRect, mDiffFlags, mPosLinesPerFrame);
109-
}
110-
pDC->SelectObject(prev);
111-
}
86+
// Per-row red diff lines have been replaced by the pink grid overlay in
87+
// ComparerView. Pilot keeps the frame tiles for video-frame selection but
88+
// no longer draws the per-row indicators.
11289

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

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

186-
if (!paneL->isAvail() && !paneR->isAvail()) {
163+
// Pilot: hide the timeline entirely when neither pane is a video. The pink
164+
// grid overlay in ComparerView already conveys diff information; the
165+
// timeline is only useful for picking a frame in a video source.
166+
bool hasVideo = (paneL->isAvail() && paneL->frames > 1)
167+
|| (paneR->isAvail() && paneR->frames > 1);
168+
169+
if (!paneL->isAvail() && !paneR->isAvail() || !hasVideo) {
187170
LOGFONT lf;
188171
mPosNumFont.GetLogFont(&lf);
189172
::lstrcpy(lf.lfFaceName, Q1UI_FONT_TEXT);
@@ -195,7 +178,10 @@ void CPosInfoView::OnDraw(CDC* pDC)
195178
memDC.SetBkMode(TRANSPARENT);
196179
memDC.SetTextColor(Q1UI_COLOR_TEXT_MUTED);
197180
CRect msgRect(0, 0, mWClient, h);
198-
memDC.DrawText(_T("Timeline"), &msgRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
181+
LPCTSTR msg = (paneL->isAvail() || paneR->isAvail())
182+
? _T("Timeline\n(video only)") : _T("Timeline");
183+
memDC.DrawText(msg, &msgRect,
184+
DT_CENTER | DT_VCENTER | DT_WORDBREAK | DT_NOCLIP);
199185
memDC.SelectObject(prevFont);
200186
goto OnDrawExit;
201187
}

0 commit comments

Comments
 (0)