I had a bit of spare time this evening while waiting for someone, so decided to have a bit of a play. I haven't bothered with error checking, you should add that yourself. I've also elected to program in a style similar to the one MS used when programming many of the different window-classes, as found in portions of the (re-engineered) Wine project. (
https://github.com/wine-mirror/wine/tree/master/dlls/comctl32[
^])
I've tried to make it very simple to interact with.
First, you must register the class - you can do this during program startup, before anything is shown on the screen.
Next, the two messages I've defined. IM_SETIMAGE and IM_GETCLICKPOS. You can send these messages explicitly, or you can use the supplied macros.
If you elect to send these messages yourself, you need only know:
IM_SETIMAGE expects the lParam to hold the HBITMAP of the new image. The return value is the previous image (if any, NULL otherwise)
IM_GETCLICKPOS takes no parameters and returns the position within the image of the last click pos (you get notified of clicks through a WM_COMMAND message). The lo-order word contains the X position and the hi-order word contains the Y position. Positions to the left/above the image return negative values.
The background colour isn't customizable from within the containing application, though you could easily add code to handle this situation, or more simply, edit the function onImgWindowEraseBackground.
The code's far from perfect, but should help provide a good starting point. You'll still have a bit of work. While you mention passing the name of a file to the program in order to choose the image, I'd suggest that you approach this with a little more sophistication and create a 'config' file of sorts. In this file, you could include things like (1) the name of the image file (2) the number of areas of interest (3) co-ordinate pairs for the points of a polygon that enclose these areas.
Perhaps something like this:
------------config.txt------------
Image: image.bmp
Poly Count: 3
Apple: 10,10, 20,10, 20,20, 10,20
Water Melon: 100,100, 200,100, 200,200, 100,200
Triangle: 50,50, 75,75, 25,75
----------------------------------
For a basic example of use, I've thrown-together the following dialog app. (written with Code::Blocks, built with MinGW)
main.cpp
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include "resource.h"
#include "../scrollImage/imgWindow.h"
HINSTANCE hInst;
BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
HBITMAP img;
img = (HBITMAP)LoadImage(hInst, "image.bmp", IMAGE_BITMAP, 0,0, LR_LOADFROMFILE);
ImageWindow_SetImage( GetDlgItem(hwndDlg, IDC_IMAGE_WINDOW), img);
}
return TRUE;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case IDC_IMAGE_WINDOW:
LPARAM clickPos = ImageWindow_GetClickPos( (HWND)lParam );
short xPos = LOWORD(clickPos);
short yPos = HIWORD(clickPos);
char msg[32];
sprintf(msg, "Click pos: %d,%d\n", xPos, yPos);
SetDlgItemText(hwndDlg, IDC_CLICKPOS_OUTPUT, msg);
break;
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
hInst=hInstance;
InitCommonControls();
IMGWINDOW_Register();
return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
}
resource.h
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
#define DLG_MAIN 100
#define IDC_CLICKPOS_OUTPUT 40000
#define IDC_IMAGE_WINDOW 40001
resource.rc
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
DLG_MAIN DIALOG 0, 0, 185, 166
STYLE DS_3DLOOK | DS_CENTER | DS_MODALFRAME | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU
CAPTION "Dialog"
FONT 8, "Ms Shell Dlg"
{
LTEXT "", IDC_CLICKPOS_OUTPUT, 7, 151, 171, 8, SS_LEFT, WS_EX_LEFT
CONTROL "", IDC_IMAGE_WINDOW, "imgWindow32", 0x50020000, 7, 7, 171, 139, 0x00000000
}
Finally, here's the meat and bones of the image display and click-pos calculation.
Again, quite simple even if a little lengthy.
imgWindow.h
#ifndef imgWindow_h
#define imgWindow_h
#include <windows.h>
BOOL IMGWINDOW_Register();
void IMGWINDOW_Unregister();
#define IMGWINDOW_CLASSA "imgWindow32"
#define IMGWINDOW_CLASSW L"imgWindow32"
#ifdef UNICODE
#define IMGWINDOW_CLASS IMGWINDOW_CLASSW
#else
#define IMGWINDOW_CLASS IMGWINDOW_CLASSA
#endif // UNICODE
#define IM_SETIMAGE WM_USER + 1
#define IM_GETCLICKPOS WM_USER + 2
#define ImageWindow_SetImage(hWnd, hBitmap) SendMessage(hWnd, IM_SETIMAGE, 0, (LPARAM)hBitmap)
#define ImageWindow_GetClickPos(hWnd) SendMessage(hWnd, IM_GETCLICKPOS, 0, 0)
#endif // imgWindow_h
imgWindow.cpp
#include "imgWindow.h"
#ifndef min
#define min(a,b) a < b ? a : b
#endif // min
typedef struct
{
HWND self;
short clickX, clickY;
BOOL mouseDown;
int xScrollPos, yScrollPos;
int xImgOfs, yImgOfs;
HBITMAP bkg;
HDC memDC;
HBITMAP oldMemBmp;
int bmpWidth, bmpHeight;
RECT clientRect;
} IMGWINDOW_INFO;
#define IMGWINDOW_GetInfoPtr(hWindow) ((IMGWINDOW_INFO *)GetWindowLongPtr (hWindow, 0))
int onImgWindowCreate(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO*)LocalAlloc(LPTR, sizeof(IMGWINDOW_INFO));
if (!infoPtr)
return -1;
ZeroMemory(infoPtr, sizeof(infoPtr));
infoPtr->self = hwnd;
infoPtr->clickX = -1;
infoPtr->clickY = -1;
HDC mDC;
mDC = GetDC(hwnd);
infoPtr->memDC = CreateCompatibleDC(mDC);
infoPtr->oldMemBmp = (HBITMAP) GetCurrentObject(infoPtr->memDC, OBJ_BITMAP);
ReleaseDC(hwnd, mDC);
GetClientRect(hwnd, &infoPtr->clientRect);
SetWindowLong(hwnd, 0, (LONG)infoPtr); return 0;
}
int onImgWindowDestroy(HWND hwnd)
{
IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO*)LocalAlloc(LPTR, sizeof(IMGWINDOW_INFO));
SelectObject(infoPtr->memDC, infoPtr->oldMemBmp);
DeleteDC( infoPtr->memDC );
LocalFree(infoPtr);
return 0;
}
int onImgWindowEraseBackground(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
RECT clientRect;
HBRUSH bkBrush;
IMGWINDOW_INFO *infoPtr = IMGWINDOW_GetInfoPtr(hwnd);
bkBrush = CreateSolidBrush( RGB(255,0,255) );
GetClientRect(hwnd, &clientRect);
FillRect( (HDC)wParam, &clientRect, bkBrush);
DeleteObject(bkBrush);
return 1;
}
int doImgWindowScroll(HWND hwnd, WPARAM wParam, int orientation, int windowMin, int windowMax)
{
int windowSize = windowMax - windowMin + 1;
switch (LOWORD(wParam))
{
case SB_THUMBTRACK:
SetScrollPos(hwnd, orientation, HIWORD(wParam), true);
break;
case SB_PAGERIGHT:
SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)+windowSize, true);
break;
case SB_PAGELEFT:
SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)-windowSize, true);
break;
case SB_LINELEFT:
SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)-16, true);
break;
case SB_LINERIGHT:
SetScrollPos(hwnd, orientation, GetScrollPos(hwnd, orientation)+16, true);
break;
}
IMGWINDOW_INFO *mData = IMGWINDOW_GetInfoPtr(hwnd);
int scrollPos = GetScrollPos(hwnd, orientation);
if (orientation == SB_HORZ)
mData->xScrollPos = scrollPos;
else if (orientation == SB_VERT)
mData->yScrollPos = scrollPos;
InvalidateRect(hwnd, NULL, true);
return 0;
}
int onImgWindowHscroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
return doImgWindowScroll(hwnd, wParam, SB_HORZ, clientRect.left, clientRect.right);
}
int onImgWindowVscroll(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
return doImgWindowScroll(hwnd, wParam, SB_VERT, clientRect.top, clientRect.bottom);
}
void imgWindowSetScrollInfo(HWND hwnd)
{
int mWidth;
RECT mRect;
SCROLLINFO si = {};
IMGWINDOW_INFO *infoPtr = IMGWINDOW_GetInfoPtr(hwnd);
GetClientRect(hwnd, &mRect);
mWidth = mRect.right - mRect.left + 1;
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = infoPtr->bmpWidth; si.nPage = mWidth;
si.nPos = 0;
infoPtr->xScrollPos = 0;
SetScrollInfo(hwnd, SB_HORZ, &si, false);
int mHeight;
mHeight = mRect.bottom-mRect.top + 1;
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
si.nMin = 0;
si.nMax = infoPtr->bmpHeight; si.nPage = mHeight;
si.nPos = 0;
infoPtr->yScrollPos = 0;
SetScrollInfo(hwnd, SB_VERT, &si, true);
}
LRESULT onImgWindowSetImage(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO *)GetWindowLongPtr(hwnd, 0);
HBITMAP oldImg;
oldImg = infoPtr->bkg;
infoPtr->bkg = (HBITMAP) lParam;
BITMAP bm;
GetObject(infoPtr->bkg, sizeof(bm), &bm);
infoPtr->bmpWidth = bm.bmWidth;
infoPtr->bmpHeight = bm.bmHeight;
if (infoPtr->bmpWidth > (infoPtr->clientRect.right-infoPtr->clientRect.left) )
ShowScrollBar(hwnd, SB_HORZ, true);
else
ShowScrollBar(hwnd, SB_HORZ, false);
if (infoPtr->bmpHeight > (infoPtr->clientRect.bottom-infoPtr->clientRect.top) )
ShowScrollBar(hwnd, SB_VERT, true);
else
ShowScrollBar(hwnd, SB_VERT, false);
imgWindowSetScrollInfo(hwnd);
SelectObject(infoPtr->memDC, infoPtr->bkg);
InvalidateRect(infoPtr->self, NULL, true);
return (LRESULT)oldImg;
}
LRESULT CALLBACK IMGWINDOW_WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
IMGWINDOW_INFO *infoPtr = (IMGWINDOW_INFO *)GetWindowLongPtr(hwnd, 0);
switch (message) {
case WM_CREATE:
return onImgWindowCreate(hwnd, wParam, lParam);
case WM_DESTROY:
return onImgWindowDestroy(hwnd);
case IM_SETIMAGE:
return onImgWindowSetImage(hwnd, wParam, lParam);
case WM_ERASEBKGND:
return onImgWindowEraseBackground(hwnd, wParam, lParam);
case WM_PAINT:
HDC hdc;
PAINTSTRUCT ps;
int mWidth, mHeight;
RECT mRect;
GetClientRect(hwnd, &mRect);
mWidth = mRect.right-mRect.left + 1;
mHeight = mRect.bottom-mRect.top + 1;
hdc = BeginPaint(hwnd, &ps);
int xOfs,yOfs;
xOfs = yOfs = 0;
if (infoPtr->bmpWidth < mWidth)
xOfs = (mWidth-infoPtr->bmpWidth)/2;
if (infoPtr->bmpHeight < mHeight)
yOfs = (mHeight-infoPtr->bmpHeight)/2;
infoPtr->xImgOfs = xOfs;
infoPtr->yImgOfs = yOfs;
int drawWidth, drawHeight;
drawWidth = min(infoPtr->bmpWidth, mWidth);
drawHeight = min(infoPtr->bmpHeight, mHeight);
int imgXofs, imgYofs;
imgXofs = infoPtr->xScrollPos;
imgYofs = infoPtr->yScrollPos;
BitBlt(
hdc,
xOfs, yOfs,
drawWidth,drawHeight,
infoPtr->memDC,
imgXofs,imgYofs,
SRCCOPY);
EndPaint(hwnd, &ps);
return 0;
case WM_HSCROLL:
return onImgWindowHscroll(hwnd, wParam, lParam);
case WM_VSCROLL:
return onImgWindowVscroll(hwnd, wParam, lParam);
case WM_SIZE:
GetClientRect(hwnd, &infoPtr->clientRect);
InvalidateRect(hwnd, NULL, true);
return 0;
case WM_GETDLGCODE:
return DLGC_STATIC;
case WM_LBUTTONDOWN:
infoPtr->mouseDown = true;
return 0;
case WM_LBUTTONUP:
{
infoPtr->clickX = (short)(LOWORD(lParam) + infoPtr->xScrollPos - infoPtr->xImgOfs);
infoPtr->clickY = (short)(HIWORD(lParam) + infoPtr->yScrollPos - infoPtr->yImgOfs);
short myNotifIdCode = 0;
if (infoPtr->mouseDown == true)
SendMessage(GetParent(infoPtr->self), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(infoPtr->self),myNotifIdCode), (LPARAM)infoPtr->self);
}
infoPtr->mouseDown = false;
break;
case IM_GETCLICKPOS:
return MAKELPARAM(infoPtr->clickX, infoPtr->clickY);
}
return DefWindowProc (hwnd, message, wParam, lParam);
}
BOOL IMGWINDOW_Register(void)
{
WNDCLASS wndClass;
ZeroMemory (&wndClass, sizeof(WNDCLASS));
wndClass.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW | CS_OWNDC; wndClass.lpfnWndProc = IMGWINDOW_WindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = sizeof(IMGWINDOW_INFO *); wndClass.hCursor = LoadCursor(0, IDC_ARROW);
wndClass.hbrBackground = 0;
wndClass.lpszClassName = IMGWINDOW_CLASS;
if (RegisterClass(&wndClass) != 0)
return true;
else
return false;
}
void IMGWINDOW_Unregister(void)
{
UnregisterClass(IMGWINDOW_CLASS, NULL);
}