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