Warning, file /office/calligra/libs/widgets/KoTriangleColorSelector.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *  Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
0003  *
0004  *  This library is free software; you can redistribute it and/or modify
0005  *  it under the terms of the GNU Lesser General Public License as published by
0006  *  the Free Software Foundation; either version 2.1 of the License,
0007  *  or (at your option) any later version.
0008  *
0009  *  This library is distributed in the hope that it will be useful,
0010  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0011  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012  *  GNU Lesser General Public License for more details.
0013  *
0014  *  You should have received a copy of the GNU Lesser General Public License
0015  *  along with this program; if not, write to the Free Software
0016  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0017  */
0018 
0019 #include "KoTriangleColorSelector.h"
0020 #include <math.h>
0021 
0022 #include <QMouseEvent>
0023 #include <QPainter>
0024 #include <QPixmap>
0025 #include <QTimer>
0026 #include <KoColorSpaceRegistry.h>
0027 #include <KoColorConversions.h>
0028 #include <KoColorDisplayRendererInterface.h>
0029 
0030 
0031 enum CurrentHandle {
0032     NoHandle,
0033     HueHandle,
0034     ValueSaturationHandle };
0035 
0036 struct Q_DECL_HIDDEN KoTriangleColorSelector::Private {
0037     Private(KoTriangleColorSelector *_q, const KoColorDisplayRendererInterface *_displayRenderer)
0038         : q(_q),
0039           displayRenderer(_displayRenderer),
0040           hue(0),
0041           saturation(0),
0042           value(0),
0043           updateAllowed(true),
0044           invalidTriangle(true),
0045           lastX(-1),
0046           lastY(-1)
0047     {
0048     }
0049 
0050     KoTriangleColorSelector *q;
0051     const KoColorDisplayRendererInterface *displayRenderer;
0052     QPixmap wheelPixmap;
0053     QPixmap trianglePixmap;
0054     int hue;
0055     int saturation;
0056     int value;
0057     int sizeColorSelector;
0058     qreal centerColorSelector;
0059     qreal wheelWidthProportion;
0060     qreal wheelWidth;
0061     qreal wheelNormExt;
0062     qreal wheelNormInt;
0063     qreal wheelInnerRadius;
0064     qreal triangleRadius;
0065     qreal triangleLength;
0066     qreal triangleHeight;
0067     qreal triangleBottom;
0068     qreal triangleTop;
0069     qreal normExt;
0070     qreal normInt;
0071     bool updateAllowed;
0072     CurrentHandle handle;
0073     qreal triangleHandleSize;
0074     bool invalidTriangle;
0075     int lastX, lastY;
0076     QTimer updateTimer;
0077 
0078     void init();
0079 };
0080 
0081 void KoTriangleColorSelector::Private::init()
0082 {
0083     q->setMinimumHeight( 100 );
0084     q->setMinimumWidth( 100 );
0085     q->setMouseTracking( true );
0086     q->updateTriangleCircleParameters();
0087     updateTimer.setInterval(1);
0088     updateTimer.setSingleShot(true);
0089     q->connect(&updateTimer, SIGNAL(timeout()), q, SLOT(update()));
0090 }
0091 
0092 KoTriangleColorSelector::KoTriangleColorSelector(QWidget* parent)
0093     : QWidget(parent),
0094       d(new Private(this, KoDumbColorDisplayRenderer::instance()))
0095 {
0096     d->init();
0097 }
0098 
0099 KoTriangleColorSelector::KoTriangleColorSelector(const KoColorDisplayRendererInterface *displayRenderer, QWidget *parent)
0100     : QWidget(parent),
0101       d(new Private(this, displayRenderer))
0102 {
0103     d->init();
0104     connect(displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(configurationChanged()));
0105 }
0106 
0107 KoTriangleColorSelector::~KoTriangleColorSelector()
0108 {
0109     delete d;
0110 }
0111 
0112 void KoTriangleColorSelector::updateTriangleCircleParameters()
0113 {
0114     d->sizeColorSelector = qMin(width(), height());
0115     d->centerColorSelector = 0.5 * d->sizeColorSelector;
0116     d->wheelWidthProportion = 0.25;
0117     d->wheelWidth = d->centerColorSelector * d->wheelWidthProportion;
0118     d->wheelNormExt = qAbs( d->centerColorSelector );
0119     d->wheelNormInt = qAbs( d->centerColorSelector * (1.0 - d->wheelWidthProportion));
0120     d->wheelInnerRadius = d->centerColorSelector * (1.0 - d->wheelWidthProportion);
0121     d->triangleRadius = d->wheelInnerRadius * 0.9;
0122     d->triangleLength = 3.0 / sqrt(3.0) * d->triangleRadius;
0123     d->triangleHeight = d->triangleLength * sqrt(3.0) * 0.5;
0124     d->triangleTop = 0.5 * d->sizeColorSelector - d->triangleRadius;
0125     d->triangleBottom = d->triangleHeight + d->triangleTop;
0126     d->triangleHandleSize = 10.0;
0127 }
0128 
0129 void KoTriangleColorSelector::paintEvent( QPaintEvent * event )
0130 {
0131 
0132     if( d->invalidTriangle )
0133     {
0134       generateTriangle();
0135     }
0136     Q_UNUSED(event);
0137     QPainter p(this);
0138     p.setRenderHint(QPainter::SmoothPixmapTransform);
0139     p.setRenderHint(QPainter::Antialiasing);
0140     QPointF pos(d->centerColorSelector, d->centerColorSelector);
0141     p.translate(QPointF( 0.5*width(), 0.5*height()  ) );
0142     // Draw the wheel
0143     p.drawPixmap( -pos, d->wheelPixmap );
0144     // Draw the triangle
0145     p.save();
0146 
0147     p.rotate( hue() + 150 );
0148 
0149 
0150     p.drawPixmap( -pos , d->trianglePixmap );
0151     // Draw selectors
0152     p.restore();
0153     // Draw value,saturation selector
0154     //   Compute coordinates
0155     {
0156         qreal vs_selector_ypos_ = value() / 255.0;
0157         qreal ls_ = (vs_selector_ypos_) * d->triangleLength; // length of the saturation on the triangle
0158         qreal vs_selector_xpos_ = ls_ * (saturation() / 255.0 - 0.5);
0159         // Draw it
0160         p.save();
0161         p.setPen( QPen( Qt::white, 1.0) );
0162 
0163         QColor currentColor = d->displayRenderer->toQColor(color());
0164 
0165         p.setBrush(currentColor);
0166         p.rotate( hue() + 150 );
0167         p.drawEllipse( QRectF( -d->triangleHandleSize*0.5 + vs_selector_xpos_,
0168                                -d->triangleHandleSize*0.5 - (d->centerColorSelector - d->triangleTop) + vs_selector_ypos_ * d->triangleHeight,
0169                                 d->triangleHandleSize , d->triangleHandleSize ));
0170     }
0171     p.restore();
0172     // Draw Hue selector
0173     p.save();
0174     p.setPen( QPen( Qt::white, 1.0) );
0175     p.rotate( hue() - 90 );
0176     qreal hueSelectorWidth_ = 0.8;
0177     qreal hueSelectorOffset_ = 0.5 *( 1.0 - hueSelectorWidth_) * d->wheelWidth;
0178     qreal hueSelectorSize_ = 0.8 * d->wheelWidth;
0179     p.drawRect( QRectF( -1.5, -d->centerColorSelector + hueSelectorOffset_, 3.0, hueSelectorSize_ ));
0180     p.restore();
0181     p.end();
0182 }
0183 
0184 
0185 // make sure to always use get/set functions when managing HSV properties( don't call directly like d->hue)
0186 // these  settings get updated A LOT when the color picker is being used. You might get unexpected results
0187 int KoTriangleColorSelector::hue() const
0188 {
0189     return d->hue;
0190 }
0191 
0192 void KoTriangleColorSelector::setHue(int h)
0193 {
0194     // setColor() will give you -1 when saturation is 0
0195     // ignore setting hue in this instance. otherwise it will mess up the hue ring
0196     if (h == -1)
0197         return;
0198 
0199 
0200     h = qBound(0, h, 359);
0201     d->hue = h;
0202     tellColorChanged();
0203     d->invalidTriangle = true;
0204     d->updateTimer.start();
0205 }
0206 
0207 int KoTriangleColorSelector::value() const
0208 {
0209     return d->value;
0210 }
0211 
0212 void KoTriangleColorSelector::setValue(int v)
0213 {
0214     v = qBound(0, v, 255);
0215     d->value = v;
0216     tellColorChanged();
0217     d->invalidTriangle = true;
0218     d->updateTimer.start();
0219 }
0220 
0221 int KoTriangleColorSelector::saturation() const
0222 {
0223     return d->saturation;
0224 }
0225 
0226 void KoTriangleColorSelector::setSaturation(int s)
0227 {
0228     s = qBound(0, s, 255);
0229     d->saturation = s;
0230     tellColorChanged();
0231     d->invalidTriangle = true;
0232     d->updateTimer.start();
0233 }
0234 
0235 void KoTriangleColorSelector::setHSV(int h, int s, int v)
0236 {
0237     d->invalidTriangle = (hue() != h);
0238     setHue(h);
0239     setValue(v);
0240     setSaturation(s);
0241 }
0242 
0243 KoColor KoTriangleColorSelector::color() const
0244 {
0245     return d->displayRenderer->fromHsv(hue(), saturation(), value());
0246 }
0247 
0248 void KoTriangleColorSelector::setColor(const KoColor & _color)
0249 {
0250     if ( color() == _color)
0251         return;
0252 
0253     //displayrenderer->getHsv is what sets the foreground color in the application
0254     if(d->updateAllowed) {
0255         int hueRef = hue();
0256         int saturationRef = saturation();
0257         int valueRef = value();
0258 
0259         d->displayRenderer->getHsv(_color, &hueRef, &saturationRef, &valueRef);
0260         setHSV(hueRef, saturationRef, valueRef);
0261 
0262         d->invalidTriangle = true;
0263         d->updateTimer.start();
0264     }
0265 }
0266 
0267 void KoTriangleColorSelector::resizeEvent( QResizeEvent * event )
0268 {
0269     QWidget::resizeEvent( event );
0270     updateTriangleCircleParameters();
0271     generateWheel();
0272     d->invalidTriangle = true;
0273 }
0274 
0275 inline qreal pow2(qreal v)
0276 {
0277     return v*v;
0278 }
0279 
0280 void KoTriangleColorSelector::tellColorChanged()
0281 {
0282     d->updateAllowed = false;
0283     emit colorChanged(color());
0284     d->updateAllowed = true;
0285 }
0286 
0287 void KoTriangleColorSelector::generateTriangle()
0288 {
0289     QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32);
0290     // Length of triangle
0291     int hue_ = hue();
0292     
0293     for(int y = 0; y < d->sizeColorSelector; ++y)
0294     {
0295         qreal ynormalize = ( d->triangleTop - y ) / ( d->triangleTop - d->triangleBottom );
0296         qreal v = 255 * ynormalize;
0297         qreal ls_ = (ynormalize) * d->triangleLength;
0298         qreal startx_ = d->centerColorSelector - 0.5 * ls_;
0299         uint* data = reinterpret_cast<uint*>(image.scanLine(y));
0300         for(int x = 0; x < d->sizeColorSelector; ++x, ++data)
0301         {
0302             qreal s = 255 * (x - startx_) / ls_;
0303             if(v < -1.0 || v > 256.0 || s < -1.0 || s > 256.0 )
0304             {
0305                 *data = qRgba(0,0,0,0);
0306             } else {
0307                 qreal va = 1.0, sa = 1.0;
0308                 if( v < 0.0) { va = 1.0 + v; v = 0; }
0309                 else if( v > 255.0 ) { va = 256.0 - v; v = 255; }
0310                 if( s < 0.0) { sa = 1.0 + s; s = 0; }
0311                 else if( s > 255.0 ) { sa = 256.0 - s; s = 255; }
0312                 qreal coeff = va * sa;
0313 
0314                 KoColor color = d->displayRenderer->fromHsv(hue_, s, v, int(coeff * 255.0));
0315                 QColor qcolor = d->displayRenderer->toQColor(color);
0316 
0317                 *data = qcolor.rgba();
0318             }
0319         }
0320     }
0321     
0322     d->trianglePixmap = QPixmap::fromImage(image);
0323     d->invalidTriangle = false;
0324 }
0325 
0326 void KoTriangleColorSelector::generateWheel()
0327 {
0328     QImage image(d->sizeColorSelector, d->sizeColorSelector, QImage::Format_ARGB32);
0329     for(int y = 0; y < d->sizeColorSelector; y++)
0330     {
0331         qreal yc = y - d->centerColorSelector;
0332         qreal y2 = pow2( yc );
0333         for(int x = 0; x < d->sizeColorSelector; x++)
0334         {
0335             qreal xc = x - d->centerColorSelector;
0336             qreal norm = sqrt(pow2( xc ) + y2);
0337             if( norm <= d->wheelNormExt + 1.0 && norm >= d->wheelNormInt - 1.0 )
0338             {
0339                 qreal acoef = 1.0;
0340                 if(norm > d->wheelNormExt ) acoef = (1.0 + d->wheelNormExt - norm);
0341                 else if(norm < d->wheelNormInt ) acoef = (1.0 - d->wheelNormInt + norm);
0342                 qreal angle = atan2(yc, xc);
0343                 int h = (int)((180 * angle / M_PI) + 180) % 360;
0344 
0345                 KoColor color = d->displayRenderer->fromHsv(h, 255, 255, int(acoef * 255.0));
0346                 QColor qcolor = d->displayRenderer->toQColor(color);
0347 
0348                 image.setPixel(x,y, qcolor.rgba());
0349             } else {
0350                 image.setPixel(x,y, qRgba(0,0,0,0));
0351             }
0352         }
0353     }
0354     d->wheelPixmap = QPixmap::fromImage(image);
0355 }
0356 
0357 void KoTriangleColorSelector::mouseReleaseEvent( QMouseEvent * event )
0358 {
0359     if(event->button() == Qt::LeftButton)
0360     {
0361         selectColorAt( event->x(), event->y());
0362         d->handle = NoHandle;
0363     } else {
0364         QWidget::mouseReleaseEvent( event );
0365     }
0366 }
0367 
0368 void KoTriangleColorSelector::mousePressEvent( QMouseEvent * event )
0369 {
0370     if(event->button() == Qt::LeftButton)
0371     {
0372         d->handle = NoHandle;
0373         selectColorAt( event->x(), event->y());
0374     } else {
0375         QWidget::mousePressEvent( event );
0376     }
0377 }
0378 
0379 void KoTriangleColorSelector::mouseMoveEvent( QMouseEvent * event )
0380 {
0381     if(event->buttons() & Qt::LeftButton)
0382     {
0383         selectColorAt( event->x(), event->y(), false );
0384     } else {
0385         QWidget::mouseMoveEvent( event);
0386     }
0387 }
0388 
0389 void KoTriangleColorSelector::selectColorAt(int _x, int _y, bool checkInWheel)
0390 {
0391     Q_UNUSED( checkInWheel );
0392     
0393     if (d->lastX == _x && d->lastY == _y)
0394     {
0395         return;
0396     }
0397     d->lastX = _x;
0398     d->lastY = _y;
0399     
0400     qreal x = _x - 0.5*width();
0401     qreal y = _y - 0.5*height();
0402     // Check if the click is inside the wheel
0403     qreal norm = sqrt( x * x + y * y);
0404     if ( ( (norm < d->wheelNormExt) && (norm > d->wheelNormInt) && d->handle == NoHandle )
0405          || d->handle == HueHandle ) {
0406         d->handle = HueHandle;
0407         setHue( (int)(atan2(y, x) * 180 / M_PI ) + 180);
0408         d->updateTimer.start();
0409     }
0410     else {
0411     // Compute the s and v value, if they are in range, use them
0412         qreal rotation = -(hue() + 150) * M_PI / 180;
0413         qreal cr = cos(rotation);
0414         qreal sr = sin(rotation);
0415         qreal x1 = x * cr - y * sr; // <- now x1 gives the saturation
0416         qreal y1 = x * sr + y * cr; // <- now y1 gives the value
0417         y1 += d->wheelNormExt;
0418         qreal ynormalize = (d->triangleTop - y1 ) / ( d->triangleTop - d->triangleBottom );
0419         if( (ynormalize >= 0.0 && ynormalize <= 1.0 ) || d->handle == ValueSaturationHandle)
0420         {
0421             d->handle = ValueSaturationHandle;
0422             qreal ls_ = (ynormalize) * d->triangleLength; // length of the saturation on the triangle
0423             qreal sat = ( x1 / ls_ + 0.5) ;
0424             if((sat >= 0.0 && sat <= 1.0) || d->handle == ValueSaturationHandle)
0425             {
0426                 setHSV( hue(), sat * 255, ynormalize * 255);
0427             }
0428         }
0429         d->updateTimer.start();
0430     }
0431 }
0432 
0433 void KoTriangleColorSelector::configurationChanged()
0434 {
0435     generateWheel();
0436     d->invalidTriangle = true;
0437     update();
0438 }