// XColorSpectrumCtrl.cpp Version 1.1 - see article at CodeProject.com // // Author: Hans Dietrich // hdietrich@gmail.com // // Description: // XColorSpectrumCtrl implements CXColorSpectrumCtrl, a control that mimics the // functionality of the "Standard Colors" color picker in MS Office // // History // Version 1.1 - 2008 April 4 // - Bug fixes // // Version 1.0 - 2008 March 12 // - Initial public release // // License: // This software is released under the Code Project Open License (CPOL), // which may be found here: http://www.codeproject.com/info/eula.aspx // You are free to use this software in any way you like, except that you // may not sell this source code. // // This software is provided "as is" with no expressed or implied warranty. // I accept no liability for any damage or loss of business that this // software may cause. // /////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "windows.h" #include "windowsx.h" #include #include #include #include "XColorSpectrumCtrl.h" //#include "rgbhsl.h" #pragma warning(disable : 4127) // for _ASSERTE: conditional expression is constant #pragma warning(disable : 4996) // disable bogus deprecation warning #pragma warning(disable : 4244) #pragma warning(disable : 4312) #pragma warning(disable : 4267) #ifndef UNUSED #define UNUSED(x) x #endif #ifdef _DEBUG // extracted from mfc\src\afxmem.cpp static void * operator new(size_t nSize, LPCSTR lpszFileName, int nLine) { return ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine); } static void operator delete(void *pData, LPCSTR /*lpszFileName*/, int /*nLine*/) { ::operator delete(pData); } #define DEBUG_NEW new(THIS_FILE, __LINE__) #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #ifndef __noop #if _MSC_VER < 1300 #define __noop ((void)0) #endif #endif #undef TRACE #define TRACE __noop #undef TRACERECT #define TRACERECT __noop //============================================================================= // if you want to see the TRACE output, uncomment this line: //#include "XTrace.h" //============================================================================= // Messages sent to parent window - // wParam = RGB color value // lParam = control id UINT WM_XCOLORPICKER_SELCHANGE = ::RegisterWindowMessage(_T("WM_XCOLORPICKER_SELCHANGE")); UINT WM_XCOLORPICKER_SELENDOK = ::RegisterWindowMessage(_T("WM_XCOLORPICKER_SELENDOK")); //============================================================================= // constants used in drawing static const int ARROW_WIDTH = 10; // slider arrow static const int ARROW_HEIGHT = 17; // slider arrow static const int HORIZONTAL_MARGIN = 2; // right margin only static const int VERTICAL_MARGIN = ARROW_HEIGHT/2 + 3; // this is necessary to // make room for arrow on // slider bar static const int SLIDER_WIDTH = 10; static const int ARROW_DELTA = 5; // delta for arrow keys static const int CROSSHAIR_SIZE = 19; // spectrum crosshair: // height = width static const int LUMINOSITY_BAR_WIDTH = 14; // cx - SLIDER_OFFSET = left side of slider area static const int SLIDER_OFFSET = SLIDER_WIDTH + HORIZONTAL_MARGIN; // cx - LUMINOSITY_BAR_OFFSET = left side of luminosity bar static const int LUMINOSITY_BAR_OFFSET = SLIDER_OFFSET + LUMINOSITY_BAR_WIDTH + 3; // cx - SPECTRUM_OFFSET = right side of spectrum static const int SPECTRUM_OFFSET = LUMINOSITY_BAR_OFFSET + 6; //============================================================================= static double HuetoRGB(double m1, double m2, double h) //============================================================================= { if (h < 0) h += 1.0; if (h > 1) h -= 1.0; if (6.0 * h < 1 ) return (m1 + (m2 - m1) * h * 6.0); if (2.0 * h < 1 ) return m2; if (3.0 * h < 2.0 ) return (m1 + (m2 - m1) * ((2.0 / 3.0) - h) * 6.0); return m1; } //============================================================================= void RGBtoHSL(COLORREF cr, double *H, double *S, double *L) //============================================================================= { double delta; double r = (double) GetRValue(cr) / 255; double g = (double) GetGValue(cr) / 255; double b = (double) GetBValue(cr) / 255; double cmax = max(r, max(g,b)); double cmin = min(r, min(g,b)); *L = (cmax + cmin) / 2.0; if (cmax == cmin) { *S = 0; *H = 0; // it's really undefined } else { if (*L < 0.5) *S = (cmax - cmin) / (cmax + cmin); else *S = (cmax - cmin) / (2.0 - cmax - cmin); delta = cmax - cmin; if (r == cmax) *H = (g - b) / delta; else if (g == cmax) *H = 2.0 + (b - r) / delta; else *H = 4.0 + (r - g) / delta; *H /= 6.0; if (*H < 0.0) *H += 1; } *H *= 240.0; *S *= 240.0; *L *= 240.0; } //============================================================================= void RGBtoHSL(COLORREF cr, BYTE *h, BYTE *s, BYTE *l) //============================================================================= { double H, S, L; RGBtoHSL(cr, &H, &S, &L); *h = (BYTE) (H + 0.5); *s = (BYTE) (S + 0.5); *l = (BYTE) (L + 0.5); if (*h > 239) *h = 239; if (*s > 240) *s = 240; if (*l > 240) *l = 240; } //============================================================================= COLORREF HSLtoRGB(double H, double S, double L) //============================================================================= { double r, g, b; double m1, m2; H /= 240.0; S /= 240.0; L /= 240.0; if (S == 0) { r = g = b = L; } else { if (L <= 0.5) m2 = L * (1.0 + S); else m2 = L + S - L * S; m1 = 2.0 * L - m2; r = HuetoRGB(m1, m2, H + 1.0/3.0); g = HuetoRGB(m1, m2, H); b = HuetoRGB(m1, m2, H - 1.0/3.0); } return RGB((BYTE)(r*255), (BYTE)(g*255), (BYTE)(b*255)); } //============================================================================= // // CXColorSpectrumCtrl() // // Purpose: Construct CXColorSpectrumCtrl object. // // Parameters: None // // Returns: None // // Notes: Construction is a two-step process. First, construct the // CXColorSpectrumCtrl object. Second, call CXColorSpectrumCtrl::Create() // to create the CXColorSpectrumCtrl window. // CXColorSpectrumCtrl::CXColorSpectrumCtrl() : m_hWnd(0), m_OldBitmap(NULL), m_nLuminosity(0), m_Hue(0), m_Sat(0), m_Lum(0), m_crLastSent(NO_COLOR), m_crCurrent(NO_COLOR), m_crBackground(NO_COLOR), m_bSliderDrag(FALSE), m_bCrosshairDrag(FALSE), m_nDlgCode(DLGC_WANTARROWS|DLGC_WANTTAB), m_bIsSpectrumFocused(FALSE), m_bIsSliderFocused(FALSE) { TRACE(_T("in CXColorSpectrumCtrl::CXColorSpectrumCtrl\n")); m_ptCurrent.x = 0; m_ptCurrent.y = VERTICAL_MARGIN; } //============================================================================= // // ~CXColorSpectrumCtrl() // // Purpose: Destroy CXColorSpectrumCtrl object. // // Parameters: None // // Returns: None // CXColorSpectrumCtrl::~CXColorSpectrumCtrl() { DeleteAll(); } //============================================================================= // // DefWindowProcX() // // Purpose: Initial window proc to dispatch messages to // CXColorSpectrumCtrl::WindowProc(). This allows us to set up // the 'this' pointer to CXColorSpectrumCtrl instance. // // Parameters: Standard windows message parameters. // // Returns: LRESULT - The return value is the result of the message // processing and depends on the message. // static LRESULT __stdcall DefWindowProcX(HWND hWnd, // handle to window UINT message, // message identifier WPARAM wParam, // first message parameter LPARAM lParam) // second message parameter { switch (message) { case WM_CREATE: { // save 'this' pointer in windows extra memory - the lParam // is set when ::CreateWindowEx() is called CREATESTRUCT* pcs = (CREATESTRUCT *) lParam; if (!pcs) { TRACE(_T("ERROR - CREATESTRUCT lParam is zero\n")); _ASSERTE(pcs); return -1; // abort creation } ::SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)pcs->lpCreateParams); return 0; } break; default: { // dispatch via saved 'this' pointer LONG_PTR lData = ::GetWindowLongPtr(hWnd, GWLP_USERDATA); if (lData) { CXColorSpectrumCtrl *pCtrl = (CXColorSpectrumCtrl *) lData; return pCtrl->WindowProc(message, wParam, lParam); } else { // probably some WM_NCxxxx message TRACE(_T("GWLP_USERDATA = 0 for message = 0x%04X\n"), message); } } break; } return ::DefWindowProc(hWnd, message, wParam, lParam); } //============================================================================= // // Create() // // Purpose: This virtual function creates the CXColorSpectrumCtrl window. // // Parameters: hInstance - handle to the instance that contains // the window procedure // dwStyle - specifies the window style attributes // rect - the size and position of the window // hParent - the parent window HWND // nID - the ID of the child window // crInitialColor - initial color selection // // Returns: BOOL - TRUE = window created successfully // BOOL CXColorSpectrumCtrl::Create(HINSTANCE hInstance, DWORD dwStyle, const RECT& rect, HWND hParent, UINT nID, COLORREF crInitialColor /*= RGB(0,0,0)*/) { TRACE(_T("in CXColorSpectrumCtrl::Create\n")); m_hWnd = 0; m_hParent = hParent; _ASSERTE(IsWindow(m_hParent)); if (!IsWindow(m_hParent)) return FALSE; m_rectCtrl = rect; TRACERECT(m_rectCtrl); TRACE(_T("width=%d height=%d\n"), m_rectCtrl.Width(), m_rectCtrl.Height()); m_rectSlider = m_rectCtrl; TRACERECT(m_rectSlider); TCHAR * pszClassName = _T("XColorSpectrumCtrl"); WNDCLASS wc = { CS_DBLCLKS, // class style - want WM_LBUTTONDBLCLK messages DefWindowProcX, // window proc 0, // class extra bytes 0, // window extra bytes hInstance, // instance handle 0, // icon ::LoadCursor(0, IDC_ARROW), // cursor 0, // background brush 0, // menu name pszClassName // class name }; if (!::RegisterClass(&wc)) { DWORD dwLastError = GetLastError(); if (dwLastError != ERROR_CLASS_ALREADY_EXISTS) { TRACE(_T("ERROR - RegisterClass failed, GetLastError() returned %u\n"), dwLastError); _ASSERTE(FALSE); return FALSE; } } // we pass 'this' pointer as lpParam, so DefWindowProcX will see it // in WM_CREATE message m_hWnd = ::CreateWindowEx(0, pszClassName, _T(""), dwStyle, m_rectCtrl.left, m_rectCtrl.top, m_rectCtrl.Width(), m_rectCtrl.Height(), hParent, (HMENU)nID, hInstance, this); if (m_hWnd == 0) { #ifdef _DEBUG DWORD dwLastError = GetLastError(); UNUSED(dwLastError); TRACE(_T("ERROR - CreateWindowEx failed, GetLastError() returned %u\n"), dwLastError); _ASSERTE(m_hWnd); #endif return FALSE; } // set up rect for spectrum ::GetClientRect(m_hWnd, &m_rectSpectrumClient); m_rectSliderClient = m_rectSpectrumClient; m_rectSpectrumClient.right -= SPECTRUM_OFFSET; m_rectSpectrumClient.top += VERTICAL_MARGIN; m_rectSpectrumClient.bottom -= VERTICAL_MARGIN; // set up rect for slider m_rectSliderClient.left = m_rectSliderClient.right - LUMINOSITY_BAR_OFFSET; m_rectSliderClient.right -= HORIZONTAL_MARGIN; m_rectSliderClient.top += VERTICAL_MARGIN - ARROW_HEIGHT/2; m_rectSliderClient.bottom -= VERTICAL_MARGIN - ARROW_HEIGHT/2; m_crLastSent = m_crCurrent = crInitialColor; RGBtoHSL(m_crLastSent, &m_Hue, &m_Sat, &m_Lum); TRACE(_T("m_crCurrent=%06X h=%d s=%d l=%d\n"), m_crCurrent, m_Hue, m_Sat, m_Lum); m_ptCurrent = GetPointFromHsl(); m_nLuminosity = GetLuminosity(); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, FALSE, FALSE); DrawArrow(NULL, m_nLuminosity, FALSE, FALSE); return m_hWnd != 0; } //============================================================================= // // GetRGB() // // Purpose: This function retrieves the current RGB value. // // Parameters: None // // Returns: COLORREF - the RGB value // COLORREF CXColorSpectrumCtrl::GetRGB() { TRACE(_T("GetRGB: %06X\n"), m_crCurrent); return m_crCurrent; } //============================================================================= // // SetRGB() // // Purpose: This function sets the RGB value. // // Parameters: cr - RGB value to set // // Returns: CXColorSpectrumCtrl& - reference to 'this' // CXColorSpectrumCtrl& CXColorSpectrumCtrl::SetRGB(COLORREF cr) { TRACE(_T("in CXColorSpectrumCtrl::SetRGB\n")); m_crLastSent = m_crCurrent = cr; RGBtoHSL(m_crCurrent, &m_Hue, &m_Sat, &m_Lum); m_ptCurrent = GetPointFromHsl(); m_nLuminosity = GetLuminosity(); //TRACE(_T("SetRGB: m_ptCurrent.y=%d m_Hue=%d m_Sat=%d m_Lum=%d =====\n"), m_ptCurrent.y, m_Hue, m_Sat, m_Lum); // update color locators, don't tell parent DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, IsFocused() && m_bIsSpectrumFocused, FALSE); DrawArrow(NULL, m_nLuminosity, IsFocused() && m_bIsSliderFocused, FALSE); return *this; } //============================================================================= // // GetHSL() // // Purpose: This function retrieves the current HSL values. // // Parameters: h - BYTE pointer to retrieved h value // s - BYTE pointer to retrieved s value // l - BYTE pointer to retrieved l value // // Returns: None // void CXColorSpectrumCtrl::GetHSL(BYTE* h, BYTE* s, BYTE* l) { *h = m_Hue; *s = m_Sat; *l = m_Lum; //TRACE(_T("GetHSL: m_ptCurrent.y=%d h=%d s=%d l=%d =====\n"), m_ptCurrent.y, *h, *s, *l); } //============================================================================= // // SetHslFromPoint() // // Purpose: This function sets the hue and sat values from point coordinates. // // Parameters: point - point for color coordinates // // Returns: None // void CXColorSpectrumCtrl::SetHslFromPoint(POINT point) { int y = point.y - VERTICAL_MARGIN; m_Hue = (BYTE)((point.x * 239) / (m_rectSpectrumClient.Width() - 1)); m_Sat = (BYTE)(((m_rectSpectrumClient.Height() - y - 1) * 240) / (m_rectSpectrumClient.Height() - 1)); if (m_Hue > 239) m_Hue = 239; if (m_Sat > 240) m_Sat = 240; //TRACE(_T("SetHslFromPoint: hue=%d sat=%d height=%d y = %d =====\n"), m_Hue, m_Sat, m_rectSpectrumClient.Height(), y); } //============================================================================= // // Internal_SetHSL() // // Purpose: Internal function to set HSL values // // Parameters: h - BYTE value of h value // s - BYTE value of s value // l - BYTE value of l value // // Returns: None // void CXColorSpectrumCtrl::Internal_SetHSL(BYTE h, BYTE s, BYTE l) { if (h > 239) h = 239; if (s > 240) s = 240; if (l > 240) l = 240; m_Hue = h; m_Sat = s; m_Lum = l; m_crCurrent = HSLtoRGB(h, s, l); //TRACE(_T("Internal_SetHSL: m_crCurrent=%06X\n"), m_crCurrent); m_ptCurrent = GetPointFromHsl(); m_nLuminosity = GetLuminosity(); } //============================================================================= // // SetHSL() // // Purpose: This function sets the HSL value. Called *only* by parent. // // Parameters: h - BYTE h value // s - BYTE s value // l - BYTE l value // // Returns: CXColorSpectrumCtrl& - reference to 'this' // CXColorSpectrumCtrl& CXColorSpectrumCtrl::SetHSL(BYTE h, BYTE s, BYTE l) { TRACE(_T("SetHSL: y=%d %d,%d,%d =====\n"), m_ptCurrent.y, h, s, l); Internal_SetHSL(h, s, l); m_crLastSent = m_crCurrent; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, m_bIsSpectrumFocused, FALSE); DrawArrow(NULL, m_nLuminosity, m_bIsSliderFocused, FALSE); //TRACE(_T("SetHSL: m_ptCurrent.x=%d m_ptCurrent.y=%d =====\n"), m_ptCurrent.x, m_ptCurrent.y); return *this; } //============================================================================= // // GoLeft() // // Purpose: This function processes left arrow keystrokes. // // Parameters: nDelta - number of pixels to move // // Returns: None // void CXColorSpectrumCtrl::GoLeft(int nDelta) { if (m_bIsSpectrumFocused) { int nHue = m_Hue - nDelta; if (nHue < 0) nHue = 0; if (nHue > 239) nHue = 239; Internal_SetHSL((BYTE)nHue, m_Sat, m_Lum); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, TRUE); } } //============================================================================= // // GoRight() // // Purpose: This function processes right arrow keystrokes. // // Parameters: nDelta - number of pixels to move // // Returns: None // void CXColorSpectrumCtrl::GoRight(int nDelta) { if (m_bIsSpectrumFocused) { int nHue = m_Hue + nDelta; if (nHue < 0) nHue = 0; if (nHue > 239) nHue = 239; Internal_SetHSL((BYTE)nHue, m_Sat, m_Lum); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, TRUE); } } //============================================================================= // // GoUp() // // Purpose: This function processes up arrow keystrokes. // // Parameters: nDelta - number of pixels to move // // Returns: None // void CXColorSpectrumCtrl::GoUp(int nDelta) { if (m_bIsSliderFocused) { int nLum = m_Lum + nDelta; if (nLum < 0) nLum = 0; if (nLum > 240) nLum = 240; Internal_SetHSL(m_Hue, m_Sat, (BYTE)nLum); DrawArrow(NULL, m_nLuminosity, TRUE, TRUE); } else { int nSat = m_Sat + nDelta; if (nSat < 0) nSat = 0; if (nSat > 240) nSat = 240; Internal_SetHSL(m_Hue, (BYTE)nSat, m_Lum); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, TRUE); } } //============================================================================= // // GoDown() // // Purpose: This function processes down arrow keystrokes. // // Parameters: nDelta - number of pixels to move // // Returns: None // void CXColorSpectrumCtrl::GoDown(int nDelta) { if (m_bIsSliderFocused) { int nLum = m_Lum - nDelta; if (nLum < 0) nLum = 0; if (nLum > 240) nLum = 240; Internal_SetHSL(m_Hue, m_Sat, (BYTE)nLum); DrawArrow(NULL, m_nLuminosity, TRUE, TRUE); } else { int nSat = m_Sat - nDelta; if (nSat < 0) nSat = 0; if (nSat > 240) nSat = 240; Internal_SetHSL(m_Hue, (BYTE)nSat, m_Lum); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, TRUE); } } //============================================================================= // // KeyDown() // // Purpose: This function processes WM_KEYDOWN messages. // // Parameters: wParam - virtual-key code // lParam - not used // // Returns: LRESULT - zero = message was processed // LRESULT CXColorSpectrumCtrl::KeyDown(WPARAM wParam, LPARAM /*lParam*/) { TRACE(_T("in CXColorSpectrumCtrl::KeyDown\n")); // for the arrow keys: // if the Ctrl key is down, advance by 1 // if the shift key is down, advance by twice the normal delta int nDelta = ARROW_DELTA; // standard advance for arrow keys if (IsCtrlDown()) // Ctrl key: advance by 1 nDelta = 1; else if (IsShiftDown()) // Shift key: advance by 2x nDelta *= 2; if (wParam == VK_TAB) { // ignore tab if we don't have focus if (::GetFocus() != m_hWnd) return 1; if (IsShiftDown()) { // shift is down, tab backwards if (m_bIsSliderFocused) { // slider has focus, switch to spectrum m_bIsSpectrumFocused = TRUE; m_bIsSliderFocused = FALSE; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, FALSE); DrawArrow(NULL, m_nLuminosity, FALSE, FALSE); return 0; } else { // spectrum has focus, give focus to previous control ::PostMessage(m_hParent, WM_NEXTDLGCTL, 1, 0); } } else { // shift is up, tab forwards if (m_bIsSliderFocused) { // slider has focus, give focus to next control ::PostMessage(m_hParent, WM_NEXTDLGCTL, 0, 0); return 0; } else { // spectrum has focus, switch to slider m_bIsSpectrumFocused = FALSE; m_bIsSliderFocused = TRUE; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, FALSE, FALSE); DrawArrow(NULL, m_nLuminosity, TRUE, FALSE); return 0; } } } else if (wParam == VK_END) { if (m_bIsSliderFocused) GoDown(20000); // position to bottom else GoRight(20000); // position to right } else if (wParam == VK_HOME) { if (m_bIsSliderFocused) GoUp(20000); // position to top else GoLeft(20000); // position to left } else if (wParam == VK_NEXT) { GoDown(20000); // position to bottom } else if (wParam == VK_PRIOR) { GoUp(20000); // position to top } else if (wParam == VK_DOWN) { GoDown(nDelta); } else if (wParam == VK_UP) { GoUp(nDelta); } else if (wParam == VK_RIGHT) { GoRight(nDelta); } else if (wParam == VK_LEFT) { GoLeft(nDelta); } return 1; // let default processing continue } //============================================================================= // // IsShiftDown() // // Purpose: This function returns TRUE if either shift key is down. // // Parameters: None // // Returns: BOOL - TRUE = shift key is down // BOOL CXColorSpectrumCtrl::IsShiftDown() { return GetAsyncKeyState(VK_SHIFT) & 0x8000; } //============================================================================= // // IsCtrlDown() // // Purpose: This function returns TRUE if either Ctrl key is down. // // Parameters: None // // Returns: BOOL - TRUE = Ctrl key is down // BOOL CXColorSpectrumCtrl::IsCtrlDown() { return GetAsyncKeyState(VK_CONTROL) & 0x8000; } //============================================================================= // // IsLeftButtonDown() // // Purpose: This function returns TRUE if left mouse button is down. // // Parameters: None // // Returns: BOOL - TRUE = left mouse button is down // BOOL CXColorSpectrumCtrl::IsLeftButtonDown() { BOOL rc = FALSE; SHORT state = 0; if (GetSystemMetrics(SM_SWAPBUTTON)) // check if buttons have been swapped state = GetAsyncKeyState(VK_RBUTTON); // buttons swapped, get right button state else state = GetAsyncKeyState(VK_LBUTTON); // if the most significant bit is set, the button is down if (state < 0) rc = TRUE; return rc; } //============================================================================= // // IsFocused() // // Purpose: This function returns TRUE if control has focus. // // Parameters: None // // Returns: BOOL - TRUE = control has focus // BOOL CXColorSpectrumCtrl::IsFocused() { BOOL rc = FALSE; HWND hFocus = ::GetFocus(); if (hFocus == m_hWnd) rc = TRUE; return rc; } //============================================================================= // // GetClientCursorPos() // // Purpose: This function retrieves the cursor location relative to the // control's client rect. // // Parameters: point - reference to the variable that receives the location // // Returns: BOOL - TRUE = location is valid // BOOL CXColorSpectrumCtrl::GetClientCursorPos(POINT& point) { BOOL rc = FALSE; if (::GetCursorPos(&point)) // check if screensaver kicked in { ::ScreenToClient(m_hWnd, &point); rc = TRUE; } return rc; } //============================================================================= // // IsPointInSpectrum() // // Purpose: This function determines if point is in spectrum. // // Parameters: point = location to check // // Returns: BOOL - TRUE = point is in spectrum // BOOL CXColorSpectrumCtrl::IsPointInSpectrum(POINT point) { BOOL rc = m_rectSpectrumClient.PtInRect(point); return rc; } //============================================================================= // // IsPointInSlider() // // Purpose: This function determines if point is in slider. // // Parameters: point = location to check // // Returns: BOOL - TRUE = point is in slider // BOOL CXColorSpectrumCtrl::IsPointInSlider(POINT point) { BOOL rc = m_rectSliderClient.PtInRect(point); return rc; } //============================================================================= // // DeleteAll() // // Purpose: This function deletes the saved bitmap // // Parameters: None // // Returns: None // void CXColorSpectrumCtrl::DeleteAll() { if (m_OldBitmap) m_dcSpectrum.SelectObject(m_OldBitmap); m_OldBitmap = NULL; if (m_bmpSpectrum) ::DeleteObject(m_bmpSpectrum); } //============================================================================= // // SetLuminosity() // // Purpose: This function sets the luminosity value for the specified // luminosity slider position. // // Parameters: nLuminosity - luminosity slider position // // Returns: None // void CXColorSpectrumCtrl::SetLuminosity(int nLuminosity) { if (nLuminosity < 0) nLuminosity = 0; if (nLuminosity > (m_rectSpectrumClient.Height() -1)) nLuminosity = m_rectSpectrumClient.Height() -1; m_nLuminosity = nLuminosity; m_Lum = (BYTE)(((m_rectSpectrumClient.Height() - m_nLuminosity - 1) * 240) / (m_rectSpectrumClient.Height() - 1)); if (m_Lum > 240) m_Lum = 240; m_crCurrent = HSLtoRGB(m_Hue, m_Sat, m_Lum); } //============================================================================= // // GetLuminosity() // // Purpose: This function retrieves the luminosity value for the // specified RGB value. // // Parameters: cr - RGB color value // // Returns: int - luminosity valie // int CXColorSpectrumCtrl::GetLuminosity() { UINT l = m_Lum; int lum = m_rectSpectrumClient.Height() - ((m_rectSpectrumClient.Height() * l) / 240); if (lum < 0) lum = 0; if (lum > m_rectSpectrumClient.Height() - 1) lum = m_rectSpectrumClient.Height() - 1; return lum; } //============================================================================= // // GetPointFromHsl() // // Purpose: This function retrieves the POINT location (in spectrum) for // the specified RGB value. // // Parameters: cr - RGB color value // // Returns: POINT - location of color // POINT CXColorSpectrumCtrl::GetPointFromHsl() { POINT point = { 0 }; UINT hue = m_Hue; UINT sat = m_Sat; point.x = (hue * m_rectSpectrumClient.Width()) / 239; if (point.x > (m_rectSpectrumClient.right-1)) point.x = m_rectSpectrumClient.right-1; point.y = m_rectSpectrumClient.Height() - ((sat * m_rectSpectrumClient.Height()) / 240); point.y += VERTICAL_MARGIN; if (point.y > (m_rectSpectrumClient.bottom-1)) point.y = m_rectSpectrumClient.bottom-1; return point; } //============================================================================= // // DrawSpectrum() // // Purpose: This function draws the spectrum and initializes the // spectrum bitmap and memory dc. // // Parameters: pDC - pointer to device context used for drawing // // Returns: None // void CXColorSpectrumCtrl::DrawSpectrum(CXDC *pDC) { TRACE(_T("in CXColorSpectrumCtrl::DrawSpectrum\n")); _ASSERTE(pDC); if (!pDC) return; TRACERECT(m_rectCtrl); if (m_OldBitmap == NULL) { // spectrum bitmap hasn't been drawn yet if (!m_dcSpectrum.CreateCompatibleDC(pDC)) { TRACE(_T("ERROR - CreateCompatibleDC failed\n")); _ASSERTE(FALSE); return; } m_bmpSpectrum = pDC->CreateCompatibleBitmap(m_rectCtrl.Width(), m_rectCtrl.Height()); m_OldBitmap = (HBITMAP) m_dcSpectrum.SelectObject(m_bmpSpectrum); if (m_crBackground == NO_COLOR) { // no background color specified, just copy current background m_dcSpectrum.BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(), pDC, 0, 0, SRCCOPY); } else { m_dcSpectrum.FillSolidRect(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(), m_crBackground); } UINT cx = m_rectCtrl.Width() - SPECTRUM_OFFSET; UINT cy = m_rectCtrl.Height() - 2*VERTICAL_MARGIN; // The saturation and luminosity values must be in the range 0 // through 240, and the hue value must be in the range 0 through 239. double cxd = cx; double cyd = cy; for (UINT y = 0; y < cy; y++) { for (UINT x = 0; x < cx; x++) { double hue = x; hue *= 240.0; hue /= cxd - 1.0; double sat = y; sat *= 240.0; sat /= cyd - 1.0; COLORREF cr = HSLtoRGB(hue, sat, 120.0); m_dcSpectrum.SetPixelV(x, cy - y - 1 + VERTICAL_MARGIN, cr); } } } // write spectrum bitmap to control's dc pDC->BitBlt(0, 0, m_rectCtrl.Width(), m_rectCtrl.Height(), &m_dcSpectrum, 0, 0, SRCCOPY); } //============================================================================= // // SendColorToParent() // // Purpose: This function sends the specified color to the parent. // // Parameters: nMessage - message ID // cr - color to send // // Returns: None // void CXColorSpectrumCtrl::SendColorToParent(UINT nMessage, COLORREF cr) { TRACE(_T("in CXColorSpectrumCtrl::SendColorToParent: cr=%06X m_crLastSent=%06X\n"), cr, m_crLastSent); // don't send duplicate messages //if ((nMessage == WM_XCOLORPICKER_SELENDOK) || (cr != m_crLastSent)) { m_crLastSent = cr; if (IsWindow(m_hParent) && (cr != NO_COLOR)) { TRACE(_T(">>>>> sending %06X\n"), m_crLastSent); ::SendMessage(m_hParent, nMessage, m_crLastSent, ::GetDlgCtrlID(m_hWnd)); } } } //============================================================================= // // DrawCrosshair() // // Purpose: This function draws spectrum crosshair. // // Parameters: pDC - pointer to device context to use for drawing // startx - x coord of center point // starty - y coord of center point // bHasFocus - TRUE = control has focus; this determines how the // crosshair is drawn // bSendColor - TRUE = send WM_XCOLORPICKER_SELCHANGE to parent // // Returns: None // void CXColorSpectrumCtrl::DrawCrosshair(CXDC *pDC, int startx, int starty, BOOL bHasFocus, BOOL bSendColor) { TRACE(_T("in CXColorSpectrumCtrl::DrawCrosshair\n")); BOOL bRelease = FALSE; if (pDC == NULL) { pDC = new CXDC(m_hWnd); bRelease = TRUE; } // restore bitmap - get rid of previous selection if (m_OldBitmap) pDC->BitBlt(0, 0, m_rectCtrl.Width()-SPECTRUM_OFFSET, m_rectCtrl.Height(), &m_dcSpectrum, 0, 0, SRCCOPY); // for each byte in this array: // 0 = skip // 1 = COLOR_WINDOWTEXT (COLOR_WINDOW if bHasFocus == FALSE) static BYTE pixels[CROSSHAIR_SIZE][CROSSHAIR_SIZE] = { 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 1 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 2 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 3 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 4 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 5 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 6 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 7 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1, // 9 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1, // 10 1,1,1,1,1,0,0,0,0,0,0,0,0,0,1,1,1,1,1, // 11 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 12 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 13 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 14 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 15 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 16 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 17 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0, // 18 0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0 // 19 }; COLORREF crFill = GetSysColor(COLOR_WINDOWTEXT); if (!bHasFocus) crFill = GetSysColor(COLOR_WINDOW); POINT pt; for (int row = 0; row < CROSSHAIR_SIZE; row++) { for (int col = 0; col < CROSSHAIR_SIZE; col++) { if (pixels[row][col] != 1) continue; pt.x = startx - CROSSHAIR_SIZE/2 + col; pt.y = starty - CROSSHAIR_SIZE/2 + row; if (m_rectSpectrumClient.PtInRect(pt)) { pDC->SetPixelV(pt.x, pt.y, crFill); } } } DrawLuminosityBar(pDC); if (bRelease) delete pDC; // let parent know color selection has changed if (bSendColor) SendColorToParent(WM_XCOLORPICKER_SELCHANGE, m_crCurrent); } //============================================================================= // // DrawLuminosityBar() // // Purpose: This function draws luminosity bar based on the color under // the crosshair. // // Parameters: pDC - pointer to device context used for drawing // // Returns: None // void CXColorSpectrumCtrl::DrawLuminosityBar(CXDC *pDC) { TRACE(_T("in CXColorSpectrumCtrl::DrawLuminosityBar\n")); BOOL bRelease = FALSE; if (pDC == NULL) { pDC = new CXDC(m_hWnd); _ASSERTE(pDC); bRelease = TRUE; } // set up for double buffering CXDC memDC; if (!memDC.CreateCompatibleDC(pDC)) { TRACE(_T("ERROR - CreateCompatibleDC failed\n")); _ASSERTE(FALSE); return; } HBITMAP bmp = pDC->CreateCompatibleBitmap(m_rectSlider.Width(), m_rectSlider.Height()); HBITMAP hOldBitmap = (HBITMAP) memDC.SelectObject(bmp); memDC.BitBlt(0, 0, m_rectSlider.Width(), m_rectSlider.Height(), pDC, 0, 0, SRCCOPY); COLORREF cr = m_crCurrent;//GetColorUnderCrosshair(); //RGB(255,255,0); if (cr == NO_COLOR) return; DOUBLE h, s, l; RGBtoHSL(cr, &h, &s, &l); //if (h > 239.0) // h = 239.0; if (h > 240.0) h = 240.0; if (s > 240.0) s = 240.0; UINT cx = m_rectSlider.Width(); UINT cy = m_rectSlider.Height() - 2*VERTICAL_MARGIN; double cyd = cy; for (UINT y = 0; y < cy; y++) { l = y; l /= cyd - 1.0; l *= 240.0; COLORREF rgb = HSLtoRGB(h, s, l); for (UINT x = cx-LUMINOSITY_BAR_OFFSET; x < cx-LUMINOSITY_BAR_OFFSET+LUMINOSITY_BAR_WIDTH; x++) memDC.SetPixelV(x, cy - y - 1 + VERTICAL_MARGIN, rgb); } // end double buffering pDC->BitBlt(0, 0, m_rectSlider.Width(), m_rectSlider.Height(), &memDC, 0, 0, SRCCOPY); // swap back the original bitmap if (hOldBitmap) memDC.SelectObject(hOldBitmap); if (bmp) ::DeleteObject(bmp); if (bRelease) delete pDC; } //============================================================================= // // DrawArrow() // // Purpose: This function draws slider arrow. // // Parameters: pDC - pointer to device context to use for drawing // starty - y coord of arror tip // bHasFocus - TRUE = control has focus; this determines how the // arrow is drawn // bSendColor - TRUE = send WM_XCOLORPICKER_SELCHANGE to parent // // Returns: None // void CXColorSpectrumCtrl::DrawArrow(CXDC *pDC, int starty, BOOL bHasFocus, BOOL bSendColor) { BOOL bRelease = FALSE; if (pDC == NULL) { pDC = new CXDC(m_hWnd); bRelease = TRUE; } // restore bitmap - get rid of previous selection if (m_OldBitmap) pDC->BitBlt(m_rectCtrl.Width()-SLIDER_OFFSET, 0, SLIDER_OFFSET, m_rectCtrl.Height(), &m_dcSpectrum, m_rectCtrl.Width()-SLIDER_OFFSET, 0, SRCCOPY); // for each byte in this array: // 0 = skip // 1 = COLOR_WINDOWTEXT // 2 = COLOR_WINDOWTEXT (COLOR_BTNSHADOW if bHasFocus == FALSE) static BYTE pixels[ARROW_HEIGHT][ARROW_WIDTH] = { 0,0,0,0,0,0,0,0,1,0, // 1 0,0,0,0,0,0,0,1,1,0, // 2 0,0,0,0,0,0,1,2,1,0, // 3 0,0,0,0,0,1,2,2,1,0, // 4 0,0,0,0,1,2,2,2,1,0, // 5 0,0,0,1,2,2,2,2,1,0, // 6 0,0,1,2,2,2,2,2,1,0, // 7 0,1,2,2,2,2,2,2,1,0, // 8 1,2,2,2,2,2,2,2,1,0, // 9 0,1,2,2,2,2,2,2,1,0, // 10 0,0,1,2,2,2,2,2,1,0, // 11 0,0,0,1,2,2,2,2,1,0, // 12 0,0,0,0,1,2,2,2,1,0, // 13 0,0,0,0,0,1,2,2,1,0, // 14 0,0,0,0,0,0,1,2,1,0, // 15 0,0,0,0,0,0,0,1,1,0, // 16 0,0,0,0,0,0,0,0,1,0 // 17 }; COLORREF crWindowText = GetSysColor(COLOR_WINDOWTEXT); COLORREF crBtnShadow = GetSysColor(COLOR_BTNSHADOW); COLORREF crFill = crWindowText; if (!bHasFocus) crFill = crBtnShadow; int startx = m_rectCtrl.Width() - SLIDER_OFFSET; if (starty < 0) starty = 0; if (starty > m_rectSliderClient.Height() - 2*(ARROW_HEIGHT/2) - 1) starty = m_rectSliderClient.Height() - 2*(ARROW_HEIGHT/2) - 1; for (int row = 0; row < ARROW_HEIGHT; row++) { for (int col = 0; col < ARROW_WIDTH; col++) { COLORREF crFill = crWindowText; if (pixels[row][col] == 1) crFill = crWindowText; else if (pixels[row][col] == 2) if (bHasFocus) crFill = crWindowText; else crFill = crBtnShadow; else continue; pDC->SetPixelV(startx + col, starty + row - ARROW_HEIGHT/2 + VERTICAL_MARGIN, crFill); } } if (bRelease) delete pDC; // let parent know color selection has changed if (bSendColor) SendColorToParent(WM_XCOLORPICKER_SELCHANGE, m_crCurrent); } //============================================================================= // Helper functions extracted from mfc\src\strcore.cpp #ifndef _UNICODE static int _mbstowcsz(wchar_t* wcstr, const char* mbstr, size_t count) { if (count == 0 && wcstr != NULL) return 0; int result = ::MultiByteToWideChar(CP_ACP, 0, mbstr, -1, wcstr, count); _ASSERTE(wcstr == NULL || result <= (int)count); if (result > 0) wcstr[result-1] = 0; return result; } #else static int _wcstombsz(char* mbstr, const wchar_t* wcstr, size_t count) { if (count == 0 && mbstr != NULL) return 0; int result = ::WideCharToMultiByte(CP_ACP, 0, wcstr, -1, mbstr, count, NULL, NULL); _ASSERTE(mbstr == NULL || result <= (int)count); if (result > 0) mbstr[result-1] = 0; return result; } #endif //_UNICODE //============================================================================= // // WindowProc() // // Purpose: This function is the window proc for CXColorSpectrumCtrl object. // Messages are forwarded to this function from DefWindowProcX(). // // Parameters: message - message identifier // wParam - first message parameter // lParam - second message parameter // // Returns: LRESULT - The return value is the result of the message // processing and depends on the message. // LRESULT CXColorSpectrumCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // will never see this message - it's handled by DefWindowProcX break; case WM_ERASEBKGND: // nothing to erase, since we draw the entire client area return TRUE; case WM_PAINT: { TRACE(_T("in WM_PAINT\n")); PAINTSTRUCT ps; HDC hdc = (wParam != NULL) ? (HDC) wParam : ::BeginPaint(m_hWnd, &ps); if (hdc == 0) return 0; CXDC dc(hdc); DrawSpectrum(&dc); DrawCrosshair(&dc, m_ptCurrent.x, m_ptCurrent.y, IsFocused() && m_bIsSpectrumFocused, FALSE); DrawArrow(&dc, m_nLuminosity, IsFocused() && m_bIsSliderFocused, FALSE); if (wParam == NULL) ::EndPaint(m_hWnd, &ps); return 0; } case WM_KEYDOWN: if (!KeyDown(wParam, lParam)) return 0; //::SendMessage(m_hParent, message, wParam, lParam); break; case WM_GETDLGCODE: return m_nDlgCode; break; case WM_LBUTTONDOWN: { POINT point; point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); TRACE(_T("in WM_LBUTTONDOWN: point.x=%d point.y=%d\n"), point.x, point.y); if (IsPointInSpectrum(point)) { SetHslFromPoint(point); //TRACE(_T("m_Hue=%d m_Sat=%d m_Lum=%d =====\n"), m_Hue, m_Sat, m_Lum); m_crCurrent = HSLtoRGB(m_Hue, m_Sat, m_Lum); //TRACE(_T("m_crCurrent=%06X\n"), m_crCurrent); m_ptCurrent = point; } else if (IsPointInSlider(point)) { SetLuminosity(point.y - VERTICAL_MARGIN); m_crCurrent = HSLtoRGB(m_Hue, m_Sat, m_Lum); } HWND hFocus = ::GetFocus(); if (hFocus != m_hWnd) { ::SetFocus(m_hWnd); // WM_SETFOCUS draws crosshair } if (IsPointInSpectrum(point)) { m_bIsSpectrumFocused = TRUE; m_bIsSliderFocused = FALSE; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, TRUE, TRUE); DrawArrow(NULL, m_nLuminosity, FALSE, FALSE); m_bCrosshairDrag = TRUE; } else if (IsPointInSlider(point)) { m_bIsSpectrumFocused = FALSE; m_bIsSliderFocused = TRUE; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, FALSE, FALSE); DrawArrow(NULL, m_nLuminosity, TRUE, TRUE); m_bSliderDrag = TRUE; } TRACE(_T("WM_LBUTTONDOWN: m_crCurrent=%06X\n"), m_crCurrent); break; } case WM_LBUTTONUP: TRACE(_T("in WM_LBUTTONUP\n")); m_bSliderDrag = FALSE; m_bCrosshairDrag = FALSE; break; case WM_LBUTTONDBLCLK: { TRACE(_T("in CXColorSpectrumCtrl::OnLButtonDblClk\n")); POINT point; point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); // let parent know a color was selected if (IsPointInSlider(point) || IsPointInSpectrum(point)) SendColorToParent(WM_XCOLORPICKER_SELENDOK, m_crCurrent); break; } case WM_SETFOCUS: { TRACE(_T("in WM_SETFOCUS\n")); LRESULT lResult = ::DefWindowProc(m_hWnd, message, wParam, lParam); if (IsShiftDown()) m_bIsSliderFocused = TRUE; else m_bIsSpectrumFocused = TRUE; DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, IsFocused() && m_bIsSpectrumFocused, FALSE); DrawArrow(NULL, m_nLuminosity, IsFocused() && m_bIsSliderFocused, FALSE); return lResult; } case WM_KILLFOCUS: { TRACE(_T("in WM_KILLFOCUS\n")); LRESULT lResult = ::DefWindowProc(m_hWnd, message, wParam, lParam); DrawCrosshair(NULL, m_ptCurrent.x, m_ptCurrent.y, FALSE, FALSE); DrawArrow(NULL, m_nLuminosity, FALSE, FALSE); m_bIsSpectrumFocused = FALSE; m_bIsSliderFocused = FALSE; return lResult; } case WM_MOUSEMOVE: { POINT point; point.x = GET_X_LPARAM(lParam); point.y = GET_Y_LPARAM(lParam); if (!IsLeftButtonDown()) { m_bSliderDrag = FALSE; m_bCrosshairDrag = FALSE; } if (m_bSliderDrag) { if (IsPointInSlider(point)) { SetLuminosity(point.y - VERTICAL_MARGIN); DrawArrow(NULL, m_nLuminosity, TRUE, TRUE); } } else if (m_bCrosshairDrag) { if (IsPointInSpectrum(point)) { m_ptCurrent = point; SetHslFromPoint(m_ptCurrent); m_crCurrent = HSLtoRGB(m_Hue, m_Sat, m_Lum); DrawCrosshair(NULL, point.x, point.y, TRUE, TRUE); } } break; } case WM_NOTIFY: { break; } } return ::DefWindowProc(m_hWnd, message, wParam, lParam); }