Newer
Older
WickedDocs / ColorPicker.h
@John Ryland John Ryland on 12 May 2015 11 KB
#include <QPainter>


// Values from this come up the same as doing:   QColor::fromHsvF(hue, sat, 1.0).rgba()
uint32_t rgbFromHsvF(qreal h1, qreal s, qreal v, qreal a)
{
	uint16_t hue = h1 == qreal(-1.0) ? USHRT_MAX : qRound(h1 * 36000);
	h1 = hue == 36000 ? 0 : hue / qreal(6000.);
	s = uint16_t(qRound(s * USHRT_MAX)) / qreal(USHRT_MAX);
	v = uint16_t(qRound(v * USHRT_MAX)) / qreal(USHRT_MAX);
	const qreal h = h1;
	const int i = int(h);
	const qreal f = h - i;
	const qreal v2 = v * USHRT_MAX;
	uint8_t components[4] = {
		qRound(v2 * (qreal(1.0) - s)) >> 8,
		qRound(v2) >> 8,
		qRound(v2 * (qreal(1.0) - (s * f))) >> 8,
		qRound(v2 * (qreal(1.0) - (s * (qreal(1.0) - f)))) >> 8
	};
	const int indexTable[10] = { 1, 2, 0, 0, 3, 1, 1, 2, 0, 0 };
	return 0xff000000 | (components[indexTable[i]] << 16) | (components[indexTable[i + 4]] << 8) | components[indexTable[i + 2]];
}

bool m_wheelCached = false;
QImage colorWheelCache;

