All Classes Namespaces Functions Variables Enumerations Properties Pages
colorwheel.cpp
1 /*
2 
3 Pencil2D - Traditional Animation Software
4 Copyright (C) 2012-2020 Matthew Chiawen Chang
5 
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; version 2 of the License.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 */
16 
17 #include <QVBoxLayout>
18 #include <QtMath>
19 #include <QPainter>
20 #include <QResizeEvent>
21 #include <QStyleOption>
22 #include <QRect>
23 #include <QDebug>
24 #include "pencildef.h"
25 
26 #include "colorwheel.h"
27 
28 ColorWheel::ColorWheel(QWidget* parent) : QWidget(parent)
29 {
30  setWindowTitle(tr("Color Wheel", "Color Wheel's window title"));
31  mCurrentColor = mCurrentColor.toHsv();
32  setMinimumHeight(100);
33 }
34 
35 QColor ColorWheel::color()
36 {
37  return mCurrentColor;
38 }
39 
40 void ColorWheel::setColor(QColor color)
41 {
42  // this is a UI updating function, never emit any signals
43  // and don't call any functions that will emit signals
44 
45  color = color.toHsv();
46 
47  if (color == mCurrentColor)
48  {
49  return;
50  }
51 
52  if (color.hue() == -1) // grayscale color, keep the current hue
53  {
54  color.setHsv(mCurrentColor.hue(), color.saturation(), color.value(), color.alpha());
55  }
56 
57  mCurrentColor = color;
58 
59  drawSquareImage(color.hue());
60  update();
61 }
62 
63 QColor ColorWheel::pickColor(const QPoint& point)
64 {
65  if (!mWheelPixmap.rect().contains(point))
66  {
67  return QColor();
68  }
69  if (mIsInWheel)
70  {
71  qreal hue = 0;
72  QPoint center(width() / 2, height() / 2);
73  QPoint diff = point - center;
74 
75  hue = qAtan2(-diff.y(), diff.x()) / M_PI * 180;
76  hue = fmod((hue + 360), 360); // shift -180~180 to 0~360
77 
78  //QString strDebug = "";
79  //strDebug += QString("Radius=%1").arg(r);
80  //strDebug += QString(" Atan2=%1").arg(qAtan2(diff.y(), diff.x()));
81  //strDebug += QString(" Hue=%1").arg(hue);
82  //qDebug() << strDebug;
83 
84  hue = (hue > 359) ? 359 : hue;
85  hue = (hue < 0) ? 0 : hue;
86 
87  return QColor::fromHsv(static_cast<int>(hue),
88  mCurrentColor.saturation(),
89  mCurrentColor.value());
90  }
91  else if (mIsInSquare)
92  {
93  QPointF p = point - mSquareRect.topLeft();
94  //qDebug("TopRight(%d, %d) Point(%d, %d)", rect.topRight().x(), rect.topRight().y(), point.x(), point.y());
95 
96  //qDebug("p(%d, %d), Region(%.1f, %.1f)", p.x(), p.y(), regionSize.width(), regionSize.height());
97  return QColor::fromHsvF(mCurrentColor.hueF(),
98  p.x() / (mSquareRect.width() - 1),
99  1.0 - (p.y() / (mSquareRect.height()-1)));
100  }
101  return QColor();
102 }
103 
104 void ColorWheel::mousePressEvent(QMouseEvent *event)
105 {
106  QPoint lastPos = event->pos();
107  if (mSquareRect.contains(lastPos))
108  {
109  mIsInWheel = false;
110  mIsInSquare = true;
111  QColor color = pickColor(lastPos);
112  saturationChanged(color.saturation());
113  valueChanged(color.value());
114 
115  }
116  else if (mWheelRect.contains(lastPos))
117  {
118  mIsInWheel = true;
119  mIsInSquare = false;
120  QColor color = pickColor(lastPos);
121  hueChanged(color.hue());
122  }
123 }
124 
125 void ColorWheel::mouseMoveEvent(QMouseEvent* event)
126 {
127  QPoint lastPos = event->pos();
128  if (event->buttons() == Qt::NoButton)
129  {
130  return;
131  }
132  if (mIsInSquare)
133  {
134  if (lastPos.x() < mSquareRect.topLeft().x())
135  {
136  lastPos.setX(mSquareRect.topLeft().x());
137  }
138  else if (lastPos.x() > mSquareRect.bottomRight().x())
139  {
140  lastPos.setX(mSquareRect.bottomRight().x());
141  }
142 
143  if (lastPos.y() < mSquareRect.topLeft().y())
144  {
145  lastPos.setY(mSquareRect.topLeft().y());
146  }
147  else if (lastPos.y() > mSquareRect.bottomRight().y())
148  {
149  lastPos.setY(mSquareRect.bottomRight().y());
150  }
151 
152  QColor color = pickColor(lastPos);
153  saturationChanged(color.saturation());
154  valueChanged(color.value());
155  }
156  else if (mWheelRect.contains(lastPos) && mIsInWheel)
157  {
158  QColor color = pickColor(lastPos);
159  hueChanged(color.hue());
160  }
161 }
162 
163 void ColorWheel::mouseReleaseEvent(QMouseEvent *)
164 {
165  mIsInWheel = false;
166  mIsInSquare = false;
167  emit colorSelected(mCurrentColor);
168 }
169 
170 void ColorWheel::resizeEvent(QResizeEvent* event)
171 {
172  mWheelPixmap = QPixmap(event->size());
173  mWheelPixmap.fill(palette().window().color());
174  drawWheelImage(event->size());
175  drawSquareImage(mCurrentColor.hue());
176 
177  update();
178 }
179 
180 void ColorWheel::paintEvent(QPaintEvent*)
181 {
182  QPainter painter;
183 
184  painter.begin(this);
185  QStyleOption opt;
186  opt.initFrom(this);
187 
188  composeWheel(mWheelPixmap);
189  painter.translate(width() / 2, height() / 2);
190  painter.translate(-mWheelPixmap.width() / 2, -mWheelPixmap.height() / 2);
191  painter.drawPixmap(0, 0, mWheelPixmap);
192 
193  style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
194 }
195 
196 void ColorWheel::drawWheelImage(const QSize &newSize)
197 {
198  int r = qMin(newSize.width(), newSize.height());
199 
200  QStyleOption option;
201  option.initFrom(this);
202 
203  QBrush backgroundBrush = option.palette.window();
204 
205  mWheelImage = QImage(newSize, QImage::Format_ARGB32_Premultiplied);
206 
207  QPainter painter(&mWheelImage);
209 
210  painter.fillRect(mWheelImage.rect(), backgroundBrush);
211 
212  QConicalGradient conicalGradient(0, 0, 0);
213 
214  for (int hue = 0; hue < 360; hue += 1)
215  {
216  conicalGradient.setColorAt(hue / 360.0, QColor::fromHsv(hue, 255, 255));
217  }
218 
219  qreal ir = r - mWheelThickness;
220 
221  /* outer circle */
222  painter.translate(width() / 2, height() / 2);
223 
224  QBrush brush(conicalGradient);
225  painter.setPen(Qt::NoPen);
226  painter.setBrush(brush);
227  painter.drawEllipse(QPoint(0, 0), r / 2, r / 2);
228 
229  /* inner circle */
230  painter.setBrush(backgroundBrush);
231  painter.drawEllipse(QPoint(0, 0), r / 2 - mWheelThickness, r / 2 - mWheelThickness);
232 
233  // Center of wheel
234  qreal m1 = (mWheelPixmap.width() / 2) - (ir / qSqrt(2));
235  qreal m2 = (mWheelPixmap.height() / 2) - (ir / qSqrt(2));
236 
237  // Calculate size of wheel width
238  qreal wheelWidth = 2 * ir / qSqrt(2);
239 
240  // Calculate wheel region
241  mWheelRect = QRectF(m1, m2, wheelWidth, wheelWidth).toAlignedRect();
242 }
243 
244 void ColorWheel::drawSquareImage(const int &hue)
245 {
246  // region of the widget
247  int w = qMin(width(), height());
248  // radius of outer circle
249  qreal r = w / 2.0;
250  // radius of inner circle
251  qreal ir = r - mWheelThickness;
252 
253  // top left of square
254  qreal m1 = (width() / 2) - (ir / qSqrt(2.1));
255  qreal m2 = (height() / 2) - (ir / qSqrt(2.1));
256 
257  QImage square(255, 255, QImage::Format_ARGB32);
258 
259  QLinearGradient colorGradient = QLinearGradient(0, 0, square.width(), 0);
260  colorGradient.setColorAt(0, QColor(255,255,255));
261 
262  // color square should always use full value and saturation
263  colorGradient.setColorAt(1, QColor::fromHsv(hue, 255, 255));
264 
265  QLinearGradient blackGradient = QLinearGradient(0, 0, 0, square.height());
266  blackGradient.setColorAt(0, QColor(0,0,0,0));
267  blackGradient.setColorAt(1, QColor(0,0,0,255));
268 
269  QBrush colorGradiantBrush = QBrush(colorGradient);
270  QBrush blackGradiantBrush = QBrush(blackGradient);
271 
272  QPainter painter(&square);
273 
274  painter.fillRect(square.rect(), colorGradiantBrush);
275  painter.fillRect(square.rect(), blackGradiantBrush);
276 
277  qreal SquareWidth = 2 * ir / qSqrt(2.1);
278  mSquareRect = QRectF(m1, m2, SquareWidth, SquareWidth).toAlignedRect();
279  mSquareImage = square.scaled(mSquareRect.size());
280 
281 }
282 
283 void ColorWheel::drawHueIndicator(const int &hue)
284 {
285  QPainter painter(&mWheelPixmap);
287  if (hue > 20 && hue < 200)
288  {
289  painter.setPen(Qt::black);
290  }
291  else
292  {
293  painter.setPen(Qt::white);
294  }
295  painter.setBrush(Qt::NoBrush);
296 
297  QPen pen = painter.pen();
298  pen.setWidth(3);
299  painter.setPen(pen);
300  qreal r = qMin(height(), width());
301  painter.translate(width() / 2, height() / 2);
302  painter.rotate(-hue);
303 
304  r = r / 2.0 - mWheelThickness / 2;
305  painter.drawEllipse(QPointF(r, 0), 7, 7);
306 }
307 
308 void ColorWheel::drawPicker(const QColor& color)
309 {
310  QPainter painter(&mWheelPixmap);
312  int ellipseSize = 9;
313 
314  QPoint squareTopLeft = mSquareRect.topLeft();
315  QSize squareSize = mSquareRect.size();
316 
317  qreal S = color.hsvSaturationF() * (squareSize.width()-1);
318  qreal V = (squareSize.height() - (color.valueF() * squareSize.height()-1));
319 
320  QPen pen;
321  pen.setWidth(1);
322  if (color.hsvSaturation() > 30 || color.value() < 50)
323  {
324  pen.setColor(Qt::white);
325  }
326  painter.setPen(pen);
327 
328  QTransform transform;
329  transform.translate(-ellipseSize/2,-ellipseSize/2);
330  transform.translate(squareTopLeft.x(),squareTopLeft.y()-1);
331  painter.setTransform(transform);
332  painter.drawEllipse(static_cast<int>(S), static_cast<int>(V), ellipseSize, ellipseSize);
333 }
334 
335 void ColorWheel::composeWheel(QPixmap& pixmap)
336 {
337  QPainter composePainter(&pixmap);
338  composePainter.drawImage(0, 0, mWheelImage);
339  composePainter.drawImage(mSquareRect, mSquareImage);
340  composePainter.end();
341  drawHueIndicator(mCurrentColor.hsvHue());
342  drawPicker(mCurrentColor);
343 }
344 
345 void ColorWheel::hueChanged(const int &hue)
346 {
347  if (hue < 0 || hue > 359)
348  {
349  return;
350  }
351  int s = mCurrentColor.hsvSaturation();
352  int v = mCurrentColor.value();
353  int a = mCurrentColor.alpha();
354 
355  mCurrentColor.setHsv(hue, s, v, a);
356 
357  if (!isVisible())
358  {
359  return;
360  }
361 
362  drawSquareImage(hue);
363 
364  update();
365  emit colorChanged(mCurrentColor);
366 }
367 
368 void ColorWheel::saturationChanged(const int &sat)
369 {
370  int hue = mCurrentColor.hsvHue();
371  int value = mCurrentColor.value();
372  int alpha = mCurrentColor.alpha();
373 
374  mCurrentColor.setHsv(hue, sat, value, alpha);
375 
376  update();
377  emit colorChanged(mCurrentColor);
378 }
379 
380 void ColorWheel::valueChanged(const int &value)
381 {
382  int hue = mCurrentColor.hsvHue();
383  int sat = mCurrentColor.hsvSaturation();
384  int alpha = mCurrentColor.alpha();
385  mCurrentColor.setHsv(hue, sat, value, alpha);
386 
387  update();
388  emit colorChanged(mCurrentColor);
389 }
void setTransform(const QTransform &transform, bool combine)
QSize size() const const
const QPalette & palette() const const
int width() const const
int width() const const
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setRenderHint(QPainter::RenderHint hint, bool on)
Format_ARGB32_Premultiplied
void fill(const QColor &color)
QWidget * window() const const
void setColorAt(qreal position, const QColor &color)
int value() const const
QStyle * style() const const
QPoint bottomRight() const const
bool isVisible() const const
NoButton
int height() const const
Qt::MouseButtons buttons() const const
void rotate(qreal angle)
int hue() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
void update()
int x() const const
int y() const const
QColor toHsv() const const
QColor fromHsv(int h, int s, int v, int a)
void initFrom(const QWidget *widget)
int width() const const
QTransform & translate(qreal dx, qreal dy)
qreal x() const const
qreal y() const const
void setPen(const QColor &color)
void drawEllipse(const QRectF &rectangle)
qreal valueF() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QRect rect() const const
QTextStream & center(QTextStream &stream)
void setBrush(const QBrush &brush)
void setColor(const QColor &color)
int alpha() const const
const QSize & size() const const
int height() const const
bool contains(const QRect &rectangle, bool proper) const const
qreal hueF() const const
int width() const const
void setWidth(int width)
qreal hsvSaturationF() const const
void setWindowTitle(const QString &)
int height() const const
QPoint topLeft() const const
void setMinimumHeight(int minh)
QRect toAlignedRect() const const
void setX(int x)
void setY(int y)
void translate(const QPointF &offset)
virtual void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const =0
void setHsv(int h, int s, int v, int a)
int hsvHue() const const
int saturation() const const
bool begin(QPaintDevice *device)
const QPen & pen() const const
QRect rect() const const
int height() const const
QImage scaled(int width, int height, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QColor fromHsvF(qreal h, qreal s, qreal v, qreal a)
int hsvSaturation() const const