/** * \file * * \author Mattia Basaglia * * \copyright Copyright (C) 2013-2020 Mattia Basaglia * \copyright Copyright (C) 2017 caryoscelus * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "QtColorWidgets/color_wheel.hpp" #include "QtColorWidgets/color_utils.hpp" #include #include #include namespace color_widgets { enum MouseStatus { Nothing, DragCircle, DragSquare }; class ColorWheel::Private { private: ColorWheel * const w; public: qreal hue, sat, val; bool backgroundIsDark; unsigned int wheel_width; MouseStatus mouse_status; QPixmap hue_ring; QImage inner_selector; std::vector inner_selector_buffer; ColorSpaceEnum color_space = ColorHSV; bool rotating_selector = true; ShapeEnum selector_shape = ShapeTriangle; QColor (*color_from)(qreal,qreal,qreal,qreal); QColor (*rainbow_from_hue)(qreal); int max_size = 128; Private(ColorWheel *widget) : w(widget), hue(0), sat(0), val(0), wheel_width(20), mouse_status(Nothing), color_from(&QColor::fromHsvF), rainbow_from_hue(&utils::rainbow_hsv) { } void setup() { qreal backgroundValue = w->palette().window().color().valueF(); backgroundIsDark = backgroundValue < 0.5; } virtual ~Private(){} /// Calculate outer wheel radius from idget center qreal outer_radius() const { return qMin(w->geometry().width(), w->geometry().height())/2; } /// Calculate inner wheel radius from idget center qreal inner_radius() const { return outer_radius()-wheel_width; } /// Calculate the edge length of the inner square qreal square_size() const { return inner_radius()*qSqrt(2); } /// Calculate the height of the inner triangle qreal triangle_height() const { return inner_radius()*3/2; } /// Calculate the side of the inner triangle qreal triangle_side() const { return inner_radius()*qSqrt(3); } /// return line from center to given point QLineF line_to_point(const QPoint &p) const { return QLineF (w->geometry().width()/2, w->geometry().height()/2, p.x(), p.y()); } /** * Ensures the internal image buffer is the correct size * and that the QImage is associated to it */ void init_buffer(QSize size) { std::size_t linear_size = size.width() * size.height(); if ( inner_selector_buffer.size() == linear_size ) return; inner_selector_buffer.resize(linear_size); inner_selector = QImage( reinterpret_cast(inner_selector_buffer.data()), size.width(), size.height(), QImage::Format_RGB32 ); } void render_square() { int width = qMin(square_size(), max_size); init_buffer(QSize(width, width)); for ( int y = 0; y < width; ++y ) { for ( int x = 0; x < width; ++x ) { QRgb color = color_from(hue,double(x)/width,double(y)/width,1).rgb(); inner_selector_buffer[width * y + x] = color; } } } /** * \brief renders the selector as a triangle * \note It's the same as a square with the edge with value=0 collapsed to a single point */ void render_triangle() { QSizeF size = selector_size(); if ( size.height() > max_size ) size *= max_size / size.height(); qreal ycenter = size.height()/2; QSize isize = size.toSize(); init_buffer(isize); for (int x = 0; x < isize.width(); x++ ) { qreal pval = x / size.height(); qreal slice_h = size.height() * pval; for (int y = 0; y < isize.height(); y++ ) { qreal ymin = ycenter-slice_h/2; qreal psat = qBound(0.0,(y-ymin)/slice_h,1.0); QRgb color = color_from(hue,psat,pval,1).rgb(); inner_selector_buffer[isize.width() * y + x] = color; } } } /// Updates the inner image that displays the saturation-value selector void render_inner_selector() { if ( selector_shape == ShapeTriangle ) render_triangle(); else render_square(); } /// Offset of the selector image QPointF selector_image_offset() { if ( selector_shape == ShapeTriangle ) return QPointF(-inner_radius(),-triangle_side()/2); return QPointF(-square_size()/2,-square_size()/2); } /** * \brief Size of the selector when rendered to the screen */ QSizeF selector_size() { if ( selector_shape == ShapeTriangle ) return QSizeF(triangle_height(), triangle_side()); return QSizeF(square_size(), square_size()); } /// Rotation of the selector image qreal selector_image_angle() { if ( selector_shape == ShapeTriangle ) { if ( rotating_selector ) return -hue*360-60; return -150; } else { if ( rotating_selector ) return -hue*360-45; else return 180; } } /// Updates the outer ring that displays the hue selector void render_ring() { hue_ring = QPixmap(outer_radius()*2,outer_radius()*2); hue_ring.fill(Qt::transparent); QPainter painter(&hue_ring); painter.setRenderHint(QPainter::Antialiasing); painter.setCompositionMode(QPainter::CompositionMode_Source); const int hue_stops = 24; QConicalGradient gradient_hue(0, 0, 0); if ( gradient_hue.stops().size() < hue_stops ) { for ( double a = 0; a < 1.0; a+=1.0/(hue_stops-1) ) { gradient_hue.setColorAt(a,rainbow_from_hue(a)); } gradient_hue.setColorAt(1,rainbow_from_hue(0)); } painter.translate(outer_radius(),outer_radius()); painter.setPen(Qt::NoPen); painter.setBrush(QBrush(gradient_hue)); painter.drawEllipse(QPointF(0,0),outer_radius(),outer_radius()); painter.setBrush(Qt::transparent);//palette().background()); painter.drawEllipse(QPointF(0,0),inner_radius(),inner_radius()); } void set_color(const QColor& c) { float nice_hue = c.hsvHueF(); if ( nice_hue < 0 ) nice_hue = c.hslHueF(); if ( nice_hue < 0 ) nice_hue = hue; switch ( color_space ) { case ColorHSV: hue = nice_hue; sat = c.hsvSaturationF(); val = c.valueF(); break; case ColorHSL: hue = nice_hue; sat = utils::color_HSL_saturationF(c); val = utils::color_lightnessF(c); break; case ColorLCH: hue = nice_hue; sat = utils::color_chromaF(c); val = utils::color_lumaF(c); break; } } void draw_ring_editor(double editor_hue, QPainter& painter, QColor color) { painter.setPen(QPen(color,3)); painter.setBrush(Qt::NoBrush); QLineF ray(0, 0, outer_radius(), 0); ray.setAngle(editor_hue*360); QPointF h1 = ray.p2(); ray.setLength(inner_radius()); QPointF h2 = ray.p2(); painter.drawLine(h1,h2); } }; } // namespace color_widgets