void makeColorWheel(int siz)
{
	const float twoPi = 2 * 3.142;
	const int cen = (siz / 2);
	const int rad = cen - 20;
	const int iter = siz * 10;

	QImage img(siz, siz, QImage::Format_ARGB32);
	img.fill(0xFF444444);
	uint32_t* bits = (uint32_t*)img.bits();
	int pitch = img.bytesPerLine() / sizeof(uint32_t);
	double dSat = 1.0 / rad;
	for (int i = 0; i < iter; i++)
	{
		double hue = double(i) / iter;
		double c = ::cos(hue * twoPi);
		double s = ::sin(hue * twoPi);
		double x = cen + rad * c;
		double y = cen + rad * s;
		double sat = 1.0;
		for (int w = rad-30; w < rad; w++)
		{
			bits[int(y)*pitch + int(x)] = rgbFromHsvF(hue, sat, 1.0, 1.0);
			x -= c;
			y -= s;
			//sat -= dSat;
		}
	}
	// Smoothing technique where we scale it up a fraction and then back down again
	img = img.scaled(img.size() + QSize(6, 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
	colorWheelCache = img.scaled(img.size() - QSize(6, 6), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
	m_wheelCached = true;
}


const int modeHands[8] = { 1, 2, 3, 4, 4, 4, 4, 4 };
const int modeHandAngles[6][4] = { { 0 }, { 0, 180 }, { 0, 180 - 30, 180 + 30 },
	{ 0, 360 - 30, 30, 180 },  /* { 0, 180 - 30, 180, 180 + 30 }, */ { 360 - 45, 45, 180 - 45, 180 + 45 } };
const char* modeNames[6] = {
	"Monochromatic", "Complementary", "Analogous", "Accented Analogous", "Tetradic"
};


struct ColorScheme
{
	//   mode (3-bits), primary hue (8-bits), angle / hue-delta (8-bits)
	//   saturation (8-bits), stretch (5-bits)
	unsigned    m_primaryHue : 8;
	unsigned    m_secondaryHueDelta : 8;
	unsigned    m_saturation : 8;
	unsigned    m_stretch : 5;
	unsigned    m_mode : 3;

	const char* modeName()
	{
		if (m_mode == 2 && m_secondaryHueDelta > 64 && m_secondaryHueDelta < (255 - 64))
			return "Triadic";
		if (m_mode == 3 && m_secondaryHueDelta > 64 && m_secondaryHueDelta < (255-64))
			return "Split-Complementary";
		return modeNames[m_mode];
	}
	int hueCount()
	{
		return modeHands[m_mode];
	}
	void setHue(int a_index, float a_val)
	{
		if (a_index == 0)
			m_primaryHue = unsigned(a_val * 255.0) & 0xff;
		else if (a_index == 1 && m_mode != 2)
			m_primaryHue = unsigned((a_val - 0.5) * 255.0) & 0xff;
		else if ((a_index == 2 && m_mode != 2) || (m_mode == 2 && a_index == 1))
			m_secondaryHueDelta = (unsigned(a_val * 255.0) - m_primaryHue) & 0xff;
		else if (m_mode == 3 || (m_mode == 2 && a_index == 2))
			m_secondaryHueDelta = (m_primaryHue - unsigned(a_val * 255.0)) & 0xff;
		else
			m_secondaryHueDelta = (unsigned((a_val - 0.5) * 255.0) - m_primaryHue) & 0xff;
	}
	float denormalizedHue(int a_index)
	{
		if (a_index == 0)
			return float(m_primaryHue) / 255.0;
		if (a_index == 1 && m_mode != 2)
			return 0.5 + float(m_primaryHue) / 255.0;
		if ((a_index == 2 && m_mode != 2) || (m_mode == 2 && a_index == 1))
			return float((int)m_primaryHue + m_secondaryHueDelta) / 255.0;
		if (m_mode == 3 || (m_mode == 2 && a_index == 2))
			return float((int)m_primaryHue - (int)m_secondaryHueDelta) / 255.0;
		return 0.5 + float((int)m_primaryHue + (int)m_secondaryHueDelta) / 255.0;
	}
	float hue(int a_index)
	{
		float h = denormalizedHue(a_index);
		while (h < 0.0) h += 1.0;
		while (h > 1.0) h -= 1.0;
		return h;
	}
};


static_assert(sizeof(ColorScheme) == 4, "bad size");


class SchemeSelection : public QWidget
{
public:
	const int titleHeight = 20;
	const int modes = 5;

	ColorScheme* scheme;
	SchemeSelection(QWidget* a_parent, ColorScheme* a_scheme) : QWidget(a_parent), scheme(a_scheme) {}

	void mousePressEvent(QMouseEvent* me)
	{
		for (int i = 0; i < modes; i++)
		{
			QRect r(16, titleHeight + 10 + i * 55, 50, 50);
			if (r.contains(me->pos()))
			{
				scheme->m_mode = i;
				parentWidget()->update();
				return;
			}
		}
	}

	void paintEvent(QPaintEvent*)
	{
		QPainter p(this);
		p.fillRect(0, 0, width(), titleHeight, QColor(0x444444));

		QFont f("Segoe UI", 13, 200);
		p.setFont(f);
		p.setPen(Qt::white);
		p.drawText(3 + 0, 15, "Scheme");

		QPixmap pix1("../color-dial-deselect.png");
		QPixmap pix2("../color-dial-select.png");
		QPixmap pixHand1("../color-pointer-deselect.png");
		QPixmap pixHand2("../color-pointer-select.png");
		p.fillRect(0, titleHeight, width(), height() - titleHeight, QColor(0x333333));
		for (int i = 0; i < modes; i++)
		{
			p.drawPixmap(16, titleHeight + 10 + i * 55, (i == scheme->m_mode) ? pix2 : pix1);
			QPixmap hands = (i == scheme->m_mode) ? pixHand2 : pixHand1;

			for (int j = 0; j < modeHands[i]; j++)
			{
				QMatrix m;
				m = m.scale(0.5, 0.5);
				m = m.rotate(modeHandAngles[i][j]);
				QPixmap newPix = hands.transformed(m, Qt::SmoothTransformation);
				QSize s = newPix.size() - hands.size() / 2;
				s = s / 2;
				p.drawPixmap(16 - s.width(), titleHeight + 10 - s.height() + i * 55, newPix);
				//pix.transformed(m, Qt::SmoothTransformation);
			}
		}
	}
};



//#include "c:\Users\John\Desktop\Euclideon\model-iterator.cpp"



// lineedit to name the scheme
// okay/cancel buttons
// styled titlebar

class ColorPicker : public QWidget
{
public:
	const float twoPi = 2 * acos(-1);
	const int siz = 250;
	const int cen = (siz / 2);
	const int rad = (siz / 2) - 20;
	const int iter = siz * 10;
	const int dotSiz = 18;// 16;
	const int dotAlpha = 96;// 64;

	const int titleHeight = 20;

	const int schemeWidth = 80;
	const int wheelWidth = siz * 2;// 600;

	const int paletteWidth = 150;
	const int paletteHeight = 350;

	const int xOff = schemeWidth;
	const int yOff = titleHeight;
	//const int modes = 6;

	int m_hueMovingIdx;

	
	ColorScheme palette;
	SchemeSelection scheme;


	// Properties of a scheme:
	//   mode (3-bits), primary hue (8-bits), angle / hue-delta (8-bits)
	//   saturation (8-bits), stretch (5-bits)

	ColorPicker(QWidget* parent = 0) : QWidget(parent), scheme(this, &palette)
	{
		palette.m_mode = 0;
		palette.m_primaryHue = 0;
		palette.m_secondaryHueDelta = 10;
		palette.m_saturation = 0;
		palette.m_stretch = 0;
		scheme.setGeometry(0, 0, schemeWidth, paletteHeight + titleHeight);

		setFixedSize(schemeWidth + wheelWidth + paletteWidth, paletteHeight + titleHeight);
		m_hueMovingIdx = -1;
	}
	~ColorPicker()
	{
	}

	void mousePressEvent(QMouseEvent* me)
	{
		for (int h = palette.hueCount() - 1; h >= 0; h--)
		{
			int x = xOff + cen + (rad - dotSiz - 5) * ::cos(palette.hue(h) * twoPi);
			int y = yOff + cen + (rad - dotSiz - 5) * ::sin(palette.hue(h) * twoPi);
			QRect r(x - dotSiz / 2, y - dotSiz / 2, x + dotSiz / 2, y + dotSiz / 2);
			if (r.contains(me->pos()))
			{
				m_hueMovingIdx = h;
				return;
			}
		}
	}

	void mouseReleaseEvent(QMouseEvent*)
	{
		m_hueMovingIdx = -1;
	}

	void mouseMoveEvent(QMouseEvent* me)
	{
		if (m_hueMovingIdx != -1)
		{
			int x = me->pos().x() - (cen + xOff);
			int y = me->pos().y() - (cen + yOff);
			palette.setHue(m_hueMovingIdx, ::atan2(y, x) / twoPi);
update();
		}
	}

	void paintEvent(QPaintEvent*)
	{
		QPainter p(this);

		p.fillRect(0, 0, width(), titleHeight, QColor(0x444444));
		p.fillRect(schemeWidth, 0, wheelWidth, titleHeight, QColor(0x333333));

		QFont f("Segoe UI", 13, 200);
		p.setFont(f);
		p.setPen(Qt::white);
		//p.drawText(3 + schemeWidth, 15, "Color Wheel");
		p.drawText(3 + schemeWidth + wheelWidth, 15, "Palette Preview");


		if (!m_wheelCached)
			makeColorWheel(siz);
		p.drawImage(xOff, yOff, colorWheelCache);
		//p.drawText(3 + schemeWidth, titleHeight + 15, QString(modeNames[m_mode]) + " Colors");
		p.drawText(3 + schemeWidth, 15, QString(palette.modeName()) + " Colors");

		for (int h = 0; h < palette.hueCount(); h++)
		{
			int x = xOff + cen + (rad /*- dotSiz */ - 15 - 5) * ::cos(palette.hue(h) * twoPi);
			int y = yOff + cen + (rad /*- dotSiz */ - 15 - 5) * ::sin(palette.hue(h) * twoPi);
			if (h == 0) {
				p.setPen(QPen(QColor(255, 255, 255, dotAlpha + 20), 2));
				p.setBrush(QColor(192, 192, 192, dotAlpha + 20));
			}
			else {
				p.setPen(QPen(QColor(0, 0, 0, dotAlpha - 10), 2));
				p.setBrush(QColor(0, 0, 0, dotAlpha - 10));
			}
			p.drawEllipse(x - dotSiz / 2, y - dotSiz / 2, dotSiz, dotSiz);
		}


		//p.fillRect(schemeWidth + siz, titleHeight, siz, paletteHeight, QColor(0x533333));
		QImage img(siz, siz, QImage::Format_ARGB32);
		img.fill(0xFF444444);
		uint32_t* bits = (uint32_t*)img.bits();
		int pitch = img.bytesPerLine() / sizeof(uint32_t);
		float hue = palette.hue(0); // Get the primary color
		for (int j = 0; j < (siz - 120); j++)
		{
			for (int i = 0; i < (siz - 40); i++)
			{
				bits[int(j + 20)*pitch + int(i + 20)] = rgbFromHsvF(hue, j / float(siz - 120), i / float(siz - 40), 1.0);
			}
		}

		for (int j = 0; j < 16; j++)
		{
			for (int i = 0; i < 10; i++)
			{
				float t = i / float(9);
				for (int x = 0; x < 16; x++)
				{
					// shades, tones and tints   (black,grey,white)
					bits[int(j + siz - 80 + 10)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0, 1.0 - t, 1.0); // shades
					bits[int(j + siz - 80 + 30)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0 - t, 1.0 - (t*0.6), 1.0); // tones
					bits[int(j + siz - 80 + 50)*pitch + int(i * 20 + x + 25)] = rgbFromHsvF(hue, 1.0 - t, 1.0, 1.0); // tints
				}
			}
		}
		p.drawImage(schemeWidth + siz, titleHeight, img);


		p.fillRect(schemeWidth + wheelWidth, titleHeight, paletteWidth, paletteHeight, QColor(0x333333));
		for (int h = 0; h < palette.hueCount(); h++)
		{
			QColor c = QColor::fromHsvF(palette.hue(h), 1.0, 1.0);
			p.setPen(QPen(Qt::white));
			p.setBrush(c);
			// p.drawRect(schemeWidth + siz + 20 + 60 * (h % 2), titleHeight + 20 + 60 * (h / 2), 50, 50);
			p.drawRect(schemeWidth + wheelWidth + 20, titleHeight + 20 + 60 * h, 50, 50);

			QFont f("Segoe UI", 12, 175);
			p.setFont(f);
			QString x = QVariant(c.toRgb()).toString();
			//QString y = QString("%1,%2,%3,%4").arg(c.cyan()).arg(c.magenta()).arg(c.yellow()).arg(c.black());
			p.drawText(schemeWidth + wheelWidth + 20 + 60, titleHeight + 20 + 60 * h + 16, x);// "#FF123456");
			//p.drawText(schemeWidth + siz + 20 + 60, titleHeight + 20 + 60 * h + 16 + 25, y);// "10,20,40,10");
		}

		int colIdxForMode[5][5] = { { 0, 0, 0, 0, -1 }, { 0, 0, 0, 1, -1 }, { 0, 0, 1, 2, -1 }, { 0, 2, 3, 1, -1 }, { 0, 2, 3, 1, -1 } };
		for (int i = 0; i < 4; i++)
		{
			int idx = colIdxForMode[palette.m_mode][i];
			float sats[4] = { 1.0 - 0.65, 1.0 - 0.35, 1.0, 1.0 };
			float vals[4] = { 1.0, 1.0, 1.0 - 0.35, 1.0 - 0.65 };
			float baseTone = 0.15;
			float hue = palette.hue(idx);
			QColor c = QColor::fromHsvF(hue, 1.0 - baseTone, 1.0 - (baseTone*0.6));

			int unitSiz = (((siz*2) - 30) / 16) * 4;
			int unitW = unitSiz - 4;
			int x1 = schemeWidth + 20 + unitSiz * i;
			int x2 = x1 + unitW;

			while (idx == colIdxForMode[palette.m_mode][i + 1]) {
				x2 += unitSiz;
				i++;
			}

			int w = x2 - x1;
			p.fillRect(x1, titleHeight + siz + 10, w, 50, c);
			for (int s = 0; s < 4; s++)
				p.fillRect(x1 + (w / 4) * s, titleHeight + siz + 10 + 50, w / 4, 25, rgbFromHsvF(hue, sats[s], vals[s], 1.0));

		}

	}
};