void ccRenderingTools::DrawColorRamp(const CC_DRAW_CONTEXT& context, const ccScalarField* sf, ccGLWindow* win, int glW, int glH, float renderZoom/*=1.0f*/)
{
	if (!sf || !sf->getColorScale() || !win)
	{
		return;
	}

	//get the set of OpenGL functions (version 2.1)
	QOpenGLFunctions_2_1 *glFunc = context.glFunctions<QOpenGLFunctions_2_1>();
	assert(glFunc != nullptr);

	if (glFunc == nullptr)
		return;

	bool logScale = sf->logScale();
	bool symmetricalScale = sf->symmetricalScale();
	bool alwaysShowZero = sf->isZeroAlwaysShown();

	//set of particular values
	//DGM: we work with doubles for maximum accuracy
	ccColorScale::LabelSet keyValues;
	bool customLabels = false;
	try
	{
		ccColorScale::Shared colorScale = sf->getColorScale();
		if (colorScale && colorScale->customLabels().size() >= 2)
		{
			keyValues = colorScale->customLabels();

			if (alwaysShowZero)
			{
				keyValues.insert(0.0);
			}

			customLabels = true;
		}
		else if (!logScale)
		{
			keyValues.insert(sf->displayRange().min());
			keyValues.insert(sf->displayRange().start());
			keyValues.insert(sf->displayRange().stop());
			keyValues.insert(sf->displayRange().max());
			keyValues.insert(sf->saturationRange().min());
			keyValues.insert(sf->saturationRange().start());
			keyValues.insert(sf->saturationRange().stop());
			keyValues.insert(sf->saturationRange().max());

			if (symmetricalScale)
				keyValues.insert(-sf->saturationRange().max());

			if (alwaysShowZero)
				keyValues.insert(0.0);
		}
		else
		{
			ScalarType minDisp = sf->displayRange().min();
			ScalarType maxDisp = sf->displayRange().max();
			ConvertToLogScale(minDisp, maxDisp);
			keyValues.insert(minDisp);
			keyValues.insert(maxDisp);

			ScalarType startDisp = sf->displayRange().start();
			ScalarType stopDisp = sf->displayRange().stop();
			ConvertToLogScale(startDisp, stopDisp);
			keyValues.insert(startDisp);
			keyValues.insert(stopDisp);

			keyValues.insert(sf->saturationRange().min());
			keyValues.insert(sf->saturationRange().start());
			keyValues.insert(sf->saturationRange().stop());
			keyValues.insert(sf->saturationRange().max());
		}
	}
	catch (const std::bad_alloc&)
	{
		//not enough memory
		return;
	}

	//magic fix (for infinite values!)
	{
		for (ccColorScale::LabelSet::iterator it = keyValues.begin(); it != keyValues.end(); ++it)
		{
#if defined(CC_WINDOWS) && defined(_MSC_VER)
			if (!_finite(*it))
#else
			if (!std::isfinite(*it))
#endif
			{
				bool minusInf = (*it < 0);
				keyValues.erase(it);
				if (minusInf)
					keyValues.insert(-std::numeric_limits<ScalarType>::max());
				else
					keyValues.insert(std::numeric_limits<ScalarType>::max());
				it = keyValues.begin(); //restart the process (easier than trying to be intelligent here ;)
			}
		}
	}

	//Internally, the elements in a set are already sorted
	//std::sort(keyValues.begin(), keyValues.end());

	if (!customLabels && !sf->areNaNValuesShownInGrey())
	{
		//remove 'hidden' values
		if (!logScale)
		{
			for (ccColorScale::LabelSet::iterator it = keyValues.begin(); it != keyValues.end(); )
			{
				if (!sf->displayRange().isInRange(static_cast<ScalarType>(*it)) && (!alwaysShowZero || *it != 0)) //we keep zero if the user has explicitely asked for it!
				{
					ccColorScale::LabelSet::iterator toDelete = it;
					++it;
					keyValues.erase(toDelete);
				}
				else
				{
					++it;
				}
			}
		}
		else
		{
			//convert actual display range to log scale
			//(we can't do the opposite, otherwise we get accuracy/round-off issues!)
			ScalarType dispMin = sf->displayRange().start();
			ScalarType dispMax = sf->displayRange().stop();
			ConvertToLogScale(dispMin, dispMax);

			for (ccColorScale::LabelSet::iterator it = keyValues.begin(); it != keyValues.end(); )
			{
				if (*it >= dispMin && *it <= dispMax)
				{
					++it;
				}
				else
				{
					ccColorScale::LabelSet::iterator toDelete = it;
					++it;
					keyValues.erase(toDelete);
				}
			}
		}
	}

	const ccGui::ParamStruct& displayParams = win->getDisplayParameters();

	//default color: text color
	const ccColor::Rgbub& textColor = displayParams.textDefaultCol;

	//histogram?
	const ccScalarField::Histogram histogram = sf->getHistogram();
	bool showHistogram = (displayParams.colorScaleShowHistogram && !logScale && histogram.maxValue != 0 && histogram.size() > 1);

	//display area
	QFont font = win->getTextDisplayFont(); //takes rendering zoom into account!
	const int strHeight = static_cast<int>(displayParams.defaultFontSize * renderZoom); //QFontMetrics(font).height() --> always returns the same value?!
	const int scaleWidth = static_cast<int>(displayParams.colorScaleRampWidth * renderZoom);
	const int scaleMaxHeight = (keyValues.size() > 1 ? std::max(glH - static_cast<int>(140 * renderZoom), 2 * strHeight) : scaleWidth); //if 1 value --> we draw a cube

	//centered orthoprojective view (-halfW,-halfH,halfW,halfH)
	int halfW = (glW >> 1);
	int halfH = (glH >> 1);

	//top-right corner of the scale ramp
	const int xShift = static_cast<int>(20 * renderZoom) + (showHistogram ? scaleWidth / 2 : 0);
	const int yShift = halfH - scaleMaxHeight / 2 - static_cast<int>(10 * renderZoom);

	glFunc->glPushAttrib(GL_DEPTH_BUFFER_BIT);
	glFunc->glDisable(GL_DEPTH_TEST);

	std::vector<double> sortedKeyValues(keyValues.begin(),keyValues.end());
	double maxRange = sortedKeyValues.back()-sortedKeyValues.front();

	//display color ramp
	{
		glFunc->glPushAttrib(GL_LINE_BIT);
		glFunc->glLineWidth(renderZoom);

		//(x,y): current display area coordinates (top-left corner)
		int x = halfW-xShift-scaleWidth;
		int y = halfH-yShift-scaleMaxHeight;

		if (keyValues.size() > 1)
		{
			int histoStart = x + scaleWidth + std::min(std::max(scaleWidth / 8, 3), static_cast<int>(15 * renderZoom));

			glFunc->glBegin(GL_LINES);
			for (int j=0; j<scaleMaxHeight; ++j)
			{
				double baseValue = sortedKeyValues.front() + (j * maxRange) / scaleMaxHeight;
				double value = baseValue;
				if (logScale)
				{
					value = exp(value*c_log10);
				}
				const ColorCompType* col = sf->getColor(static_cast<ScalarType>(value));
				if (!col)
				{
					//special case: if we have user-defined labels, we want all the labels to be displayed with their associated color
					if (customLabels)
					{
						assert(sf->getColorScale() && !sf->getColorScale()->isRelative());
						col = sf->getColorScale()->getColorByValue(value, ccColor::lightGrey.rgba);
					}
					else
					{
						col = ccColor::lightGrey.rgba;
					}
				}
				assert(col);
				glFunc->glColor3ubv(col);

				glFunc->glVertex2i(x,y+j);
				glFunc->glVertex2i(x+scaleWidth,y+j);

				if (showHistogram)
				{
					double bind = (value - sf->displayRange().min())*(histogram.size() - 1) / sf->displayRange().maxRange();
					int bin = static_cast<int>(floor(bind));

					double hVal = 0.0;
					if (bin >= 0 && bin < static_cast<int>(histogram.size())) //in symmetrical case we can get values outside of the real SF range
					{
						hVal = histogram[bin];
						if (bin+1 < static_cast<int>(histogram.size()))
						{
							//linear interpolation
							double alpha = bind-static_cast<double>(bin);
							hVal = (1.0-alpha) * hVal + alpha * histogram[bin+1];
						}
					}

					int xSpan = std::max(static_cast<int>(hVal / histogram.maxValue * (scaleWidth/2)),1);
					glFunc->glVertex2i(histoStart,y+j);
					glFunc->glVertex2i(histoStart+xSpan,y+j);
				}
			}
			glFunc->glEnd();
		}
		else
		{
			//if there's a unique (visible) scalar value, we only draw a square!
			double value = sortedKeyValues.front();
			if (logScale)
				value = exp(value*c_log10);
			const ColorCompType* col = sf->getColor(static_cast<ScalarType>(value));
			glFunc->glColor3ubv(col ? col : ccColor::lightGrey.rgba);
			glFunc->glBegin(GL_POLYGON);
			glFunc->glVertex2i(x,y);
			glFunc->glVertex2i(x+scaleWidth,y);
			glFunc->glVertex2i(x+scaleWidth,y+scaleMaxHeight-1);
			glFunc->glVertex2i(x,y+scaleMaxHeight-1);
			glFunc->glEnd();
		}

		//scale border
		const ccColor::Rgbub& lineColor = textColor;
		glFunc->glColor3ubv(lineColor.rgb);
		glFunc->glLineWidth(2.0f * renderZoom);
		glFunc->glEnable(GL_LINE_SMOOTH);
		glFunc->glBegin(GL_LINE_LOOP);
		glFunc->glVertex2i(x,y);
		glFunc->glVertex2i(x+scaleWidth,y);
		glFunc->glVertex2i(x+scaleWidth,y+scaleMaxHeight);
		glFunc->glVertex2i(x,y+scaleMaxHeight);
		glFunc->glEnd();

		glFunc->glPopAttrib();
	}

	//display labels
	{
		//list of labels to draw
		vlabelSet drawnLabels;

		//add first label
		drawnLabels.push_back(vlabel(0, 0, strHeight, sortedKeyValues.front()));

		if (keyValues.size() > 1)
		{
			//add last label
			drawnLabels.push_back(vlabel(scaleMaxHeight, scaleMaxHeight - strHeight, scaleMaxHeight, sortedKeyValues.back()));
		}

		//we try to display the other keyPoints (if any)
		if (keyValues.size() > 2)
		{
			assert(maxRange > 0.0);
			const int minGap = strHeight;
			for (size_t i=1; i<keyValues.size()-1; ++i)
			{
				int yScale = static_cast<int>((sortedKeyValues[i]-sortedKeyValues[0]) * scaleMaxHeight / maxRange);
				vlabelPair nLabels = GetVLabelsAround(yScale,drawnLabels);

				assert(nLabels.first != drawnLabels.end() && nLabels.second != drawnLabels.end());
				if (	(nLabels.first == drawnLabels.end() || nLabels.first->yMax <= yScale - minGap)
					&&	(nLabels.second == drawnLabels.end() || nLabels.second->yMin >= yScale + minGap))
				{
					//insert it at the right place (so as to keep a sorted list!)
					drawnLabels.insert(nLabels.second,vlabel(yScale,yScale-strHeight/2,yScale+strHeight/2,sortedKeyValues[i]));
				}
			}
		}

		//now we recursively display labels for which we have some room left
		if (!customLabels && drawnLabels.size() > 1)
		{
			const int minGap = strHeight*2;

			size_t drawnLabelsBefore = 0; //just to init the loop
			size_t drawnLabelsAfter = drawnLabels.size();

			//proceed until no more label can be inserted
			while (drawnLabelsAfter > drawnLabelsBefore)
			{
				drawnLabelsBefore = drawnLabelsAfter;

				vlabelSet::iterator it1 = drawnLabels.begin();
				vlabelSet::iterator it2 = it1; ++it2;
				for (; it2 != drawnLabels.end(); ++it2)
				{
					if (it1->yMax + 2*minGap < it2->yMin)
					{
						//insert label
						double val = (it1->val + it2->val)/2.0;
						int yScale = static_cast<int>((val-sortedKeyValues[0]) * scaleMaxHeight / maxRange);

						//insert it at the right place (so as to keep a sorted list!)
						drawnLabels.insert(it2,vlabel(yScale,yScale-strHeight/2,yScale+strHeight/2,val));
					}
					it1 = it2;
				}

				drawnLabelsAfter = drawnLabels.size();
			}
		}

		//display labels

		//Some versions of Qt seem to need glColorf instead of glColorub! (see https://bugreports.qt-project.org/browse/QTBUG-6217)
		glFunc->glColor3f(textColor.r / 255.0f, textColor.g / 255.0f, textColor.b / 255.0f);

		//Scalar field name
		const char* sfName = sf->getName();
		if (sfName)
		{
			//QString sfTitle = QString("[%1]").arg(sfName);
			QString sfTitle(sfName);
			if (sf->getGlobalShift() != 0)
				sfTitle += QString("[Shifted]");
			if (logScale)
				sfTitle += QString("[Log scale]");
			//we leave some (vertical) space for the top-most label!
			win->displayText(sfTitle, glW-xShift, glH-yShift+strHeight, ccGLWindow::ALIGN_HRIGHT | ccGLWindow::ALIGN_VTOP, 0, 0, &font);
		}

		//precision (same as color scale)
		const unsigned precision = displayParams.displayedNumPrecision;
		//format
		const char format = (sf->logScale() ? 'E' : 'f');
		//tick
		const int tickSize = static_cast<int>(4 * renderZoom);

		//for labels
		const int x = glW-xShift-scaleWidth-2*tickSize-1;
		const int y = glH-yShift-scaleMaxHeight;
		//for ticks
		const int xTick = halfW-xShift-scaleWidth-tickSize-1;
		const int yTick = halfH-yShift-scaleMaxHeight;

		for (vlabelSet::iterator it = drawnLabels.begin(); it != drawnLabels.end(); ++it)
		{
			vlabelSet::iterator itNext = it; ++itNext;
			//position
			unsigned char align = ccGLWindow::ALIGN_HRIGHT;
			if (it == drawnLabels.begin())
				align |= ccGLWindow::ALIGN_VTOP;
			else if (itNext == drawnLabels.end())
				align |= ccGLWindow::ALIGN_VBOTTOM;
			else
				align |= ccGLWindow::ALIGN_VMIDDLE;

			double value = it->val;
			if (logScale)
				value = exp(value*c_log10);

			win->displayText(QString::number(value,format,precision), x, y+it->yPos, align, 0, 0, &font);
			glFunc->glBegin(GL_LINES);
			glFunc->glVertex2i(xTick,yTick+it->yPos);
			glFunc->glVertex2i(xTick+tickSize,yTick+it->yPos);
			glFunc->glEnd();
		}
	}

	glFunc->glPopAttrib();
}
Exemple #2
0
void ccRenderingTools::DrawColorRamp(const CC_DRAW_CONTEXT& context)
{
	const ccScalarField* sf = context.sfColorScaleToDisplay;
	if (!sf || !sf->getColorScale())
		return;

//#define USE_OLD_SCALE_RENDERING
#ifndef USE_OLD_SCALE_RENDERING

	ccGLWindow* win = static_cast<ccGLWindow*>(context._win);
	if (!win)
		return;

	bool logScale = sf->logScale();
	bool symmetricalScale = sf->symmetricalScale();
	bool alwaysShowZero = sf->isZeroAlwaysShown();
	
	//set of particular values
	//DGM: we work with doubles for maximum accuracy
	std::set<double> keyValues;
	if (!logScale)
	{
		keyValues.insert(sf->displayRange().min());
		keyValues.insert(sf->displayRange().start());
		keyValues.insert(sf->displayRange().stop());
		keyValues.insert(sf->displayRange().max());
		keyValues.insert(sf->saturationRange().min());
		keyValues.insert(sf->saturationRange().start());
		keyValues.insert(sf->saturationRange().stop());
		keyValues.insert(sf->saturationRange().max());

		if (symmetricalScale)
			keyValues.insert(-sf->saturationRange().max());

		if (alwaysShowZero)
			keyValues.insert(0.0);
	}
	else
	{
		ScalarType minDisp = sf->displayRange().min();
		ScalarType maxDisp = sf->displayRange().max();
		ConvertToLogScale(minDisp,maxDisp);
		keyValues.insert(minDisp);
		keyValues.insert(maxDisp);

		ScalarType startDisp = sf->displayRange().start();
		ScalarType stopDisp = sf->displayRange().stop();
		ConvertToLogScale(startDisp,stopDisp);
		keyValues.insert(startDisp);
		keyValues.insert(stopDisp);

		keyValues.insert(sf->saturationRange().min());
		keyValues.insert(sf->saturationRange().start());
		keyValues.insert(sf->saturationRange().stop());
		keyValues.insert(sf->saturationRange().max());
	}

	//Internally, the elements in a set are already sorted
	//std::sort(keyValues.begin(),keyValues.end());

	if (!sf->areNaNValuesShownInGrey())
	{
		//remove 'hidden' values
		if (!logScale)
		{
			for (std::set<double>::iterator it = keyValues.begin(); it != keyValues.end(); )
			{
				if (!sf->displayRange().isInRange(static_cast<ScalarType>(*it)) && (!alwaysShowZero || *it != 0)) //we keep zero if the user has explicitely asked for it!
				{
					std::set<double>::iterator toDelete = it;
					++it;
					keyValues.erase(toDelete);
				}
				else
				{
					++it;
				}
		}
      }
		else
		{
			//convert actual display range to log scale
			//(we can't do the opposite, otherwise we get accuracy/round-off issues!)
			ScalarType dispMin = sf->displayRange().start();
			ScalarType dispMax = sf->displayRange().stop();
			ConvertToLogScale(dispMin,dispMax);

			for (std::set<double>::iterator it = keyValues.begin(); it != keyValues.end(); )
			{
				if (*it >= dispMin && *it <= dispMax)
				{
					++it;
				}
				else
				{
					std::set<double>::iterator toDelete = it;
					++it;
					keyValues.erase(toDelete);
				}
			}
		}
	}

	//Font metrics for proper display of labels!
	QFontMetrics strMetrics(win->font());

	//default color: text color
	const unsigned char* textColor = ccGui::Parameters().textDefaultCol;

	//histogram?
	const::ccScalarField::Histogram histogram = sf->getHistogram();
	bool showHistogram = (ccGui::Parameters().colorScaleShowHistogram && !logScale && histogram.maxValue != 0 && histogram.size() > 1);

	//display area
	const int strHeight = strMetrics.height();
	const int scaleWidth = ccGui::Parameters().colorScaleRampWidth;
	const int scaleMaxHeight = (keyValues.size() > 1 ? std::max(context.glH-120,2*strHeight) : scaleWidth); //if 1 value --> we draw a cube

	//centered orthoprojective view (-halfW,-halfH,halfW,halfH)
	int halfW = (context.glW>>1);
	int halfH = (context.glH>>1);

	//top-right corner of the scale ramp
	const int xShift = 20 + (showHistogram ? scaleWidth/2 : 0);
	const int yShift = halfH-scaleMaxHeight/2;

	glPushAttrib(GL_LINE_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_LINE_SMOOTH);
	glDisable(GL_DEPTH_TEST);
	
	std::vector<double> sortedKeyValues(keyValues.begin(),keyValues.end());
	double maxRange = sortedKeyValues.back()-sortedKeyValues.front();

	//const colorType* lineColor = ccColor::white;
	////clear background?
	//if (ccGui::Parameters().backgroundCol[0] + ccGui::Parameters().backgroundCol[1] + ccGui::Parameters().backgroundCol[2] > 3*128)
	//	lineColor = ccColor::black;
	const colorType* lineColor = textColor;

	//display color ramp
	{
		//(x,y): current display area coordinates (top-left corner)
		int x = halfW-xShift-scaleWidth;
		int y = halfH-yShift-scaleMaxHeight;

		if (keyValues.size() > 1)
		{
			int histoStart = x+scaleWidth+std::min(std::max(scaleWidth/8,3),15);

			glLineWidth(1.0f);
			glBegin(GL_LINES);
			for (int j=0; j<scaleMaxHeight; ++j)
			{
				double value = sortedKeyValues.front() + ((double)j * maxRange) / (double)scaleMaxHeight;
				if (logScale)
					value = exp(value*c_log10);
				const colorType* col = sf->getColor(static_cast<ScalarType>(value));
				glColor3ubv(col ? col : ccColor::lightGrey);

				glVertex2i(x,y+j);
				glVertex2i(x+scaleWidth,y+j);
				
				if (showHistogram)
				{
					double bind = (value-(double)sf->displayRange().min())*(double)(histogram.size()-1)/(double)sf->displayRange().maxRange();
					int bin = static_cast<int>(floor(bind));
					
					double hVal = 0.0;
					if (bin >= 0 && bin < (int)histogram.size()) //in symmetrical case we can get values outside of the real SF range
					{
						hVal = (double)histogram[bin];
						if (bin+1 < (int)histogram.size())
						{
							//linear interpolation
							double alpha = bind-(double)bin;
							hVal = (1.0-alpha) * hVal + alpha * (double)histogram[bin+1];
						}
					}

					int xSpan = std::max(static_cast<int>(hVal / (double)histogram.maxValue * (double)(scaleWidth/2)),1);
					glVertex2i(histoStart,y+j);
					glVertex2i(histoStart+xSpan,y+j);
				}
			}
			glEnd();
		}
		else
		{
			//if there's a unique (visible) scalar value, we only draw a square!
			double value = sortedKeyValues.front();
			if (logScale)
				value = exp(value*c_log10);
			const colorType* col = sf->getColor(value);
			glColor3ubv(col ? col : ccColor::lightGrey);
			glBegin(GL_POLYGON);
			glVertex2i(x,y);
			glVertex2i(x+scaleWidth,y);
			glVertex2i(x+scaleWidth,y+scaleMaxHeight-1);
			glVertex2i(x,y+scaleMaxHeight-1);
			glEnd();
		}

		//scale border
		glLineWidth(2.0);
		glColor3ubv(lineColor);
		glBegin(GL_LINE_LOOP);
		glVertex2i(x,y);
		glVertex2i(x+scaleWidth,y);
		glVertex2i(x+scaleWidth,y+scaleMaxHeight);
		glVertex2i(x,y+scaleMaxHeight);
		glEnd();
	}

	//display labels
	{
		//list of labels to draw
		vlabelSet drawnLabels;

		//add first label
		drawnLabels.push_back(vlabel(0,0,strHeight,sortedKeyValues.front()));

		if (keyValues.size() > 1)
		{
			//add last label
			drawnLabels.push_back(vlabel(scaleMaxHeight,scaleMaxHeight-strHeight,scaleMaxHeight,sortedKeyValues.back()));
		}

		//we try to display the other keyPoints (if any)
		if (keyValues.size() > 2)
		{
			assert(maxRange > 0.0);
			const int minGap = strHeight;
			for (size_t i=1; i<keyValues.size()-1; ++i)
			{
				int yScale = static_cast<int>((sortedKeyValues[i]-sortedKeyValues[0]) * (double)scaleMaxHeight / maxRange);
				vlabelPair nLabels = GetVLabelsAround(yScale,drawnLabels);

				assert(nLabels.first != drawnLabels.end() && nLabels.second != drawnLabels.end());
				if (	(nLabels.first == drawnLabels.end() || nLabels.first->yMax <= yScale - minGap)
					&&	(nLabels.second == drawnLabels.end() || nLabels.second->yMin >= yScale + minGap))
				{
					//insert it at the right place (so as to keep a sorted list!)
					drawnLabels.insert(nLabels.second,vlabel(yScale,yScale-strHeight/2,yScale+strHeight/2,sortedKeyValues[i]));
				}
			}
		}

		//now we recursively display labels where we have some rool left
		if (drawnLabels.size() > 1)
		{
			const int minGap = strHeight*2;

			size_t drawnLabelsBefore = 0; //just to init the loop
			size_t drawnLabelsAfter = drawnLabels.size(); 

			//proceed until no more label can be inserted
			while (drawnLabelsAfter > drawnLabelsBefore)
			{
				drawnLabelsBefore = drawnLabelsAfter;

				vlabelSet::iterator it1 = drawnLabels.begin();
				vlabelSet::iterator it2 = it1; it2++;
				for (; it2 != drawnLabels.end(); ++it2)
				{
					if (it1->yMax + 2*minGap < it2->yMin)
					{
						//insert label
						double val = (it1->val + it2->val)/2.0;
						int yScale = static_cast<int>((val-sortedKeyValues[0]) * (double)scaleMaxHeight / maxRange);

						//insert it at the right place (so as to keep a sorted list!)
						drawnLabels.insert(it2,vlabel(yScale,yScale-strHeight/2,yScale+strHeight/2,val));
					}
					it1 = it2;
				}

				drawnLabelsAfter = drawnLabels.size();
			}
		}

		//display labels

		//Some versions of Qt seem to need glColorf instead of glColorub! (see https://bugreports.qt-project.org/browse/QTBUG-6217)
		glColor3f((float)textColor[0]/255.0f,(float)textColor[1]/255.0f,(float)textColor[2]/255.0f);

		//Scalar field name
		const char* sfName = sf->getName();
		if (sfName)
		{
			//QString sfTitle = QString("[%1]").arg(sfName);
			QString sfTitle(sfName);
			if (logScale)
				sfTitle += QString("[Log scale]");
			//we leave some (vertical) space for the top-most label!
			win->displayText(sfTitle, context.glW-xShift, context.glH-yShift+strMetrics.height(), ccGLWindow::ALIGN_HRIGHT | ccGLWindow::ALIGN_VTOP);
		}

		//precision (same as color scale)
		const unsigned precision = ccGui::Parameters().displayedNumPrecision;
		//format
		const char format = (sf->logScale() ? 'E' : 'f');
		//tick
		const int tickSize = 4;

		//for labels
		const int x = context.glW-xShift-scaleWidth-2*tickSize-1;
		const int y = context.glH-yShift-scaleMaxHeight;
		//for ticks
		const int xTick = halfW-xShift-scaleWidth-tickSize-1;
		const int yTick = halfH-yShift-scaleMaxHeight;

		for (vlabelSet::iterator it = drawnLabels.begin(); it != drawnLabels.end(); ++it)
		{
			vlabelSet::iterator itNext = it; itNext++;
			//position
			unsigned char align = ccGLWindow::ALIGN_HRIGHT;
			if (it == drawnLabels.begin())
				align |= ccGLWindow::ALIGN_VTOP;
			else if (itNext == drawnLabels.end())
				align |= ccGLWindow::ALIGN_VBOTTOM;
			else
				align |= ccGLWindow::ALIGN_VMIDDLE;

			double value = it->val;
			if (logScale)
				value = exp(value*c_log10);

			win->displayText(QString::number(value,format,precision), x, y+it->yPos, align);
			glBegin(GL_LINES);
			glVertex2f(xTick,yTick+it->yPos);
			glVertex2f(xTick+tickSize,yTick+it->yPos);
			glEnd();
		}
	}

	glPopAttrib();

#else

	double minVal = sf->getMin();
	double minDisplayed = sf->displayRange().start();
	double minSaturation = sf->saturationRange().start();
	double maxSaturation = sf->saturationRange().stop();
	double maxDisplayed = sf->displayRange().stop();
	double maxVal = sf->getMax();

	bool strictlyPositive = (minVal >= 0);
	bool symmetricalScale = sf->symmetricalScale();
	bool logScale = sf->logScale();

	const int c_cubeSize = ccGui::Parameters().colorScaleRampWidth;
	const int c_defaultSpace = 4;

	//this vector stores the values that will be "represented" by the scale
	//they will be automatically displayed in a regular "pace"
	std::vector<ScaleElement> theScaleElements;
	std::vector<double> theCubeEquivalentDist; //to deduce its color!

	int maxNumberOfCubes = (int)(floor((float)(context.glH-120)/(float)(c_cubeSize+2*c_defaultSpace)));

	ccColorScale::Shared colorScale = context.sfColorScaleToDisplay->getColorScale();
	if (!colorScale)
	{
		assert(false);
		return;
	}
	unsigned colorRampSteps = context.sfColorScaleToDisplay->getColorRampSteps();

	//first we fill the two vectors below with scale "values"
	if (strictlyPositive || !symmetricalScale) //only positive values
	{
		bool dispZero = minDisplayed>0.0 && strictlyPositive;
		bool dispMinVal = false;//(minVal<minDisplayed);
		bool dispMinDispVal = true;
		bool dispMinSat = (minSaturation>minDisplayed && minSaturation<maxSaturation);
		bool dispMaxSat = (maxSaturation>=minSaturation && maxSaturation<maxDisplayed);
		bool dispMaxDispVal = (maxDisplayed>minDisplayed && maxDisplayed<maxVal);
		bool dispMaxVal = true;

		int addedCubes = int(dispZero) + int(dispMinVal) + int(dispMinDispVal) + int(dispMinSat) + int(dispMaxSat) + int(dispMaxDispVal) + int(dispMaxVal);

		//not enough room for display!
		if (maxNumberOfCubes < addedCubes)
			return;

		//number of cubes available for ramp display
		int numberOfCubes = std::min<int>(maxNumberOfCubes-addedCubes,colorRampSteps);

		double startValue = minVal; //we want it to be the same color as 'minVal' even if we start at '0'
		if (dispZero)
			theScaleElements.push_back(ScaleElement(0.0,true,true));

		if (dispMinVal)
		{
			//precedent cube color
			if (!theScaleElements.empty())
				theCubeEquivalentDist.push_back(startValue);

			theScaleElements.push_back(ScaleElement(minVal,true,dispMinDispVal || dispMinSat));
			startValue = minVal;
		}

		if (dispMinDispVal)
		{
			//precedent cube color
			if (!theScaleElements.empty())
				theCubeEquivalentDist.push_back(startValue);

			theScaleElements.push_back(ScaleElement(minDisplayed,true,dispMinSat));
			startValue = minDisplayed;
		}

		if (dispMinSat)
		{
			//precedent cube color
			if (!theScaleElements.empty())
				theCubeEquivalentDist.push_back(startValue);

			theScaleElements.push_back(ScaleElement(minSaturation));
			startValue = minSaturation;
		}

		//the actual color ramp
		if (numberOfCubes>0 && minSaturation<maxSaturation && minDisplayed<maxDisplayed)
		{
			double endValue = (dispMaxSat ? maxSaturation : maxDisplayed);
			double intervale = (endValue-startValue)/(double)numberOfCubes;
			double firstValue = startValue;

			if (logScale)
			{
				double endValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(endValue)));
				double startValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(startValue)));
				intervale = (endValueLog-startValueLog)/(double)numberOfCubes;
				firstValue = startValueLog;
			}

			if (intervale < ZERO_TOLERANCE)
			{
				//finally, we won't draw this ramp!
				theScaleElements.back().condensed = true;
			}
			else
			{
				if (logScale)
				{
					for (int i=0;i<numberOfCubes;++i)
					{
						double val = firstValue+intervale*static_cast<double>(i);
						double logVal = val+intervale*0.5;
						theCubeEquivalentDist.push_back(exp(logVal*log(10.0)));

						theScaleElements.push_back(ScaleElement(exp((val+intervale)*log(10.0)),true,false));
					}
				}
				else
				{
					for (int i=0;i<numberOfCubes;++i)
					{
						double val = firstValue+intervale*static_cast<double>(i);

						theCubeEquivalentDist.push_back(val+intervale*0.5);
						theScaleElements.push_back(ScaleElement(val+intervale,true,false));
					}
				}
			}
		}

		if (dispMaxSat && dispMaxDispVal)
		{
			theCubeEquivalentDist.push_back(maxSaturation);
			theScaleElements.back().condensed = true;
			theScaleElements.push_back(ScaleElement(maxDisplayed, true, true));
		}

		if ((dispMaxSat || dispMaxDispVal) && dispMaxVal)
		{
			theCubeEquivalentDist.push_back(maxVal);
			theScaleElements.back().condensed = true;
			theScaleElements.push_back(ScaleElement(maxVal));
		}
	}
	else //both positive and negative values
	{
		//TODO FIXME!!!

		//if the ramp should be symmetrical
		bool symmetry = ccGui::Parameters().colorScaleAlwaysSymmetrical;
		if (symmetry)
		{
			//we display the color ramp between -maxDisp and +maxDisp
			double maxDisp = std::max(-minVal,maxVal);

			bool dispZero = true;
			bool dispMinSat = (minSaturation>0.0);
			bool dispMaxSat = (maxSaturation>minSaturation && maxSaturation<maxDisp);
			bool dispMaxVal = true;

			int addedCubes = 2 * (int(dispZero && dispMinSat) + int(dispMaxSat && dispMaxVal));

			//not enough room for display!
			if (maxNumberOfCubes < addedCubes)
				return;

			//number of cubes available for ramp display
			int numberOfCubes = std::min<int>((maxNumberOfCubes-addedCubes)/2,colorRampSteps);

			//1st section: -maxDisp
			double startValue = -maxDisp;
			if (dispMaxVal)
				theScaleElements.push_back(ScaleElement(-maxDisp,true,dispMaxSat));

			//2nd section: -maxSaturation
			if (dispMaxSat)
			{
				//precedent cube color
				if (!theScaleElements.empty())
					theCubeEquivalentDist.push_back(startValue);
				theScaleElements.push_back(ScaleElement(-maxSaturation));
				startValue = -maxSaturation;
			}

			//3rd section: the real color ramp (negative part)
			if (numberOfCubes>1)
			{
				double endValue = (dispMinSat ? -minSaturation : 0.0);
				double intervale = (endValue-startValue)/(double)numberOfCubes;
				double firstValue = startValue;

				if (logScale)
				{
					double endValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(-endValue)));
					double startValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(-startValue)));
					intervale = -(endValueLog-startValueLog)/(double)numberOfCubes;
					firstValue = startValueLog;
				}

				if (intervale < ZERO_TOLERANCE)
				{
					//finally, we won't draw this ramp!
					theScaleElements.back().condensed = true;
				}
				else
				{
					if (logScale)
					{
						for (int i=0;i<numberOfCubes-1;++i)
						{
							double logVal = firstValue-intervale*0.5;
							theCubeEquivalentDist.push_back(-exp(logVal*log(10.0)));
							firstValue -= intervale;
							//if (i==0 && firstValue>maxVal) //specific case: all values in the tail
							//	theScaleElements.push_back(ScaleElement(maxVal,true));
							//else
								theScaleElements.push_back(ScaleElement(-exp(firstValue*log(10.0)),true));
						}
					}
					else
					{
						for (int i=0;i<numberOfCubes-1;++i)
						{
							theCubeEquivalentDist.push_back(firstValue + intervale*0.5);
							firstValue += intervale;
							//if (i==0 && firstValue>maxVal) //specific case: all values in the tail
							//	theScaleElements.push_back(ScaleElement(maxVal,true));
							//else
								theScaleElements.push_back(ScaleElement(firstValue,true));
						}
					}
				}
			}

			//4th section: -minSaturation
			if (dispMinSat)
			{
				theCubeEquivalentDist.push_back(-minSaturation);
				theScaleElements.push_back(ScaleElement(-minSaturation, true, true));
			}

			//5th section: zero
			if (dispZero)
			{
				theCubeEquivalentDist.push_back(0.5*theCubeEquivalentDist.back());
				theScaleElements.push_back(ScaleElement(0, true, dispMinSat));
			}

			//6th section: minSaturation
			if (dispMinSat)
			{
				theCubeEquivalentDist.push_back(0.0);
				theScaleElements.push_back(ScaleElement(minSaturation));
			}

			//7th section: the real color ramp (positive part)
			if (numberOfCubes>1)
			{
				double intervale = (maxSaturation-minSaturation)/(double)numberOfCubes;
				double firstValue = minSaturation;

				if (logScale)
				{
					double endValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(maxSaturation)));
					double startValueLog = log10(std::max<double>(ZERO_TOLERANCE,fabs(minSaturation)));
					intervale = (endValueLog-startValueLog)/(double)numberOfCubes;
					firstValue = startValueLog;
				}

				if (intervale < ZERO_TOLERANCE)
				{
					//finally, we won't draw this ramp!
					theScaleElements.back().condensed = true;
				}
				else
				{
					if (logScale)
					{
						for (int i=0;i<numberOfCubes-1;++i)
						{
							double logVal = firstValue+intervale*0.5;
							theCubeEquivalentDist.push_back(exp(logVal*log(10.0)));
							firstValue += intervale;
							//if (i+2==numberOfCubes && firstValue<minVal) //specific case: all values in the head
							//	theScaleElements.push_back(ScaleElement(minVal,true));
							//else
								theScaleElements.push_back(ScaleElement(exp(firstValue*log(10.0)),true));
						}
					}
					else
					{
						for (int i=0;i<numberOfCubes-1;++i)
						{
							theCubeEquivalentDist.push_back(firstValue + intervale*0.5);
							firstValue += intervale;
							//if (i+2==numberOfCubes && firstValue<minVal) //specific case: all values in the head
							//	theScaleElements.push_back(ScaleElement(minVal,true));
							//else
								theScaleElements.push_back(ScaleElement(firstValue,true));
						}
					}
				}
			}

			//8th section: maxSaturation
			if (dispMaxSat)
			{
				theCubeEquivalentDist.push_back(maxSaturation);
				theScaleElements.push_back(ScaleElement(maxSaturation,true,true));
			}

			//9th section: maxVal
			if (dispMaxVal)
			{
				theCubeEquivalentDist.push_back(maxDisp);
				theScaleElements.push_back(ScaleElement(maxDisp));
			}
		}
		else
		{
			//TODO
		}
	}

	if (theScaleElements.empty())
		return;

	//scale height
	unsigned n = (unsigned)theScaleElements.size();
	//assert(theCubeEquivalentDist.size()+(dispZero ? 1 : 0)==n);
	int scaleHeight = (c_cubeSize+2*c_defaultSpace)*n;

	const int xShift = c_cubeSize+20;
	const int yShift = 40;

	//centered orthoprojective view (-halfW,-halfH,halfW,halfH)
	int halfW = (context.glW>>1);
	int halfH = (context.glH>>1);

	/*** now we can render the scale ***/

	//(x,y): current display area coordinates
	int x = halfW-xShift;
	int y = yShift-scaleHeight/2;

	//first horizontal delimiter
	glBegin(GL_LINES);
	glVertex2i(x,y);
	glVertex2i(x+c_cubeSize,y);
	glEnd();

	ccGLWindow* win = (ccGLWindow*)context._win;
	assert(win);

	if (theScaleElements[0].textDisplayed)
		win->displayText(QString::number(theScaleElements[0].value, logScale ? 'E' : 'f', ccGui::Parameters().displayedNumPrecision), halfW+x-5, y+halfH, ccGLWindow::ALIGN_HRIGHT | ccGLWindow::ALIGN_VMIDDLE);

	const colorType* lineColor = ccColor::white;
	//clear background?
	if (ccGui::Parameters().backgroundCol[0] + ccGui::Parameters().backgroundCol[1] + ccGui::Parameters().backgroundCol[2] > 3*128)
		lineColor = ccColor::black;

	for (int i=0;i+1<(int)n;++i)
	{
		y += c_defaultSpace;

		//a colored cube
		//d = 0.5*(theScaleElements[i].value + theScaleElements[i+1].value);
		double d = theCubeEquivalentDist[i];

		const colorType* col = sf->getColor(d);

		if (i==0 && theScaleElements[i].condensed)
		{
			//DOWN ARROW

			glBegin(GL_LINE_LOOP);
			glColor3ubv(lineColor);
			glVertex2i(x,y+c_cubeSize);
			glVertex2i(x+c_cubeSize,y+c_cubeSize);
			glVertex2i(x+c_cubeSize/2,y);
			glEnd();

			if (col)
			{
				glBegin(GL_POLYGON);
				glColor3ubv(col);
				glVertex2i(x,y+c_cubeSize);
				glVertex2i(x+c_cubeSize,y+c_cubeSize);
				glVertex2i(x+c_cubeSize/2,y);
				glEnd();
			}
		}
		else if (i+2 == (int)n && theScaleElements[i].condensed)
		{
			//UP ARROW

			glBegin(GL_LINE_LOOP);
			glColor3ubv(lineColor);
			glVertex2i(x,y);
			glVertex2i(x+c_cubeSize,y);
			glVertex2i(x+c_cubeSize/2,y+c_cubeSize);
			glEnd();

			if (col)
			{
				glBegin(GL_POLYGON);
				glColor3ubv(col);
				glVertex2i(x,y+1);
				glVertex2i(x+c_cubeSize,y+1);
				glVertex2i(x+c_cubeSize/2,y+c_cubeSize);
				glEnd();
			}
		}
		else //RECTANGLE
		{
			if (!theScaleElements[i].condensed)
			{
				//simple box
				if (col)
				{
					glBegin(GL_POLYGON);
					glColor3ubv(col);
					glVertex2i(x,y);
					glVertex2i(x+c_cubeSize,y);
					glVertex2i(x+c_cubeSize,y+c_cubeSize);
					glVertex2i(x,y+c_cubeSize);
					glEnd();
				}

				glBegin(GL_LINE_LOOP);
				glColor3ubv(lineColor);
				glVertex2i(x,y);
				glVertex2i(x+c_cubeSize,y);
				glVertex2i(x+c_cubeSize,y+c_cubeSize);
				glVertex2i(x,y+c_cubeSize);
				glEnd();
			}
			else
			{
				float third = (float)c_cubeSize *0.8/3.0f;
				//slashed box
				if (col)
				{
					glColor3ubv(col);

					glBegin(GL_POLYGON);
					glVertex2i(x,y);
					glVertex2f(x,(float)y+third);
					glVertex2f(x+c_cubeSize,(float)y+2.0f*third);
					glVertex2i(x+c_cubeSize,y);
					glEnd();

					glBegin(GL_POLYGON);
					glVertex2i(x,y+c_cubeSize);
					glVertex2i(x+c_cubeSize,y+c_cubeSize);
					glVertex2f(x+c_cubeSize,(float)(y+c_cubeSize)-third);
					glVertex2f(x,(float)(y+c_cubeSize)-2.0*third);
					glEnd();
				}

				glColor3ubv(lineColor);
				glBegin(GL_LINE_LOOP);
				glVertex2i(x,y);
				glVertex2f(x,(float)y+third);
				glVertex2f(x+c_cubeSize,(float)y+2.0f*third);
				glVertex2i(x+c_cubeSize,y);
				glEnd();

				glBegin(GL_LINE_LOOP);
				glVertex2i(x,y+c_cubeSize);
				glVertex2i(x+c_cubeSize,y+c_cubeSize);
				glVertex2f(x+c_cubeSize,(float)(y+c_cubeSize)-third);
				glVertex2f(x,(float)(y+c_cubeSize)-2.0*third);
				glEnd();
			}
		}

		y += c_cubeSize+c_defaultSpace;

		//separator
		glColor3ubv(lineColor);
		glBegin(GL_LINES);
		glVertex2i(x,y);
		glVertex2i(x+c_cubeSize,y);
		glEnd();

		if (theScaleElements[i+1].textDisplayed)
		{
			double dispValue = theScaleElements[i+1].value;
			win->displayText(QString::number(dispValue,logScale ? 'E' : 'f',ccGui::Parameters().displayedNumPrecision), halfW+x-5, y+halfH, ccGLWindow::ALIGN_HRIGHT | ccGLWindow::ALIGN_VMIDDLE);
		}
	}

	//Scale title
	const char* sfName = context.sfColorScaleToDisplay->getName();
	if (sfName)
	{
		//QString sfTitle = QString("[%1]").arg(sfName);
		QString sfTitle(sfName);
		win->displayText(sfTitle, context.glW-c_cubeSize/2, (y+c_cubeSize)+halfH, ccGLWindow::ALIGN_HRIGHT | ccGLWindow::ALIGN_VTOP);
	}

#endif
}