00001
00022 #include "color_wheel.hpp"
00023
00024 #include <cmath>
00025 #include <QMouseEvent>
00026 #include <QPainter>
00027 #include <QLineF>
00028 #include <QDragEnterEvent>
00029 #include <QMimeData>
00030 #include "color_utils.hpp"
00031
00032 namespace color_widgets {
00033
00034 enum MouseStatus
00035 {
00036 Nothing,
00037 DragCircle,
00038 DragSquare
00039 };
00040
00041 static const ColorWheel::DisplayFlags hard_default_flags = ColorWheel::SHAPE_TRIANGLE|ColorWheel::ANGLE_ROTATING|ColorWheel::COLOR_HSV;
00042 static ColorWheel::DisplayFlags default_flags = hard_default_flags;
00043 static const double selector_radius = 6;
00044
00045 class ColorWheel::Private
00046 {
00047 private:
00048 ColorWheel * const w;
00049
00050 public:
00051 qreal hue, sat, val;
00052 unsigned int wheel_width;
00053 MouseStatus mouse_status;
00054 QPixmap hue_ring;
00055 QImage inner_selector;
00056 DisplayFlags display_flags;
00057 QColor (*color_from)(qreal,qreal,qreal,qreal);
00058 QColor (*rainbow_from_hue)(qreal);
00059 int max_size = 128;
00060
00061 Private(ColorWheel *widget)
00062 : w(widget), hue(0), sat(0), val(0),
00063 wheel_width(20), mouse_status(Nothing),
00064 display_flags(FLAGS_DEFAULT),
00065 color_from(&QColor::fromHsvF), rainbow_from_hue(&detail::rainbow_hsv)
00066 { }
00067
00069 qreal outer_radius() const
00070 {
00071 return qMin(w->geometry().width(), w->geometry().height())/2;
00072 }
00073
00075 qreal inner_radius() const
00076 {
00077 return outer_radius()-wheel_width;
00078 }
00079
00081 qreal square_size() const
00082 {
00083 return inner_radius()*qSqrt(2);
00084 }
00085
00087 qreal triangle_height() const
00088 {
00089 return inner_radius()*3/2;
00090 }
00091
00093 qreal triangle_side() const
00094 {
00095 return inner_radius()*qSqrt(3);
00096 }
00097
00099 QLineF line_to_point(const QPoint &p) const
00100 {
00101 return QLineF (w->geometry().width()/2, w->geometry().height()/2, p.x(), p.y());
00102 }
00103
00104 void render_square()
00105 {
00106 int width = qMin<int>(square_size(), max_size);
00107 QSize size(width, width);
00108 inner_selector = QImage(size, QImage::Format_RGB32);
00109
00110 for ( int y = 0; y < width; ++y )
00111 {
00112 for ( int x = 0; x < width; ++x )
00113 {
00114 inner_selector.setPixel( x, y,
00115 color_from(hue,double(x)/width,double(y)/width,1).rgb());
00116 }
00117 }
00118 }
00119
00124 void render_triangle()
00125 {
00126 QSizeF size = selector_size();
00127 if ( size.height() > max_size )
00128 size *= max_size / size.height();
00129
00130 qreal ycenter = size.height()/2;
00131 inner_selector = QImage(size.toSize(), QImage::Format_RGB32);
00132
00133 for (int x = 0; x < inner_selector.width(); x++ )
00134 {
00135 qreal pval = x / size.height();
00136 qreal slice_h = size.height() * pval;
00137 for (int y = 0; y < inner_selector.height(); y++ )
00138 {
00139 qreal ymin = ycenter-slice_h/2;
00140 qreal psat = qBound(0.0,(y-ymin)/slice_h,1.0);
00141
00142 inner_selector.setPixel(x,y,color_from(hue,psat,pval,1).rgb());
00143 }
00144 }
00145 }
00146
00148 void render_inner_selector()
00149 {
00150 if ( display_flags & ColorWheel::SHAPE_TRIANGLE )
00151 render_triangle();
00152 else
00153 render_square();
00154 }
00155
00157 QPointF selector_image_offset()
00158 {
00159 if ( display_flags & SHAPE_TRIANGLE )
00160 return QPointF(-inner_radius(),-triangle_side()/2);
00161 return QPointF(-square_size()/2,-square_size()/2);
00162 }
00163
00167 QSizeF selector_size()
00168 {
00169 if ( display_flags & SHAPE_TRIANGLE )
00170 return QSizeF(triangle_height(), triangle_side());
00171 return QSizeF(square_size(), square_size());
00172 }
00173
00174
00176 qreal selector_image_angle()
00177 {
00178 if ( display_flags & SHAPE_TRIANGLE )
00179 {
00180 if ( display_flags & ANGLE_ROTATING )
00181 return -hue*360-60;
00182 return -150;
00183 }
00184 else
00185 {
00186 if ( display_flags & ANGLE_ROTATING )
00187 return -hue*360-45;
00188 else
00189 return 180;
00190 }
00191 }
00192
00194 void render_ring()
00195 {
00196 hue_ring = QPixmap(outer_radius()*2,outer_radius()*2);
00197 hue_ring.fill(Qt::transparent);
00198 QPainter painter(&hue_ring);
00199 painter.setRenderHint(QPainter::Antialiasing);
00200 painter.setCompositionMode(QPainter::CompositionMode_Source);
00201
00202
00203 const int hue_stops = 24;
00204 QConicalGradient gradient_hue(0, 0, 0);
00205 if ( gradient_hue.stops().size() < hue_stops )
00206 {
00207 for ( double a = 0; a < 1.0; a+=1.0/(hue_stops-1) )
00208 {
00209 gradient_hue.setColorAt(a,rainbow_from_hue(a));
00210 }
00211 gradient_hue.setColorAt(1,rainbow_from_hue(0));
00212 }
00213
00214 painter.translate(outer_radius(),outer_radius());
00215
00216 painter.setPen(Qt::NoPen);
00217 painter.setBrush(QBrush(gradient_hue));
00218 painter.drawEllipse(QPointF(0,0),outer_radius(),outer_radius());
00219
00220 painter.setBrush(Qt::transparent);
00221 painter.drawEllipse(QPointF(0,0),inner_radius(),inner_radius());
00222 }
00223
00224 void set_color(const QColor& c)
00225 {
00226 if ( display_flags & ColorWheel::COLOR_HSV )
00227 {
00228 hue = qMax(0.0, c.hsvHueF());
00229 sat = c.hsvSaturationF();
00230 val = c.valueF();
00231 }
00232 else if ( display_flags & ColorWheel::COLOR_HSL )
00233 {
00234 hue = qMax(0.0, c.hueF());
00235 sat = detail::color_HSL_saturationF(c);
00236 val = detail::color_lightnessF(c);
00237 }
00238 else if ( display_flags & ColorWheel::COLOR_LCH )
00239 {
00240 hue = qMax(0.0, c.hsvHueF());
00241 sat = detail::color_chromaF(c);
00242 val = detail::color_lumaF(c);
00243 }
00244 }
00245 };
00246
00247 ColorWheel::ColorWheel(QWidget *parent) :
00248 QWidget(parent), p(new Private(this))
00249 {
00250 setDisplayFlags(FLAGS_DEFAULT);
00251 setAcceptDrops(true);
00252 }
00253
00254 ColorWheel::~ColorWheel()
00255 {
00256 delete p;
00257 }
00258
00259 QColor ColorWheel::color() const
00260 {
00261 return p->color_from(p->hue, p->sat, p->val, 1);
00262 }
00263
00264 QSize ColorWheel::sizeHint() const
00265 {
00266 return QSize(p->wheel_width*5, p->wheel_width*5);
00267 }
00268
00269 qreal ColorWheel::hue() const
00270 {
00271 if ( (p->display_flags & COLOR_LCH) && p->sat > 0.01 )
00272 return color().hueF();
00273 return p->hue;
00274 }
00275
00276 qreal ColorWheel::saturation() const
00277 {
00278 return color().hsvSaturationF();
00279 }
00280
00281 qreal ColorWheel::value() const
00282 {
00283 return color().valueF();
00284 }
00285
00286 unsigned int ColorWheel::wheelWidth() const
00287 {
00288 return p->wheel_width;
00289 }
00290
00291 void ColorWheel::setWheelWidth(unsigned int w)
00292 {
00293 p->wheel_width = w;
00294 p->render_inner_selector();
00295 update();
00296 }
00297
00298 void ColorWheel::paintEvent(QPaintEvent * )
00299 {
00300 QPainter painter(this);
00301 painter.setRenderHint(QPainter::Antialiasing);
00302 painter.translate(geometry().width()/2,geometry().height()/2);
00303
00304
00305 if(p->hue_ring.isNull())
00306 p->render_ring();
00307
00308 painter.drawPixmap(-p->outer_radius(), -p->outer_radius(), p->hue_ring);
00309
00310
00311 painter.setPen(QPen(Qt::black,3));
00312 painter.setBrush(Qt::NoBrush);
00313 QLineF ray(0, 0, p->outer_radius(), 0);
00314 ray.setAngle(p->hue*360);
00315 QPointF h1 = ray.p2();
00316 ray.setLength(p->inner_radius());
00317 QPointF h2 = ray.p2();
00318 painter.drawLine(h1,h2);
00319
00320
00321 if(p->inner_selector.isNull())
00322 p->render_inner_selector();
00323
00324 painter.rotate(p->selector_image_angle());
00325 painter.translate(p->selector_image_offset());
00326
00327 QPointF selector_position;
00328 if ( p->display_flags & SHAPE_SQUARE )
00329 {
00330 qreal side = p->square_size();
00331 selector_position = QPointF(p->sat*side, p->val*side);
00332 }
00333 else if ( p->display_flags & SHAPE_TRIANGLE )
00334 {
00335 qreal side = p->triangle_side();
00336 qreal height = p->triangle_height();
00337 qreal slice_h = side * p->val;
00338 qreal ymin = side/2-slice_h/2;
00339
00340 selector_position = QPointF(p->val*height, ymin + p->sat*slice_h);
00341 QPolygonF triangle;
00342 triangle.append(QPointF(0,side/2));
00343 triangle.append(QPointF(height,0));
00344 triangle.append(QPointF(height,side));
00345 QPainterPath clip;
00346 clip.addPolygon(triangle);
00347 painter.setClipPath(clip);
00348 }
00349
00350 painter.drawImage(QRectF(QPointF(0, 0), p->selector_size()), p->inner_selector);
00351 painter.setClipping(false);
00352
00353
00354 painter.setPen(QPen(p->val > 0.5 ? Qt::black : Qt::white, 3));
00355 painter.setBrush(Qt::NoBrush);
00356 painter.drawEllipse(selector_position, selector_radius, selector_radius);
00357
00358 }
00359
00360 void ColorWheel::mouseMoveEvent(QMouseEvent *ev)
00361 {
00362 if (p->mouse_status == DragCircle )
00363 {
00364 p->hue = p->line_to_point(ev->pos()).angle()/360.0;
00365 p->render_inner_selector();
00366
00367 emit colorSelected(color());
00368 emit colorChanged(color());
00369 update();
00370 }
00371 else if(p->mouse_status == DragSquare)
00372 {
00373 QLineF glob_mouse_ln = p->line_to_point(ev->pos());
00374 QLineF center_mouse_ln ( QPointF(0,0),
00375 glob_mouse_ln.p2() - glob_mouse_ln.p1() );
00376
00377 center_mouse_ln.setAngle(center_mouse_ln.angle()+p->selector_image_angle());
00378 center_mouse_ln.setP2(center_mouse_ln.p2()-p->selector_image_offset());
00379
00380 if ( p->display_flags & SHAPE_SQUARE )
00381 {
00382 p->sat = qBound(0.0, center_mouse_ln.x2()/p->square_size(), 1.0);
00383 p->val = qBound(0.0, center_mouse_ln.y2()/p->square_size(), 1.0);
00384 }
00385 else if ( p->display_flags & SHAPE_TRIANGLE )
00386 {
00387 QPointF pt = center_mouse_ln.p2();
00388
00389 qreal side = p->triangle_side();
00390 p->val = qBound(0.0, pt.x() / p->triangle_height(), 1.0);
00391 qreal slice_h = side * p->val;
00392
00393 qreal ycenter = side/2;
00394 qreal ymin = ycenter-slice_h/2;
00395
00396 if ( slice_h > 0 )
00397 p->sat = qBound(0.0, (pt.y()-ymin)/slice_h, 1.0);
00398 }
00399
00400 emit colorSelected(color());
00401 emit colorChanged(color());
00402 update();
00403 }
00404 }
00405
00406 void ColorWheel::mousePressEvent(QMouseEvent *ev)
00407 {
00408 if ( ev->buttons() & Qt::LeftButton )
00409 {
00410 setFocus();
00411 QLineF ray = p->line_to_point(ev->pos());
00412 if ( ray.length() <= p->inner_radius() )
00413 p->mouse_status = DragSquare;
00414 else if ( ray.length() <= p->outer_radius() )
00415 p->mouse_status = DragCircle;
00416
00417
00418 mouseMoveEvent(ev);
00419 }
00420 }
00421
00422 void ColorWheel::mouseReleaseEvent(QMouseEvent *ev)
00423 {
00424 mouseMoveEvent(ev);
00425 p->mouse_status = Nothing;
00426 }
00427
00428 void ColorWheel::resizeEvent(QResizeEvent *)
00429 {
00430 p->render_ring();
00431 p->render_inner_selector();
00432 }
00433
00434 void ColorWheel::setColor(QColor c)
00435 {
00436 qreal oldh = p->hue;
00437 p->set_color(c);
00438 if (!qFuzzyCompare(oldh+1, p->hue+1))
00439 p->render_inner_selector();
00440 update();
00441 emit colorChanged(c);
00442 }
00443
00444 void ColorWheel::setHue(qreal h)
00445 {
00446 p->hue = qBound(0.0, h, 1.0);
00447 p->render_inner_selector();
00448 update();
00449 }
00450
00451 void ColorWheel::setSaturation(qreal s)
00452 {
00453 p->sat = qBound(0.0, s, 1.0);
00454 update();
00455 }
00456
00457 void ColorWheel::setValue(qreal v)
00458 {
00459 p->val = qBound(0.0, v, 1.0);
00460 update();
00461 }
00462
00463
00464 void ColorWheel::setDisplayFlags(DisplayFlags flags)
00465 {
00466 if ( ! (flags & COLOR_FLAGS) )
00467 flags |= default_flags & COLOR_FLAGS;
00468 if ( ! (flags & ANGLE_FLAGS) )
00469 flags |= default_flags & ANGLE_FLAGS;
00470 if ( ! (flags & SHAPE_FLAGS) )
00471 flags |= default_flags & SHAPE_FLAGS;
00472
00473 if ( (flags & COLOR_FLAGS) != (p->display_flags & COLOR_FLAGS) )
00474 {
00475 QColor old_col = color();
00476 if ( flags & ColorWheel::COLOR_HSL )
00477 {
00478 p->hue = old_col.hueF();
00479 p->sat = detail::color_HSL_saturationF(old_col);
00480 p->val = detail::color_lightnessF(old_col);
00481 p->color_from = &detail::color_from_hsl;
00482 p->rainbow_from_hue = &detail::rainbow_hsv;
00483 }
00484 else if ( flags & ColorWheel::COLOR_LCH )
00485 {
00486 p->hue = old_col.hueF();
00487 p->sat = detail::color_chromaF(old_col);
00488 p->val = detail::color_lumaF(old_col);
00489 p->color_from = &detail::color_from_lch;
00490 p->rainbow_from_hue = &detail::rainbow_lch;
00491 }
00492 else
00493 {
00494 p->hue = old_col.hsvHueF();
00495 p->sat = old_col.hsvSaturationF();
00496 p->val = old_col.valueF();
00497 p->color_from = &QColor::fromHsvF;
00498 p->rainbow_from_hue = &detail::rainbow_hsv;
00499 }
00500 p->render_ring();
00501 }
00502
00503 p->display_flags = flags;
00504 p->render_inner_selector();
00505 update();
00506 emit displayFlagsChanged(flags);
00507 }
00508
00509 ColorWheel::DisplayFlags ColorWheel::displayFlags(DisplayFlags mask) const
00510 {
00511 return p->display_flags & mask;
00512 }
00513
00514 void ColorWheel::setDefaultDisplayFlags(DisplayFlags flags)
00515 {
00516 if ( !(flags & COLOR_FLAGS) )
00517 flags |= hard_default_flags & COLOR_FLAGS;
00518 if ( !(flags & ANGLE_FLAGS) )
00519 flags |= hard_default_flags & ANGLE_FLAGS;
00520 if ( !(flags & SHAPE_FLAGS) )
00521 flags |= hard_default_flags & SHAPE_FLAGS;
00522 default_flags = flags;
00523 }
00524
00525 ColorWheel::DisplayFlags ColorWheel::defaultDisplayFlags(DisplayFlags mask)
00526 {
00527 return default_flags & mask;
00528 }
00529
00530 void ColorWheel::setDisplayFlag(DisplayFlags flag, DisplayFlags mask)
00531 {
00532 setDisplayFlags((p->display_flags&~mask)|flag);
00533 }
00534
00535 void ColorWheel::dragEnterEvent(QDragEnterEvent* event)
00536 {
00537 if ( event->mimeData()->hasColor() ||
00538 ( event->mimeData()->hasText() && QColor(event->mimeData()->text()).isValid() ) )
00539 event->acceptProposedAction();
00540 }
00541
00542 void ColorWheel::dropEvent(QDropEvent* event)
00543 {
00544 if ( event->mimeData()->hasColor() )
00545 {
00546 setColor(event->mimeData()->colorData().value<QColor>());
00547 event->accept();
00548 }
00549 else if ( event->mimeData()->hasText() )
00550 {
00551 QColor col(event->mimeData()->text());
00552 if ( col.isValid() )
00553 {
00554 setColor(col);
00555 event->accept();
00556 }
00557 }
00558 }
00559
00560 }