#include <QtGui>
#include "ColorPicker.h"
static const int modeHands[8] = {
1, 2, 3, 4, 4, 4, 4, 4
};
static 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 }
};
static const char* modeNames[6] = {
"Monochromatic", "Complementary", "Analogous", "Accented Analogous", "Tetradic"
};
const char* ColorScheme::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 ColorScheme::hueCount()
{
return modeHands[m_mode];
}
void ColorScheme::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(ColorScheme a_scheme, int a_index)
{
if (a_index == 0)
return float(a_scheme.m_primaryHue) / 255.0;
if (a_index == 1 && a_scheme.m_mode != 2)
return 0.5 + float(a_scheme.m_primaryHue) / 255.0;
if ((a_index == 2 && a_scheme.m_mode != 2) || (a_scheme.m_mode == 2 && a_index == 1))
return float((int)a_scheme.m_primaryHue + a_scheme.m_secondaryHueDelta) / 255.0;
if (a_scheme.m_mode == 3 || (a_scheme.m_mode == 2 && a_index == 2))
return float((int)a_scheme.m_primaryHue - (int)a_scheme.m_secondaryHueDelta) / 255.0;
return 0.5 + float((int)a_scheme.m_primaryHue + (int)a_scheme.m_secondaryHueDelta) / 255.0;
}
float ColorScheme::hue(int a_index)
{
float h = denormalizedHue(*this, a_index);
while (h < 0.0) h += 1.0;
while (h > 1.0) h -= 1.0;
return h;
}
// 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] = {
uint8_t(qRound(v2 * (qreal(1.0) - s)) >> 8),
uint8_t(qRound(v2) >> 8),
uint8_t(qRound(v2 * (qreal(1.0) - (s * f))) >> 8),
uint8_t(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, unsigned bgColor)
{
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(0xFF000000 | bgColor);
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;
// Enable this if wanting to vary the saturation out from the center
//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;
}
static const int siz = 250;
static const int cen = (siz / 2);
static const int rad = (siz / 2) - 20;
static const int iter = siz * 10;
static const int dotSiz = 18;// 16;
static const int dotAlpha = 96;// 64;
static const int titleHeight = 35;
static const int schemeWidth = 80;
static const int wheelWidth = siz * 2;// 600;
static const int paletteWidth = 150;
static const int paletteHeight = 350;
static const int xOff = schemeWidth;
static const int yOff = titleHeight;
class ModeButton : public QPushButton
{
public:
ModeButton(QWidget* a_parent, int a_mode)
: QPushButton(a_parent)
, m_mode(a_mode)
{
setAutoExclusive(true);
setCheckable(true);
setFlat(true);
setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
setMinimumSize(45,45);
}
void paintEvent(QPaintEvent*)
{
QPainter p(this);
QPixmap pix1("../color-dial-deselect.png");
QPixmap pix2("../color-dial-select.png");
QPixmap pixHand1("../color-pointer-deselect.png");
QPixmap pixHand2("../color-pointer-select.png");
bool checked = isChecked();
int xoff = (width() - 55) / 2;
int yoff = (height() - 55) / 2;
p.drawPixmap(xoff, yoff, (checked) ? pix2 : pix1);
QPixmap hands = (checked) ? pixHand2 : pixHand1;
for (int j = 0; j < modeHands[m_mode]; j++)
{
QMatrix m;
m = m.scale(0.5, 0.5);
m = m.rotate(modeHandAngles[m_mode][j]);
QPixmap newPix = hands.transformed(m, Qt::SmoothTransformation);
QSize s = newPix.size() - hands.size() / 2;
s = s / 2;
p.drawPixmap(xoff - s.width(), yoff - s.height(), newPix);
}
}
private:
int m_mode;
};
SchemeSelection::SchemeSelection(QWidget* a_parent) : QWidget(a_parent)
{
QVBoxLayout* vbox = new QVBoxLayout(this);
QSignalMapper* signalMapper = new QSignalMapper(this);
connect(signalMapper, SIGNAL(mapped(int)), a_parent, SLOT(setMode(int)));
for (int i = 0; i < ColorScheme::modes; i++) {
modeButtons[i] = new ModeButton(this, i);
connect(modeButtons[i], SIGNAL(clicked()), signalMapper, SLOT(map()));
signalMapper->setMapping(modeButtons[i], i);
vbox->addWidget(modeButtons[i]);
}
modeButtons[0]->setChecked(true);
setLayout(vbox);
vbox->setMargin(0);
vbox->setSpacing(0);
}
// lineedit to name the scheme
// okay/cancel buttons
// styled titlebar
const unsigned bgColor1 = 0x444444;
const unsigned bgColor2 = 0x888888;
const int lineHeight = 50;
// Properties of a scheme:
// mode (3-bits), primary hue (8-bits), angle / hue-delta (8-bits)
// saturation (8-bits), stretch (5-bits)
ColorPicker::ColorPicker(QWidget* parent) : QDialog(parent), scheme(this)
{
setModal(true);
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 + lineHeight);
m_hueMovingIdx = -1;
QPushButton* okyBut = new QPushButton("Okay", this);
QPushButton* canBut = new QPushButton("Cancel", this);
okyBut->setGeometry(width() - 120, height() - 90, 100, 30);
canBut->setGeometry(width() - 120, height() - 50, 100, 30);
QLabel* nameLab = new QLabel("Palette's name: ", this);
nameLab->setGeometry(10, height() - 50, 100, 30);
QLineEdit* name = new QLineEdit(this);
name->setGeometry(120, height() - 50, width() - 120 - 140, 30);
}
ColorPicker::~ColorPicker()
{
}
void ColorPicker::setMode(int a_mode)
{
palette.m_mode = a_mode;
update();
}
void ColorPicker::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 ColorPicker::mouseReleaseEvent(QMouseEvent*)
{
m_hueMovingIdx = -1;
}
void ColorPicker::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 ColorPicker::paintEvent(QPaintEvent*)
{
QPainter p(this);
p.fillRect(0, 0, width(), height(), QColor(bgColor1));
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 + titleHeight, "Palette Preview");
if (!m_wheelCached)
makeColorWheel(siz, bgColor2);
p.drawImage(xOff, yOff, colorWheelCache);
f.setPixelSize(32);
p.setFont(f);
p.drawText(3 + schemeWidth, 25, QString(palette.modeName()) + " Color Scheme");
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);
}
QImage img(siz, siz, QImage::Format_ARGB32);
img.fill(0xFF000000 | bgColor2);
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);
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 + wheelWidth + 20, titleHeight + 16 + 20 + 60 * h, 50, 50);
QFont f("Segoe UI", 12, 175);
p.setFont(f);
QString x = QVariant(c.toRgb()).toString();
p.drawText(schemeWidth + wheelWidth + 20 + 60, titleHeight + 16 + 20 + 60 * h + 16, x);
}
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));
}
}