/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ ///////////////////////////////////////////////////////////////////////////////// // // ColorPicker.cpp : implementation file // // #include "stdafx.h" #include "colorpicker.h" #include "colorpickerdialogclass.h" #include #include "utils.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //extern HINSTANCE _hinstance; ///////////////////////////////////////////////////////////////////////////// // // ColorPickerClass // ColorPickerClass::ColorPickerClass (void) : m_hBitmap (NULL), m_iWidth (0), m_iHeight (0), m_CurrentPoint (0, 0), m_CurrentColor (0), m_bSelecting (false), m_pBits (NULL), m_hMemDC (NULL), m_CurrentHue (0), CWnd () { return ; } ///////////////////////////////////////////////////////////////////////////// // // ~ColorPickerClass // ColorPickerClass::~ColorPickerClass (void) { if (m_hMemDC != NULL) { ::DeleteObject (m_hMemDC); m_hMemDC = NULL; } Free_Bitmap (); return ; } BEGIN_MESSAGE_MAP(ColorPickerClass, CWnd) //{{AFX_MSG_MAP(ColorPickerClass) ON_WM_PAINT() ON_WM_CREATE() ON_WM_SIZE() ON_WM_ERASEBKGND() ON_WM_LBUTTONDOWN() ON_WM_LBUTTONUP() ON_WM_MOUSEMOVE() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // // OnPaint // void ColorPickerClass::OnPaint (void) { CPaintDC dc (this); if (m_hMemDC != NULL) { HBITMAP hold_bmp = (HBITMAP)::SelectObject (m_hMemDC, m_hBitmap); CRect rect; GetClientRect (&rect); ::BitBlt (dc, 0, 0, rect.Width (), rect.Height (), m_hMemDC, 0, 0, SRCCOPY); ::SelectObject (m_hMemDC, hold_bmp); Paint_Marker (); } return ; } ///////////////////////////////////////////////////////////////////////////// // // RegisterColorPicker // void RegisterColorPicker (HINSTANCE hinst) { // Has the class already been registered? WNDCLASS wndclass = { 0 }; if (::GetClassInfo (hinst, "WWCOLORPICKER", &wndclass) == FALSE) { wndclass.style = CS_GLOBALCLASS | CS_OWNDC | CS_VREDRAW | CS_HREDRAW; wndclass.lpfnWndProc = fnColorPickerProc; wndclass.hInstance = hinst; wndclass.hbrBackground = (HBRUSH)COLOR_3DFACE + 1; wndclass.hCursor = ::LoadCursor (NULL, IDC_ARROW); wndclass.lpszClassName = "WWCOLORPICKER"; // Let the windows manager know about this global class ::RegisterClass (&wndclass); } return ; } ///////////////////////////////////////////////////////////////////////////// // // fnColorPickerProc // LRESULT WINAPI fnColorPickerProc ( HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam ) { //static ColorPickerClass *pwnd = NULL; //static bool bcreated = false; switch (message) { case WM_CREATE: { LPCREATESTRUCT pcreate_info = (LPCREATESTRUCT)lparam; if (pcreate_info != NULL) { // Should we create a new class manager for this window? ColorPickerClass *pwnd = (ColorPickerClass *)pcreate_info->lpCreateParams; BOOL created = FALSE; if (pwnd == NULL) { pwnd = new ColorPickerClass; created = TRUE; } // Pull some hacks to get MFC to use the message map pwnd->Attach (hwnd); // Let the window know its being created pwnd->OnCreate (pcreate_info); WNDPROC *pOldWndProc = pwnd->GetSuperWndProcAddr (); if (pOldWndProc) { WNDPROC pold_proc = (WNDPROC)::SetWindowLong (hwnd, GWL_WNDPROC, (DWORD)::AfxGetAfxWndProc ()); ASSERT (pold_proc != NULL); (*pOldWndProc) = pold_proc; } // Store some information in the window handle ::SetProp (hwnd, "CLASSPOINTER", (HANDLE)pwnd); ::SetProp (hwnd, "CREATED", (HANDLE)created); } } break; case WM_DESTROY: { /*pwnd->Detach (); WNDPROC *pOldWndProc = pwnd->GetSuperWndProcAddr (); if (pOldWndProc) { ::SetWindowLong (hwnd, GWL_WNDPROC, (DWORD)(*pOldWndProc)); (*pOldWndProc) = NULL; } if (bcreated) { delete pwnd; pwnd = NULL; }*/ // Get the creation information from the window handle ColorPickerClass *pwnd = (ColorPickerClass *)::GetProp (hwnd, "CLASSPOINTER"); BOOL created = (BOOL)::GetProp (hwnd, "CREATED"); if (pwnd != NULL) { pwnd->Detach (); WNDPROC *pOldWndProc = pwnd->GetSuperWndProcAddr (); if (pOldWndProc) { ::SetWindowLong (hwnd, GWL_WNDPROC, (DWORD)(*pOldWndProc)); (*pOldWndProc) = NULL; } if (created) { delete pwnd; pwnd = NULL; } } } break; } // Allow default processing to occur return ::DefWindowProc (hwnd, message, wparam, lparam); } ///////////////////////////////////////////////////////////////////////////// // // OnCreate // ///////////////////////////////////////////////////////////////////////////// int ColorPickerClass::OnCreate (LPCREATESTRUCT lpCreateStruct) { if (CWnd::OnCreate(lpCreateStruct) == -1) return -1; m_hMemDC = ::CreateCompatibleDC (NULL); Create_Bitmap (); return 0; } ///////////////////////////////////////////////////////////////////////////// // // Create // ///////////////////////////////////////////////////////////////////////////// BOOL ColorPickerClass::Create ( LPCTSTR /*lpszClassName*/, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT &rect, CWnd *pparent_wnd, UINT nID, CCreateContext * /*pContext*/ ) { // Create the window (it will force the message map and everthing) HWND hparent_wnd = (pparent_wnd != NULL) ? pparent_wnd->m_hWnd : NULL; HWND hwnd = ::CreateWindow ("WWCOLORPICKER", lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, hparent_wnd, (HMENU)nID, ::AfxGetInstanceHandle (), this); // Return the true/false result code return (hwnd != NULL); } ///////////////////////////////////////////////////////////////////////////// // // Create_Bitmap // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Create_Bitmap (void) { // Start fresh Free_Bitmap (); CRect rect; GetClientRect (&rect); m_iWidth = rect.Width (); m_iHeight = rect.Height (); // Set-up the fields of the BITMAPINFOHEADER BITMAPINFOHEADER bitmap_info; bitmap_info.biSize = sizeof (BITMAPINFOHEADER); bitmap_info.biWidth = m_iWidth; bitmap_info.biHeight = -m_iHeight; // Top-down DIB uses negative height bitmap_info.biPlanes = 1; bitmap_info.biBitCount = 24; bitmap_info.biCompression = BI_RGB; bitmap_info.biSizeImage = ((m_iWidth * m_iHeight) * 3); bitmap_info.biXPelsPerMeter = 0; bitmap_info.biYPelsPerMeter = 0; bitmap_info.biClrUsed = 0; bitmap_info.biClrImportant = 0; // Get a temporary screen DC HDC hscreen_dc = ::GetDC (NULL); // Create a bitmap that we can access the bits directly of m_hBitmap = ::CreateDIBSection (hscreen_dc, (const BITMAPINFO *)&bitmap_info, DIB_RGB_COLORS, (void **)&m_pBits, NULL, 0L); // Release our temporary screen DC ::ReleaseDC (NULL, hscreen_dc); // Paint the color range into this bitmap Paint_DIB (m_iWidth, m_iHeight, m_pBits); return ; } ///////////////////////////////////////////////////////////////////////////// // // Fill_Rect // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Fill_Rect ( UCHAR *pbits, const RECT &rect, COLORREF color, int scanline_size ) { UCHAR red = GetRValue (color); UCHAR green = GetRValue (color); UCHAR blue = GetRValue (color); for (int irow = rect.top; irow < rect.bottom; irow ++) { int index = irow * scanline_size; index += (rect.left * 3); for (int col = rect.left; col < rect.right; col ++) { pbits[index++] = blue; pbits[index++] = green; pbits[index++] = red; } } return ; } ///////////////////////////////////////////////////////////////////////////// // // Color_From_Point // ///////////////////////////////////////////////////////////////////////////// COLORREF ColorPickerClass::Color_From_Point ( int x, int y ) { COLORREF color = RGB (0,0,0); // x,y location inside boundaries? if ((x >= 0) && (x < m_iWidth) && (y >= 0) && (y < m_iHeight)) { // Window's bitmaps are DWORD aligned, so make sure // we take that into account. int alignment_offset = (m_iWidth * 3) % 4; alignment_offset = (alignment_offset != 0) ? (4 - alignment_offset) : 0; int scanline_size = (m_iWidth * 3) + alignment_offset; // Read the color value straight from the BMP int index = (scanline_size * y) + (x * 3); int blue = m_pBits[index]; int green = m_pBits[index + 1]; int red = m_pBits[index + 2]; // Turn the individual compenents into a color color = RGB (red, green, blue); RECT rect; Calc_Display_Rect (rect); int width = rect.right-rect.left; m_CurrentHue = ((float)x) / ((float)width); } // Return the color return color; } ///////////////////////////////////////////////////////////////////////////// // // Polar_To_Rect // ///////////////////////////////////////////////////////////////////////////// void Polar_To_Rect (float radius, float angle, float &x, float &y) { x = radius * float(cos(angle)); y = radius * float(sin(angle)); return ; } ///////////////////////////////////////////////////////////////////////////// // // Rect_To_Polar // ///////////////////////////////////////////////////////////////////////////// void Rect_To_Polar (float x, float y, float &radius, float &angle) { if (x == 0 && y == 0) { radius = 0; angle = 0; } else { radius = (float)::sqrt ((x * x) + (y * y)); angle = (float)::atan2 (y, x); } return ; } ///////////////////////////////////////////////////////////////////////////// // // RGB_to_Hue // ///////////////////////////////////////////////////////////////////////////// void RGB_to_Hue (int red_val, int green_val, int blue_val, float &hue, float &value) { float red_radius = (float)red_val; float green_radius = (float)green_val; float blue_radius = (float)blue_val; float red_angle = 0; float green_angle = (3.1415926535F * 2.0F) / 3.0F; float blue_angle = (3.1415926535F * 4.0F) / 3.0F; struct Color2D { float x; float y; }; Color2D red = { 0 }; Color2D green = { 0 }; Color2D blue = { 0 }; Color2D result = { 0 }; ::Polar_To_Rect (red_radius, red_angle, red.x, red.y); ::Polar_To_Rect (green_radius, green_angle, green.x, green.y); ::Polar_To_Rect (blue_radius, blue_angle, blue.x, blue.y); result.x = red.x + green.x + blue.x; result.y = red.y + green.y + blue.y; float hue_angle = 0; float hue_radius = 0; ::Rect_To_Polar (result.x, result.y, hue_radius, hue_angle); hue = hue_angle / (3.14159265359F * 2.0F); if (hue < 0) { hue += 1; } else if (hue > 1) { hue -= 1; } value = (float)::fabs (hue_radius / 255); return ; } ///////////////////////////////////////////////////////////////////////////// // // Point_From_Color // ///////////////////////////////////////////////////////////////////////////// CPoint ColorPickerClass::Point_From_Color (COLORREF color) { // Window's bitmaps are DWORD aligned, so make sure // we take that into account. int alignment_offset = (m_iWidth * 3) % 4; alignment_offset = (alignment_offset != 0) ? (4 - alignment_offset) : 0; int scanline_size = (m_iWidth * 3) + alignment_offset; int red = GetRValue (color); int green = GetGValue (color); int blue = GetBValue (color); float hue = 0; float value = 0; RGB_to_Hue (red, green, blue, hue, value); RECT rect; Calc_Display_Rect (rect); int width = rect.right-rect.left; int height = rect.bottom-rect.top; float whiteness = (float)min (min (red, green), blue); float percent = whiteness / 255; float darkness = 0; // // Determine what the 'darkness' of the hue should be // if (percent != 1) { float start_red = (red - whiteness) / (1 - percent); float start_green = (green - whiteness) / (1 - percent); float start_blue = (blue - whiteness) / (1 - percent); darkness = max (max (start_red, start_green), start_blue); } int x = int(width * hue); int y = ((255 - (int)darkness) * height) / 255; // Return the point return CPoint (x, y); } ///////////////////////////////////////////////////////////////////////////// // // Paint_DIB // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Paint_DIB ( int width, int height, UCHAR *pbits ) { // Window's bitmaps are DWORD aligned, so make sure // we take that into account. int alignment_offset = (width * 3) % 4; alignment_offset = (alignment_offset != 0) ? (4 - alignment_offset) : 0; int scanline_size = (width * 3) + alignment_offset; // // Paint the border (if any) // CRect rect (0, 0, width, height); LONG lstyle = ::GetWindowLong (m_hWnd, GWL_STYLE); if (lstyle & CPS_SUNKEN) { ::Draw_Sunken_Rect (pbits, rect, scanline_size); rect.DeflateRect (1, 1); } else if (lstyle & CPS_RAISED) { ::Draw_Raised_Rect (pbits, rect, scanline_size); rect.DeflateRect (1, 1); } else if (lstyle & WS_BORDER) { ::Frame_Rect (pbits, rect, RGB (255, 255, 255), scanline_size); rect.DeflateRect (1, 1); } // Recalc the width and height (it could of changed) width = rect.Width (); height = rect.Height (); // Build an array of column indicies where we will switch color // components... int col_remainder = (width % 6); int channel_switch_cols[6]; for (int channel = 0; channel < 6; channel ++) { // Each column has at least X/6 pixels channel_switch_cols[channel] = width / 6; // Do we need to add a remainder to this col? // (Occurs when the width isn't evenly divisible by 6) if (col_remainder > 0) { channel_switch_cols[channel] += 1; col_remainder --; } // Add the previous column's switching index if (channel > 0) { channel_switch_cols[channel] += channel_switch_cols[channel - 1]; } } // Start with max red float red = 255.0F; float green = 0; float blue = 0; // Calculate some starting channel variables int curr_channel = 0; float curr_inc = (255.0F / ((float)channel_switch_cols[0])); float *pcurr_component = &green; // // Paint the image // for (int icol = rect.left; icol < rect.right; icol ++) { // Determine how much to 'darken' the current hue by for each row (goes to black) float red_dec = -((float)(red) / (float)(height-1)); float green_dec = -((float)(green) / (float)(height-1)); float blue_dec = -((float)(blue) / (float)(height-1)); // Start with the normal hue color float curr_red = red; float curr_green = green; float curr_blue = blue; // Paint all pixels in this row int bitmap_index = (icol * 3) + (rect.left * scanline_size); for (int irow = rect.top; irow < rect.bottom; irow ++) { // Put these values into the bitmap pbits[bitmap_index] = UCHAR(((int)curr_blue) & 0xFF); pbits[bitmap_index + 1] = UCHAR(((int)curr_green) & 0xFF); pbits[bitmap_index + 2] = UCHAR(((int)curr_red) & 0xFF); // Determine the current red, green, and blue values curr_red = curr_red + red_dec; curr_green = curr_green + green_dec; curr_blue = curr_blue + blue_dec; // Increment our offset into the bitmap bitmap_index += scanline_size; } // Is it time to switch to a new color channel? if ((icol - rect.left) == channel_switch_cols[curr_channel]) { // Recompute values for the new channel curr_channel ++; curr_inc = (255.0F / ((float)(channel_switch_cols[curr_channel] - channel_switch_cols[curr_channel-1]))); // Which channel are we painting? if (curr_channel == 1) { pcurr_component = &red; curr_inc = -curr_inc; } else if (curr_channel == 2) { pcurr_component = &blue; } else if (curr_channel == 3) { pcurr_component = &green; curr_inc = -curr_inc; } else if (curr_channel == 4) { pcurr_component = &red; } else if (curr_channel == 5) { pcurr_component = &blue; curr_inc = -curr_inc; } } // Increment the current color component (*pcurr_component) = (*pcurr_component) + curr_inc; } return ; } ///////////////////////////////////////////////////////////////////////////// // // Free_Bitmap // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Free_Bitmap (void) { if (m_hBitmap != NULL) { ::DeleteObject (m_hBitmap); m_hBitmap = NULL; m_pBits = NULL; } m_iWidth = 0; m_iHeight = 0; return ; } ///////////////////////////////////////////////////////////////////////////// // // OnSize // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::OnSize ( UINT nType, int cx, int cy ) { // Allow the base class to process this message CWnd::OnSize (nType, cx, cy); // Recreate the BMP to reflect the new window size Create_Bitmap (); // Determine the new point based on the current color //m_CurrentColor = RGB (128, 222, 199); //m_CurrentPoint = Point_From_Color (m_CurrentColor); return ; } ///////////////////////////////////////////////////////////////////////////// // // OnEraseBkgnd // ///////////////////////////////////////////////////////////////////////////// BOOL ColorPickerClass::OnEraseBkgnd (CDC * /*pDC*/) { return TRUE; } ///////////////////////////////////////////////////////////////////////////// // // OnLButtonDown // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::OnLButtonDown ( UINT nFlags, CPoint point ) { SetCapture (); m_bSelecting = true; RECT rect; Calc_Display_Rect (rect); ClientToScreen (&rect); ::ClipCursor (&rect); Erase_Marker (); m_CurrentPoint = point; m_CurrentColor = Color_From_Point (point.x, point.y); // // Notify the parent window that the user double-clicked // one of the keyframes // LONG id = ::GetWindowLong (m_hWnd, GWL_ID); CP_NMHDR notify_hdr = { 0 }; notify_hdr.hdr.hwndFrom = m_hWnd; notify_hdr.hdr.idFrom = id; notify_hdr.hdr.code = CPN_COLORCHANGE; notify_hdr.red = GetRValue (m_CurrentColor); notify_hdr.green = GetGValue (m_CurrentColor); notify_hdr.blue = GetBValue (m_CurrentColor); notify_hdr.hue = m_CurrentHue; ::SendMessage (::GetParent (m_hWnd), WM_NOTIFY, id, (LPARAM)¬ify_hdr); Paint_Marker (); // Allow the base class to process this message CWnd::OnLButtonDown (nFlags, point); return ; } ///////////////////////////////////////////////////////////////////////////// // // OnLButtonUp // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::OnLButtonUp ( UINT nFlags, CPoint point ) { if (m_bSelecting) { ::ClipCursor (NULL); ReleaseCapture (); m_bSelecting = false; } // Allow the base class to process this message CWnd::OnLButtonUp (nFlags, point); return ; } ///////////////////////////////////////////////////////////////////////////// // // OnMouseMove // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::OnMouseMove ( UINT nFlags, CPoint point ) { if (m_bSelecting) { Erase_Marker (); m_CurrentPoint = point; m_CurrentColor = Color_From_Point (point.x, point.y); // // Notify the parent window that the user double-clicked // one of the keyframes // LONG id = ::GetWindowLong (m_hWnd, GWL_ID); CP_NMHDR notify_hdr = { 0 }; notify_hdr.hdr.hwndFrom = m_hWnd; notify_hdr.hdr.idFrom = id; notify_hdr.hdr.code = CPN_COLORCHANGE; notify_hdr.red = GetRValue (m_CurrentColor); notify_hdr.green = GetGValue (m_CurrentColor); notify_hdr.blue = GetBValue (m_CurrentColor); notify_hdr.hue = m_CurrentHue; ::SendMessage (::GetParent (m_hWnd), WM_NOTIFY, id, (LPARAM)¬ify_hdr); Paint_Marker (); } // Allow the base class to process this message CWnd::OnMouseMove (nFlags, point); return ; } ///////////////////////////////////////////////////////////////////////////// // // Calc_Display_Rect // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Calc_Display_Rect (RECT &rect) { GetClientRect (&rect); LONG lstyle = ::GetWindowLong (m_hWnd, GWL_STYLE); if ((lstyle & CPS_SUNKEN) || (lstyle & CPS_RAISED) || (lstyle & WS_BORDER)) { rect.left += 1; rect.right -= 1; rect.top += 1; rect.bottom -= 1; } return ; } ///////////////////////////////////////////////////////////////////////////// // // Erase_Marker // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Erase_Marker (void) { HDC hdc = ::GetDC (m_hWnd); if (m_hMemDC != NULL) { HBITMAP hold_bmp = (HBITMAP)::SelectObject (m_hMemDC, m_hBitmap); CRect rect; GetClientRect (&rect); ::BitBlt (hdc, m_CurrentPoint.x - 5, m_CurrentPoint.y - 5, 11, 11, m_hMemDC, m_CurrentPoint.x - 5, m_CurrentPoint.y - 5, SRCCOPY); ::SelectObject (m_hMemDC, hold_bmp); } ::ReleaseDC (m_hWnd, hdc); return ; } ///////////////////////////////////////////////////////////////////////////// // // Paint_Marker // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Paint_Marker (void) { HDC hdc = ::GetDC (m_hWnd); if (m_hMemDC != NULL) { HBITMAP hmarker_bmp = ::LoadBitmap (::AfxGetResourceHandle (), MAKEINTRESOURCE (IDB_MARKER)); HBITMAP hold_bmp = (HBITMAP)::SelectObject (m_hMemDC, hmarker_bmp); CRect rect; GetClientRect (&rect); ::BitBlt (hdc, m_CurrentPoint.x - 5, m_CurrentPoint.y - 5, 11, 11, m_hMemDC, 0, 0, SRCINVERT); ::SelectObject (m_hMemDC, hold_bmp); ::DeleteObject (hmarker_bmp); } ::ReleaseDC (m_hWnd, hdc); return ; } ///////////////////////////////////////////////////////////////////////////// // // Select_Color // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Select_Color (int red, int green, int blue) { m_CurrentColor = RGB (red, green, blue); m_CurrentPoint = Point_From_Color (m_CurrentColor); m_CurrentColor = Color_From_Point (m_CurrentPoint.x, m_CurrentPoint.y); // Refresh the window InvalidateRect (NULL, FALSE); UpdateWindow (); return ; } ///////////////////////////////////////////////////////////////////////////// // // Get_Current_Color // ///////////////////////////////////////////////////////////////////////////// void ColorPickerClass::Get_Current_Color (int *red, int *green, int *blue) { (*red) = GetRValue (m_CurrentColor); (*green) = GetGValue (m_CurrentColor); (*blue) = GetBValue (m_CurrentColor); return ; }