File: ColorManager.h

package info (click to toggle)
veroroute 2.38-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,044 kB
  • sloc: cpp: 21,512; xml: 89; sh: 65; lisp: 20; makefile: 5
file content (273 lines) | stat: -rw-r--r-- 9,540 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
/*
	VeroRoute - Qt based Veroboard/Perfboard/PCB layout & routing application.

	Copyright (C) 2017  Alex Lawrow    ( dralx@users.sourceforge.net )

	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/>.
*/

#pragma once

#include "AdjInfoManager.h"
#include "MyRGB.h"
#include "Grid.h"

#define MYNUMCOLORS 		12
#define MY_GREY				(MYNUMCOLORS)
#define MY_BLACK			(MYNUMCOLORS+1)
#define MY_WHITE			(MYNUMCOLORS+2)
#define NUM_PIXMAP_COLORS	(MYNUMCOLORS+3)
#define MY_LYR_BOT			(MYNUMCOLORS+4)
#define MY_LYR_TOP			(MYNUMCOLORS+5)

// Manager class to handle assignment of colors to nodeIds

const int BAD_COLORID = -1;

class ColorManager : public Persist, public Merge
{
public:
	ColorManager() : m_iSaturation(100), m_iFillSaturation(0), m_bReAssign(true) {}
	virtual ~ColorManager() {}
	ColorManager(const ColorManager& o) { *this = o; }	// Never called
	ColorManager& operator=(const ColorManager& o)
	{
		m_mapNodeIdToColorId.clear();
		m_mapNodeIdToColorId.insert(o.m_mapNodeIdToColorId.begin(), o.m_mapNodeIdToColorId.end());
		m_mapNodeIdToCustomColor.clear();
		m_mapNodeIdToCustomColor.insert(o.m_mapNodeIdToCustomColor.begin(), o.m_mapNodeIdToCustomColor.end());
		m_iSaturation		= o.m_iSaturation;
		m_iFillSaturation	= o.m_iFillSaturation;
		m_bReAssign			= o.m_bReAssign;
		return *this;
	}
	bool operator==(const ColorManager& o) const
	{
		if ( m_mapNodeIdToCustomColor.size() != o.m_mapNodeIdToCustomColor.size() ) return false;
		for (const auto& mapObj : m_mapNodeIdToCustomColor)
		{
			const auto iter = o.m_mapNodeIdToCustomColor.find( mapObj.first );
			if ( iter == o.m_mapNodeIdToCustomColor.end() ) return false;
			if ( mapObj.second != iter->second ) return false;
		}
		return true;
	}
	bool operator!=(const ColorManager& o) const
	{
		return !(*this == o);
	}
	void Clear()					{ m_mapNodeIdToColorId.clear(); m_mapNodeIdToCustomColor.clear(); ReAssignColors(); }
	void ReAssignColors()			{ m_bReAssign = true; }
	void SetSaturation(int i)		{ m_iSaturation = i; }
	void SetFillSaturation(int i)	{ m_iFillSaturation = i; }
	void SetNodeColor(int nodeId, const QColor& color)
	{
		if ( nodeId != BAD_NODEID ) m_mapNodeIdToCustomColor[nodeId] = MyRGB(color);
	}
	bool GetIsFixed(int nodeId) const
	{
		return m_mapNodeIdToCustomColor.find(nodeId) != m_mapNodeIdToCustomColor.end();
	}
	void Fix(int nodeId)
	{
		if ( nodeId == BAD_NODEID || GetIsFixed(nodeId) ) return;	// Already fixed
		const int colorId = m_mapNodeIdToColorId[nodeId];
		const bool bOK = colorId != BAD_COLORID;	assert(bOK);
		if ( bOK )
			m_mapNodeIdToCustomColor[nodeId] = GetPixmapRGB(colorId, false);
	}
	void Unfix(int nodeId)
	{
		auto iter = m_mapNodeIdToCustomColor.find(nodeId);
		if ( iter != m_mapNodeIdToCustomColor.end() )
			m_mapNodeIdToCustomColor.erase(iter);
	}
	void CalculateColors(const AdjInfoManager& adjManager, ElementGrid* pBoard)	// The coloring algorithm
	{
		std::vector<int> nodeIds;
		adjManager.GetBoardNodeIds(nodeIds);	// The set of valid nodeIDs on the board

		if ( m_bReAssign )
		{
			if ( pBoard->GetNumNodeIds() > MYNUMCOLORS )
				m_mapNodeIdToColorId.clear();	// Only wipe if we require more colors
			m_bReAssign = false;
		}
		std::list<int> cnList[MYNUMCOLORS];	// Lists of nodeIds used by colours
		int iStartColorId(0);
		for (size_t i = 0, iSize = nodeIds.size(); i < iSize; i++)	// Loop nodeIds on board
		{
			const int&	nodeIdI		= nodeIds[i];
			const int	colorIdI	= GetColorId(nodeIdI);
			if ( colorIdI != BAD_COLORID )
			{
				cnList[colorIdI].push_back(nodeIdI);	// Update list
				continue;	// Don't recolor
			}

			const AdjInfo* pI = adjManager.GetAdjInfo( nodeIdI );

			int bestColorId(0), minCost(INT_MAX);
			for (int iLoopColor = 0; iLoopColor < MYNUMCOLORS; iLoopColor++)
			{
				const int iColorId = ( iStartColorId + iLoopColor ) % MYNUMCOLORS;
				m_mapNodeIdToColorId[nodeIdI] = iColorId;
				int cost(0);
				for (size_t j = 0; j < iSize; j++)	// Loop nodeIds on board
				{
					if ( j == i ) continue;	// Skip self
					const int&	nodeIdJ	= nodeIds[j];

					if ( !pI->GetHasAdj(nodeIdJ) ) continue;	// Skip non-adjacent nodes
					if ( iColorId == GetColorId(nodeIdJ) ) cost++;
				}
				if ( cost < minCost ) { minCost = cost;	bestColorId = iColorId; }	// Update bestColorId
			}
			m_mapNodeIdToColorId[nodeIdI] = bestColorId;
			cnList[bestColorId].push_back(nodeIdI);	// Update list
			iStartColorId = bestColorId;
		}
		// Handle case when we have unused colors
		while ( true )
		{
			// Find first unused colorId, and the most used colorId (used more than once)
			int iMostUsedColor(BAD_COLORID), iUnusedColor(BAD_COLORID);
			size_t nMaxCount(1);
			for (int i = 0; i < MYNUMCOLORS; i++)
			{
				if ( cnList[i].size() > nMaxCount ) { iMostUsedColor = i; nMaxCount = cnList[i].size(); }
				if ( cnList[i].size() == 0 && iUnusedColor == BAD_COLORID) iUnusedColor = i;
			}
			if ( iUnusedColor == BAD_COLORID ) break;		// All colors used
			if ( iMostUsedColor == BAD_COLORID ) break;		// No color used more than once
			// Do the color swap
			auto iter = cnList[iMostUsedColor].begin(); ++iter;
			const int iNodeId = *iter;						// Pick second lowest nodeId
			m_mapNodeIdToColorId[iNodeId] = iUnusedColor;	// Give it the unused colorId
			cnList[iMostUsedColor].erase(iter);				// Remove nodeId from list of iMostUsedColor
			cnList[iUnusedColor].push_back(iNodeId);		// Add nodeId to list of iUnusedColor
		}
	}
	int GetColorId(int nodeId) const
	{
		if ( nodeId == BAD_NODEID ) return BAD_COLORID;
		const auto iter = m_mapNodeIdToColorId.find(nodeId);
		return ( iter != m_mapNodeIdToColorId.end() ) ? iter->second : BAD_COLORID;
	}
	QColor GetColorFromNodeId(int nodeId, bool bUseSaturation = true) const
	{
		// First check if the nodeId is in the custom list
		const auto iter = m_mapNodeIdToCustomColor.find(nodeId);
		if ( iter != m_mapNodeIdToCustomColor.end() )
		{
			int R(0), G(0), B(0);
			const MyRGB& rgb = iter->second;
			rgb.GetRGB(R, G, B);
			if ( bUseSaturation ) HandleSaturation(R, G, B);
			return QColor(R, G, B);
		}
		// Use auto-calculated colors
		return GetPixmapColor(GetColorId(nodeId), bUseSaturation);
	}
	MyRGB GetPixmapRGB(int colorId, bool bUseSaturation = true) const
	{
		int R(0), G(0), B(0);
		if		( colorId == BAD_COLORID )	{ R = G = B = 255; }
		else if ( colorId == MY_GREY )		{ R = G = B = 96; }
		else if ( colorId == MY_WHITE )		{ R = G = B = 255; }
		else if ( colorId == MY_LYR_BOT )	{ G = 128; }
		else if ( colorId == MY_LYR_TOP )	{ G = 96; B = 192; }
		else if	( colorId >= 0 && colorId < MYNUMCOLORS )
		{
			const MyRGB& rgb = sm_color[colorId % MYNUMCOLORS];
			rgb.GetRGB(R, G, B);
			if ( bUseSaturation ) HandleSaturation(R, G, B);
		}
		else
		{
			assert(colorId == MY_BLACK);
		}
		MyRGB rgb;
		rgb.SetRGB(R, G, B);
		return rgb;
	}
	QColor GetPixmapColor(int colorId, bool bUseSaturation = true) const
	{
		return GetPixmapRGB(colorId, bUseSaturation).GetQColor();
	}
	void HandleSaturation(int& R, int&G, int& B) const
	{
		const int iA = (100 - m_iSaturation) * 255;
		if ( m_iFillSaturation == 0 )
		{
			R = ( iA + m_iSaturation * R ) / 100;
			G = ( iA + m_iSaturation * G ) / 100;
			B = ( iA + m_iSaturation * B ) / 100;
		}
		else
		{
			R = G = B = iA / 100;
		}
	}
	// Merge interface functions
	virtual void UpdateMergeOffsets(MergeOffsets& o) override
	{
		for (const auto& mapObj : m_mapNodeIdToCustomColor)
			if ( mapObj.first != BAD_NODEID ) o.deltaNodeId = std::max(o.deltaNodeId, mapObj.first  + 1);
	}
	virtual void ApplyMergeOffsets(const MergeOffsets& o) override
	{
		std::unordered_map<int,MyRGB> tmp;
		for (const auto& mapObj : m_mapNodeIdToCustomColor)
			tmp[mapObj.first + o.deltaNodeId] = mapObj.second;
		m_mapNodeIdToCustomColor.clear();
		m_mapNodeIdToCustomColor.insert(tmp.begin(), tmp.end());
	}
	void Merge(const ColorManager& src)
	{
		m_mapNodeIdToCustomColor.insert(src.m_mapNodeIdToCustomColor.begin(), src.m_mapNodeIdToCustomColor.end());
	}
	// Persist interface functions
	virtual void Load(DataStream& inStream) override
	{
		m_mapNodeIdToCustomColor.clear();
		unsigned int numNodeIds(0);
		inStream.Load(numNodeIds);
		for (unsigned int i = 0; i < numNodeIds; i++)
		{
			int iNodeId;
			MyRGB rgb;
			inStream.Load(iNodeId);
			rgb.Load(inStream);
			m_mapNodeIdToCustomColor[iNodeId] = rgb;
		}
	}
	virtual void Save(DataStream& outStream) override
	{
		const unsigned int numNodeIds = static_cast<unsigned int>( m_mapNodeIdToCustomColor.size() );
		outStream.Save(numNodeIds);
		for (auto& mapObj : m_mapNodeIdToCustomColor)
		{
			outStream.Save(mapObj.first);
			mapObj.second.Save(outStream);
		}
	}
private:
	static MyRGB					sm_color[MYNUMCOLORS];	// Hard-coded colors
	std::unordered_map<int,int>		m_mapNodeIdToColorId;
	std::unordered_map<int,MyRGB>	m_mapNodeIdToCustomColor;
	int								m_iSaturation;		// 0 to 100. At 0 the colors would all fade to white.
	int								m_iFillSaturation;	// 0 to 100. If non-zero then turn colors grey.
	bool							m_bReAssign;
};