Krita Source Code Documentation
Loading...
Searching...
No Matches
KoTriangleColorSelector.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6
8#include <math.h>
9
10#include <QMouseEvent>
11#include <QPainter>
12#include <QPixmap>
13#include <QTimer>
15#include <KoColorConversions.h>
17
18
23
24struct Q_DECL_HIDDEN KoTriangleColorSelector::Private {
26 : q(_q),
27 displayRenderer(_displayRenderer),
28 lastX(-1),
29 lastY(-1)
30 {
31 }
32
34 const KoColorDisplayRendererInterface *displayRenderer {nullptr};
35 QPixmap wheelPixmap;
37 int hue {0};
38 int saturation {0};
39 int value {0};
40 int rotation {0};
41 bool followHue {true};
42 int sizeColorSelector {0};
43 qreal centerColorSelector {0.0};
44 qreal wheelWidthProportion {0.0};
45 qreal wheelWidth {0.0};
46 qreal wheelNormExt {0.0};
47 qreal wheelNormInt {0.0};
48 qreal wheelInnerRadius {0.0};
49 qreal triangleRadius {0.0};
50 qreal triangleLength {0.0};
51 qreal triangleHeight {0.0};
52 qreal triangleBottom {0.0};
53 qreal triangleTop {0.0};
54 qreal normExt {0.0};
55 qreal normInt {0.0};
56 bool updateAllowed {true};
58 qreal triangleHandleSize {0.0};
59 bool invalidTriangle {true};
60 int lastX {-1};
61 int lastY {-1};
63
64 void init();
65};
66
67void KoTriangleColorSelector::Private::init()
68{
69 q->setMinimumHeight( 100 );
70 q->setMinimumWidth( 100 );
71 q->setMouseTracking( true );
72 q->updateTriangleCircleParameters();
73 updateTimer.setInterval(1);
74 updateTimer.setSingleShot(true);
75 q->connect(&updateTimer, SIGNAL(timeout()), q, SLOT(update()));
76}
77
84
87 d(new Private(this, displayRenderer))
88{
89 d->init();
90 connect(displayRenderer, SIGNAL(displayConfigurationChanged()), this, SLOT(configurationChanged()), Qt::UniqueConnection);
91}
92
97
99{
100 d->sizeColorSelector = qMin(width(), height());
101 d->centerColorSelector = 0.5 * d->sizeColorSelector;
102 d->wheelWidthProportion = 0.25;
103 d->wheelWidth = d->centerColorSelector * d->wheelWidthProportion;
104 d->wheelNormExt = qAbs( d->centerColorSelector );
105 d->wheelNormInt = qAbs( d->centerColorSelector * (1.0 - d->wheelWidthProportion));
106 d->wheelInnerRadius = d->centerColorSelector * (1.0 - d->wheelWidthProportion);
107 d->triangleRadius = d->wheelInnerRadius * 0.9;
108 d->triangleLength = 3.0 / sqrt(3.0) * d->triangleRadius;
109 d->triangleHeight = d->triangleLength * sqrt(3.0) * 0.5;
110 d->triangleTop = 0.5 * d->sizeColorSelector - d->triangleRadius;
111 d->triangleBottom = d->triangleHeight + d->triangleTop;
112 d->triangleHandleSize = 10.0;
113}
114
115void KoTriangleColorSelector::paintEvent( QPaintEvent * event )
116{
117
118 if( d->invalidTriangle )
119 {
121 }
122 Q_UNUSED(event);
123 QPainter p(this);
124 p.setRenderHint(QPainter::SmoothPixmapTransform);
125 p.setRenderHint(QPainter::Antialiasing);
126 QPointF pos(d->centerColorSelector, d->centerColorSelector);
127 p.translate(QPointF( 0.5*width(), 0.5*height() ) );
128 // Draw the wheel
129 p.drawPixmap( -pos, d->wheelPixmap );
130 // Draw the triangle
131 p.save();
132
133 p.rotate( rotation() );
134
135
136 p.drawPixmap( -pos , d->trianglePixmap );
137 // Draw selectors
138 p.restore();
139 // Draw value,saturation selector
140 // Compute coordinates
141 {
142 qreal vs_selector_ypos_ = value() / 255.0;
143 qreal ls_ = (vs_selector_ypos_) * d->triangleLength; // length of the saturation on the triangle
144 qreal vs_selector_xpos_ = ls_ * (saturation() / 255.0 - 0.5);
145 // Draw it
146 p.save();
147 p.setPen( QPen( Qt::white, 1.0) );
148
149 QColor currentColor = d->displayRenderer->toQColor(getCurrentColor());
150
151 p.setBrush(currentColor);
152 p.rotate( rotation() );
153 p.drawEllipse( QRectF( -d->triangleHandleSize*0.5 + vs_selector_xpos_,
154 -d->triangleHandleSize*0.5 - (d->centerColorSelector - d->triangleTop) + vs_selector_ypos_ * d->triangleHeight,
155 d->triangleHandleSize , d->triangleHandleSize ));
156 }
157 p.restore();
158 // Draw Hue selector
159 p.save();
160 p.setPen( QPen( Qt::white, 1.0) );
161 p.rotate( hue() - 90 );
162 qreal hueSelectorWidth_ = 0.8;
163 qreal hueSelectorOffset_ = 0.5 *( 1.0 - hueSelectorWidth_) * d->wheelWidth;
164 qreal hueSelectorSize_ = 0.8 * d->wheelWidth;
165 p.drawRect( QRectF( -1.5, -d->centerColorSelector + hueSelectorOffset_, 3.0, hueSelectorSize_ ));
166 p.restore();
167 p.end();
168}
169
171{
172 if (d->followHue) return hue() + 150;
173 return d->rotation;
174}
175
177{
178 d->rotation = angle;
179 setFollowHue(false);
180}
181
183{
184 d->followHue = follow;
185 d->updateTimer.start();
186}
187
188// make sure to always use get/set functions when managing HSV properties (don't call directly like d->hue)
189// these settings get updated A LOT when the color sampler is being used. You might get unexpected results
191{
192 return d->hue;
193}
194
196{
197 // setRealColor() will give you -1 when saturation is 0
198 // ignore setting hue in this instance. otherwise it will mess up the hue ring
199 if (h == -1)
200 return;
201
202
203 h = qBound(0, h, 359);
204 d->hue = h;
206 d->invalidTriangle = true;
207 d->updateTimer.start();
208}
209
211{
212 return d->value;
213}
214
216{
217 v = qBound(0, v, 255);
218 d->value = v;
220 d->invalidTriangle = true;
221 d->updateTimer.start();
222}
223
225{
226 return d->saturation;
227}
228
230{
231 s = qBound(0, s, 255);
232 d->saturation = s;
234 d->invalidTriangle = true;
235 d->updateTimer.start();
236}
237
238void KoTriangleColorSelector::setHSV(int h, int s, int v)
239{
240 d->invalidTriangle = (hue() != h);
241 setHue(h);
242 setValue(v);
243 setSaturation(s);
244}
245
247{
248 return d->displayRenderer->fromHsv(hue(), saturation(), value());
249}
250
252{
253 if ( getCurrentColor() == color)
254 return;
255
256 //displayrenderer->getHsv is what sets the foreground color in the application
257 if(d->updateAllowed) {
258 int hueRef = hue();
259 int saturationRef = saturation();
260 int valueRef = value();
261
262 d->displayRenderer->getHsv(color, &hueRef, &saturationRef, &valueRef);
263 setHSV(hueRef, saturationRef, valueRef);
264
265 d->invalidTriangle = true;
266 d->updateTimer.start();
267 }
268}
269
270void KoTriangleColorSelector::resizeEvent( QResizeEvent * event )
271{
272 QWidget::resizeEvent( event );
275 d->invalidTriangle = true;
276}
277
278inline qreal pow2(qreal v)
279{
280 return v*v;
281}
282
284{
285 d->updateAllowed = false;
287 emit(colorChanged(getCurrentColor().toQColor()));
288 d->updateAllowed = true;
289}
290
292{
293 QSize size = QSize(1, 1)*d->sizeColorSelector*devicePixelRatioF(); // use when int needed
294 QImage image(size, QImage::Format_ARGB32);
295 image.setDevicePixelRatio(devicePixelRatioF());
296
297 // Length of triangle
298 int hue_ = hue();
299
300 qreal triangleTop = d->triangleTop*devicePixelRatioF();
301 qreal triangleBottom = d->triangleBottom*devicePixelRatioF();
302
303 for(int y = 0; y < size.height(); ++y)
304 {
305 qreal ynormalize = ( triangleTop - y ) / ( triangleTop - triangleBottom );
306 qreal v = 255 * ynormalize;
307 qreal ls_ = (ynormalize) * d->triangleLength*devicePixelRatioF();
308 qreal xStart = d->centerColorSelector*devicePixelRatioF() - 0.5 * ls_;
309 qreal xEnd = xStart + ls_;
310 qreal xMin = xStart - 1.0;
311 qreal xMax = xEnd + 1.0;
312 uint* data = reinterpret_cast<uint*>(image.scanLine(y));
313 for(int x = 0; x < size.width(); ++x, ++data)
314 {
315 if (v < -1.0 || v > 256.0 || x < xMin || x > xMax)
316 {
317 *data = qRgba(0,0,0,0);
318 } else {
319 qreal s = 0.0;
320 qreal va = 1.0, sa = 1.0;
321 if( v < 0.0) { va = 1.0 + v; v = 0; }
322 else if( v > 255.0 ) { va = 256.0 - v; v = 255; }
323
324 if (x < xStart) {
325 sa = x - xMin;
326 } else if (x > xEnd) {
327 sa = xMax - x;
328 s = 255;
329 }
330 // avoid NaN values if we hit the triangle tip where ls_ is zero
331 // (and black has undefined saturation anyway)
332 else if (ls_ > 0.01) {
333 s = 255 * (x - xStart) / ls_;
334 }
335 qreal coeff = va * sa;
336
337 KoColor color = d->displayRenderer->fromHsv(hue_, s, v, int(coeff * 255.0));
338 QColor qcolor = d->displayRenderer->toQColor(color);
339
340 *data = qcolor.rgba();
341 }
342 }
343 }
344
345 d->trianglePixmap = QPixmap::fromImage(image);
346 d->invalidTriangle = false;
347}
348
350{
351 QSize size = QSize(1, 1)*d->sizeColorSelector*devicePixelRatioF(); // use only when int needed
352 QImage image(size, QImage::Format_ARGB32);
353 image.setDevicePixelRatio(devicePixelRatioF());
354
355 // the -0.5 ensures dimensions are respective to pixel centers and hence symmetrical
356 qreal center = d->centerColorSelector*devicePixelRatioF() - 0.5;
357 qreal wheelNormExt = d->wheelNormExt*devicePixelRatioF() - 0.5;
358 qreal wheelNormInt = d->wheelNormInt*devicePixelRatioF() - 0.5;
359
360
361 for(int y = 0; y < size.height(); y++)
362 {
363 qreal yc = y - center;
364 qreal y2 = pow2( yc );
365 for(int x = 0; x < size.width(); x++)
366 {
367 qreal xc = x - center;
368 qreal norm = sqrt(pow2( xc ) + y2);
369 if( norm <= wheelNormExt + 1.0 && norm >= wheelNormInt - 1.0 )
370 {
371 qreal acoef = 1.0;
372 if(norm > wheelNormExt ) acoef = (1.0 + wheelNormExt - norm);
373 else if(norm < wheelNormInt ) acoef = (1.0 - wheelNormInt + norm);
374 qreal angle = atan2(yc, xc);
375 int h = (int)((180 * angle / M_PI) + 180) % 360;
376
377 KoColor color = d->displayRenderer->fromHsv(h, 255, 255, int(acoef * 255.0));
378 QColor qcolor = d->displayRenderer->toQColor(color);
379
380 image.setPixel(x,y, qcolor.rgba());
381 } else {
382 image.setPixel(x,y, qRgba(0,0,0,0));
383 }
384 }
385 }
386 d->wheelPixmap = QPixmap::fromImage(image);
387}
388
390{
391 if(event->button() == Qt::LeftButton)
392 {
393 selectColorAt( event->x(), event->y());
394 d->handle = NoHandle;
395 } else {
396 QWidget::mouseReleaseEvent( event );
397 }
398}
399
401{
402 if(event->button() == Qt::LeftButton)
403 {
404 d->handle = NoHandle;
405 selectColorAt( event->x(), event->y());
406 } else {
407 QWidget::mousePressEvent( event );
408 }
409}
410
412{
413 if(event->buttons() & Qt::LeftButton)
414 {
415 selectColorAt( event->x(), event->y(), false );
416 } else {
417 QWidget::mouseMoveEvent( event);
418 }
419}
420
421void KoTriangleColorSelector::selectColorAt(int _x, int _y, bool checkInWheel)
422{
423 Q_UNUSED( checkInWheel );
424
425 if (d->lastX == _x && d->lastY == _y)
426 {
427 return;
428 }
429 d->lastX = _x;
430 d->lastY = _y;
431
432 qreal x = _x - 0.5*width();
433 qreal y = _y - 0.5*height();
434 // Check if the click is inside the wheel
435 qreal norm = sqrt( x * x + y * y);
436 if ( ( (norm < d->wheelNormExt) && (norm > d->wheelNormInt) && d->handle == NoHandle )
437 || d->handle == HueHandle ) {
438 d->handle = HueHandle;
439 setHue( (int)(atan2(y, x) * 180 / M_PI ) + 180);
440 d->updateTimer.start();
441 }
442 else {
443 // Compute the s and v value, if they are in range, use them
444 qreal rotation = -(this->rotation()) * M_PI / 180;
445 qreal cr = cos(rotation);
446 qreal sr = sin(rotation);
447 qreal x1 = x * cr - y * sr; // <- now x1 gives the saturation
448 qreal y1 = x * sr + y * cr; // <- now y1 gives the value
449 y1 += d->wheelNormExt;
450 qreal ynormalize = (d->triangleTop - y1 ) / ( d->triangleTop - d->triangleBottom );
451 if( (ynormalize >= 0.0 && ynormalize <= 1.0 ) || d->handle == ValueSaturationHandle)
452 {
453 d->handle = ValueSaturationHandle;
454 qreal ls_ = (ynormalize) * d->triangleLength; // length of the saturation on the triangle
455 qreal sat = ( x1 / ls_ + 0.5) ;
456 if((sat >= 0.0 && sat <= 1.0) || d->handle == ValueSaturationHandle)
457 {
458 setHSV( hue(), sat * 255, ynormalize * 255);
459 }
460 }
461 d->updateTimer.start();
462 }
463}
464
466{
468 d->invalidTriangle = true;
469 update();
470}
float value(const T *src, size_t ch)
const Params2D p
qreal v
unsigned int uint
qreal pow2(qreal v)
@ ValueSaturationHandle
PythonPluginManager * instance
void sigNewColor(const KoColor &c)
T pow2(const T &x)
Definition kis_global.h:166
#define M_PI
Definition kis_global.h:111
Private(KoTriangleColorSelector *_q, const KoColorDisplayRendererInterface *_displayRenderer)
void setHSV(int h, int s, int v)
void mousePressEvent(QMouseEvent *event) override
KoColor getCurrentColor() const override
void mouseMoveEvent(QMouseEvent *event) override
void slotSetColor(const KoColor &) override
void colorChanged(const QColor &)
void resizeEvent(QResizeEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
void paintEvent(QPaintEvent *event) override
const KoColorDisplayRendererInterface * displayRenderer
void selectColorAt(int x, int y, bool checkInWheel=true)