564 lines
14 KiB
C++
564 lines
14 KiB
C++
|
/*
|
||
|
** 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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
/***********************************************************************************************
|
||
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
||
|
***********************************************************************************************
|
||
|
* *
|
||
|
* Project Name : LevelEdit *
|
||
|
* *
|
||
|
* $Archive:: /Commando/Code/Tools/LevelEdit/HistogramCtl.cpp $Modtime:: 7/2/99 6:28p $*
|
||
|
* *
|
||
|
* $Revision:: 3 $*
|
||
|
* *
|
||
|
*---------------------------------------------------------------------------------------------*
|
||
|
* Functions: *
|
||
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
#include "StdAfx.H"
|
||
|
#include "HistogramCtl.H"
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// HistogramCtlClass
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
HistogramCtlClass::HistogramCtlClass (void)
|
||
|
: m_hBitmap (NULL),
|
||
|
m_pBits (NULL),
|
||
|
m_ScanlineSize (0),
|
||
|
m_BMPWidth (0),
|
||
|
m_BMPHeight (0),
|
||
|
m_IsDirty (true),
|
||
|
m_AllowRefresh (true),
|
||
|
m_LowColor (0, 0.5F, 0),
|
||
|
m_HighColor (1, 0, 0),
|
||
|
m_BkLowColor (0.75F, 0.75F, 0.75F),
|
||
|
m_BkHighColor (0.1F, 0.1F, 0.1F),
|
||
|
m_SelColor (0, 0, 0),
|
||
|
m_BkSelColor (1, 1, 1)
|
||
|
{
|
||
|
m_XAxis.min = 0;
|
||
|
m_XAxis.max = 1;
|
||
|
|
||
|
m_YAxis.min = 0;
|
||
|
m_YAxis.max = 1;
|
||
|
|
||
|
m_Selection.min = -1;
|
||
|
m_Selection.max = -1;
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// ~HistogramCtlClass
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
HistogramCtlClass::~HistogramCtlClass (void)
|
||
|
{
|
||
|
Destroy_DIB_Section ();
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Create_DIB_Section
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Create_DIB_Section (void)
|
||
|
{
|
||
|
// Start fresh
|
||
|
Destroy_DIB_Section ();
|
||
|
|
||
|
//
|
||
|
// Set-up the fields of the BITMAPINFOHEADER
|
||
|
// Note: Top-down DIBs use negative height in Win32.
|
||
|
//
|
||
|
BITMAPINFOHEADER bitmap_info = { 0 };
|
||
|
bitmap_info.biSize = sizeof (BITMAPINFOHEADER);
|
||
|
bitmap_info.biWidth = m_BMPWidth;
|
||
|
bitmap_info.biHeight = -m_BMPHeight;
|
||
|
bitmap_info.biPlanes = 1;
|
||
|
bitmap_info.biBitCount = 24;
|
||
|
bitmap_info.biCompression = BI_RGB;
|
||
|
bitmap_info.biSizeImage = ((m_BMPWidth * m_BMPHeight) * 3);
|
||
|
bitmap_info.biXPelsPerMeter = 0;
|
||
|
bitmap_info.biYPelsPerMeter = 0;
|
||
|
bitmap_info.biClrUsed = 0;
|
||
|
bitmap_info.biClrImportant = 0;
|
||
|
|
||
|
//
|
||
|
// Create a bitmap that we can access the bits directly of
|
||
|
//
|
||
|
HDC hscreen_dc = ::GetDC (NULL);
|
||
|
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);
|
||
|
|
||
|
// Window's bitmaps are DWORD aligned, so make sure
|
||
|
// we take that into account.
|
||
|
int alignment_offset = (m_BMPWidth * 3) % 4;
|
||
|
alignment_offset = (alignment_offset != 0) ? (4 - alignment_offset) : 0;
|
||
|
m_ScanlineSize = (m_BMPWidth * 3) + alignment_offset;
|
||
|
|
||
|
// Force a repaint next time its necessary
|
||
|
Set_Dirty (true);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Destroy_DIB_Section
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Destroy_DIB_Section (void)
|
||
|
{
|
||
|
if (m_hBitmap != NULL) {
|
||
|
::DeleteObject (m_hBitmap);
|
||
|
m_hBitmap = NULL;
|
||
|
m_pBits = NULL;
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Dimensions
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Set_Dimensions (int width, int height)
|
||
|
{
|
||
|
if ((m_BMPWidth != width) ||
|
||
|
(m_BMPHeight != height)) {
|
||
|
|
||
|
//
|
||
|
// Recreate the BMP we use
|
||
|
//
|
||
|
m_BMPWidth = width;
|
||
|
m_BMPHeight = height;
|
||
|
Create_DIB_Section ();
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Paint_DIB
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Paint_DIB (void)
|
||
|
{
|
||
|
float x_range = m_XAxis.max - m_XAxis.min;
|
||
|
float y_range = m_YAxis.max - m_YAxis.min;
|
||
|
|
||
|
int sel_start_x = int((m_Selection.min / x_range) * (float)(m_BMPWidth-1));
|
||
|
int sel_end_x = int((m_Selection.max / x_range) * (float)(m_BMPWidth-1));
|
||
|
|
||
|
BYTE sel_back_red = BYTE (m_BkSelColor.X * 255);
|
||
|
BYTE sel_back_green = BYTE (m_BkSelColor.Y * 255);
|
||
|
BYTE sel_back_blue = BYTE (m_BkSelColor.Z * 255);
|
||
|
BYTE sel_red = BYTE (m_SelColor.X * 255);
|
||
|
BYTE sel_green = BYTE (m_SelColor.Y * 255);
|
||
|
BYTE sel_blue = BYTE (m_SelColor.Z * 255);
|
||
|
|
||
|
//
|
||
|
// Paint the background
|
||
|
//
|
||
|
if (m_pBits != NULL) {
|
||
|
float red = m_BkHighColor.X;
|
||
|
float green = m_BkHighColor.Y;
|
||
|
float blue = m_BkHighColor.Z;
|
||
|
|
||
|
float red_inc = (m_BkLowColor.X - m_BkHighColor.X) / (float)m_BMPHeight;
|
||
|
float green_inc = (m_BkLowColor.Y - m_BkHighColor.Y) / (float)m_BMPHeight;
|
||
|
float blue_inc = (m_BkLowColor.Z - m_BkHighColor.Z) / (float)m_BMPHeight;
|
||
|
|
||
|
//
|
||
|
// Loop through all the pixels and set their background color.
|
||
|
//
|
||
|
int bmp_index = 0;
|
||
|
int row_offset = m_ScanlineSize - (m_BMPWidth * 3);
|
||
|
for (int row = 0; row < m_BMPHeight; row ++) {
|
||
|
BYTE curr_red = BYTE(red * 255);
|
||
|
BYTE curr_green = BYTE(green * 255);
|
||
|
BYTE curr_blue = BYTE(blue * 255);
|
||
|
|
||
|
for (int col = 0; col < m_BMPWidth; col ++) {
|
||
|
|
||
|
if ((col >= sel_start_x) && (col <= sel_end_x)) {
|
||
|
m_pBits[bmp_index ++] = sel_back_blue;
|
||
|
m_pBits[bmp_index ++] = sel_back_green;
|
||
|
m_pBits[bmp_index ++] = sel_back_red;
|
||
|
} else {
|
||
|
m_pBits[bmp_index ++] = curr_blue;
|
||
|
m_pBits[bmp_index ++] = curr_green;
|
||
|
m_pBits[bmp_index ++] = curr_red;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
red += red_inc;
|
||
|
green += green_inc;
|
||
|
blue += blue_inc;
|
||
|
bmp_index += row_offset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Paint the columns if necessary
|
||
|
//
|
||
|
if ((m_pBits != NULL) && (m_ValueList.Count () > 0)) {
|
||
|
int start_index = Find_Value_Index (m_XAxis.min);
|
||
|
int end_index = Find_Value_Index (m_XAxis.max);
|
||
|
|
||
|
int * x_vector = new int [m_BMPWidth];
|
||
|
::memset (x_vector, 0, sizeof (int) * m_BMPWidth);
|
||
|
|
||
|
//
|
||
|
// Loop through all the values and combine pixel-duplicates
|
||
|
//
|
||
|
for (int index = start_index; index <= end_index; index ++) {
|
||
|
VALUE &value = m_ValueList[index];
|
||
|
int x_pos = int((value.value / x_range) * (float)(m_BMPWidth-1));
|
||
|
x_vector[x_pos] += value.count;
|
||
|
|
||
|
}
|
||
|
|
||
|
float start_red = m_HighColor.X;
|
||
|
float start_green = m_HighColor.Y;
|
||
|
float start_blue = m_HighColor.Z;
|
||
|
float red_inc = (m_LowColor.X - m_HighColor.X) / (float)m_BMPHeight;
|
||
|
float green_inc = (m_LowColor.Y - m_HighColor.Y) / (float)m_BMPHeight;
|
||
|
float blue_inc = (m_LowColor.Z - m_HighColor.Z) / (float)m_BMPHeight;
|
||
|
|
||
|
//
|
||
|
// Paint each column of information
|
||
|
//
|
||
|
for (int x_pos = 0; x_pos < m_BMPWidth; x_pos ++) {
|
||
|
if (x_vector[x_pos] > 0) {
|
||
|
int height = int(((float)(x_vector[x_pos]) / y_range) * (float)m_BMPHeight);
|
||
|
height = min (height, m_BMPHeight);
|
||
|
int y_pos = m_BMPHeight - height;
|
||
|
|
||
|
//
|
||
|
// Determine the starting red, green, blue
|
||
|
//
|
||
|
float curr_red = (start_red + (y_pos * red_inc));
|
||
|
float curr_green = (start_green + (y_pos * green_inc));
|
||
|
float curr_blue = (start_blue + (y_pos * blue_inc));
|
||
|
|
||
|
//
|
||
|
// Paint the column
|
||
|
//
|
||
|
int bmp_index = (y_pos * m_ScanlineSize) + (x_pos * 3);
|
||
|
for (int pixel = y_pos; pixel < m_BMPHeight; pixel ++) {
|
||
|
|
||
|
//
|
||
|
// Plot the pixel in the BMP (note, the bits are orgainzed
|
||
|
// in an BGR format).
|
||
|
//
|
||
|
|
||
|
if ((x_pos >= sel_start_x) && (x_pos <= sel_end_x)) {
|
||
|
m_pBits[bmp_index] = sel_blue;
|
||
|
m_pBits[bmp_index + 1] = sel_green;
|
||
|
m_pBits[bmp_index + 2] = sel_red;
|
||
|
} else {
|
||
|
m_pBits[bmp_index] = BYTE(curr_blue * 255);
|
||
|
m_pBits[bmp_index + 1] = BYTE(curr_green * 255);
|
||
|
m_pBits[bmp_index + 2] = BYTE(curr_red * 255);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Increment the current cocor
|
||
|
//
|
||
|
curr_red += red_inc;
|
||
|
curr_green += green_inc;
|
||
|
curr_blue += blue_inc;
|
||
|
|
||
|
// Move down to the next scanline
|
||
|
bmp_index += m_ScanlineSize;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
delete [] x_vector;
|
||
|
Set_Dirty (false);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Add_Data_Point
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Add_Data_Point (float value)
|
||
|
{
|
||
|
int start = 0;
|
||
|
int end = m_ValueList.Count ();
|
||
|
end = max (end, 0);
|
||
|
|
||
|
bool found = false;
|
||
|
int index = end;
|
||
|
while (end > start)
|
||
|
{
|
||
|
index = start + ((end - start) >> 1);
|
||
|
float curr_value = m_ValueList[index].value;
|
||
|
|
||
|
if (value > curr_value) {
|
||
|
if (start == index) { index ++; break; }
|
||
|
start = index;
|
||
|
} else if (value < curr_value) {
|
||
|
if (end == index) { index --; break; }
|
||
|
end = index;
|
||
|
} else {
|
||
|
m_ValueList[index].count ++;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
ASSERT (index >=0 );
|
||
|
|
||
|
if (found == false) {
|
||
|
m_ValueList.Insert (index, VALUE (value, 1));
|
||
|
}
|
||
|
|
||
|
|
||
|
/*int start = 0;
|
||
|
int end = m_ValueList.Count () - 1;
|
||
|
|
||
|
//
|
||
|
// Simple binary search to find the right place
|
||
|
// to insert the new data point
|
||
|
//
|
||
|
bool found = false;
|
||
|
while ((end > start) && !found) {
|
||
|
|
||
|
int index = start + ((end - start) >> 1);
|
||
|
VALUE &curr_point = m_ValueList[index];
|
||
|
if (index == start) {
|
||
|
break;
|
||
|
} else if (value > curr_point.value) {
|
||
|
start = index;
|
||
|
} else if (value < curr_point.value) {
|
||
|
end = index;
|
||
|
} else {
|
||
|
curr_point.count ++;
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we didn't find the exact point in our search,
|
||
|
// then add the point to the list.
|
||
|
//
|
||
|
if (found == false) {
|
||
|
m_ValueList.Insert (start, VALUE (value, 1));
|
||
|
}*/
|
||
|
|
||
|
Set_Dirty (true);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Find_Value_Index
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
int
|
||
|
HistogramCtlClass::Find_Value_Index (float value)
|
||
|
{
|
||
|
//int retval = -1;
|
||
|
|
||
|
|
||
|
int start = 0;
|
||
|
int end = m_ValueList.Count ();
|
||
|
end = max (end, 0);
|
||
|
|
||
|
int index = end;
|
||
|
while (end > start)
|
||
|
{
|
||
|
index = start + ((end - start) >> 1);
|
||
|
float curr_value = m_ValueList[index].value;
|
||
|
|
||
|
if (value > curr_value) {
|
||
|
if (start == index) { index ++; break; }
|
||
|
start = index;
|
||
|
} else if (value < curr_value) {
|
||
|
if (end == index) { index --; break; }
|
||
|
end = index;
|
||
|
} else {
|
||
|
|
||
|
while ((curr_value == value) && (index > 0)) {
|
||
|
curr_value = m_ValueList[--index].value;
|
||
|
}
|
||
|
|
||
|
index = (curr_value == value) ? index : index + 1;
|
||
|
|
||
|
//m_ValueList[index].count ++;
|
||
|
//found = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
ASSERT (index >=0 );
|
||
|
|
||
|
/*if (found == false) {
|
||
|
m_ValueList.Insert (index, VALUE (value, 1));
|
||
|
}*/
|
||
|
|
||
|
|
||
|
//
|
||
|
// Simple binary search to find the right place
|
||
|
// to insert the new data point
|
||
|
//
|
||
|
/*int start = 0;
|
||
|
int end = m_ValueList.Count () - 1;
|
||
|
while ((end > start) && (retval == -1)) {
|
||
|
|
||
|
int index = start + ((end - start) >> 1);
|
||
|
VALUE &curr_point = m_ValueList[index];
|
||
|
if (index == start) {
|
||
|
retval = (value > curr_point.value) ? end : start;
|
||
|
break;
|
||
|
} else if (value > curr_point.value) {
|
||
|
start = index;
|
||
|
} else if (value < curr_point.value) {
|
||
|
end = index;
|
||
|
} else {
|
||
|
retval = index;
|
||
|
}
|
||
|
}*/
|
||
|
|
||
|
/*if (retval == -1) {
|
||
|
retval = index;
|
||
|
}*/
|
||
|
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Render
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Render (HDC hdc, int x_pos, int y_pos)
|
||
|
{
|
||
|
//
|
||
|
// Ensure we have an up-to-date graph
|
||
|
//
|
||
|
if (m_IsDirty) {
|
||
|
Paint_DIB ();
|
||
|
}
|
||
|
|
||
|
if (m_hBitmap != NULL) {
|
||
|
HDC mem_dc = ::CreateCompatibleDC (NULL);
|
||
|
HBITMAP old_bmp = (HBITMAP)::SelectObject (mem_dc, m_hBitmap);
|
||
|
|
||
|
//
|
||
|
// Paint our graph onto the window
|
||
|
//
|
||
|
::BitBlt (hdc, x_pos, y_pos, m_BMPWidth, m_BMPHeight, mem_dc, 0, 0, SRCCOPY);
|
||
|
|
||
|
// Cleanup
|
||
|
::SelectObject (mem_dc, old_bmp);
|
||
|
::DeleteDC (mem_dc);
|
||
|
}
|
||
|
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Reset_Data_Points
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Reset_Data_Points (void)
|
||
|
{
|
||
|
m_ValueList.Delete_All ();
|
||
|
Set_Dirty (true);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Bk_Color
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Set_Bk_Color
|
||
|
(
|
||
|
const Vector3 &low,
|
||
|
const Vector3 &high
|
||
|
)
|
||
|
{
|
||
|
m_BkLowColor = low;
|
||
|
m_BkHighColor = high;
|
||
|
Set_Dirty (true);
|
||
|
return ;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Set_Color
|
||
|
//
|
||
|
/////////////////////////////////////////////////////////////////////////////
|
||
|
void
|
||
|
HistogramCtlClass::Set_Color
|
||
|
(
|
||
|
const Vector3 &low,
|
||
|
const Vector3 &high
|
||
|
)
|
||
|
{
|
||
|
m_LowColor = low;
|
||
|
m_HighColor = high;
|
||
|
Set_Dirty (true);
|
||
|
return ;
|
||
|
}
|