Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_cie_tongue_widget.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2015 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
3 *
4 * Based on the Digikam CIE Tongue widget
5 * SPDX-FileCopyrightText: 2006-2013 Gilles Caulier <caulier dot gilles at gmail dot com>
6 *
7 * Any source code are inspired from lprof project and
8 * SPDX-FileCopyrightText: 1998-2001 Marti Maria
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 **/
12
28#include <QPointF>
29#include <QPainter>
30#include <QPainterPath>
31#include <QFile>
32#include <QPaintEvent>
33#include <QImage>
34#include <cmath>
35
36#include <klocalizedstring.h>
37
38#include <kis_icon.h>
40
42
43static const double spectral_chromaticity[81][3] =
44{
45 { 0.1741, 0.0050 }, // 380 nm
46 { 0.1740, 0.0050 },
47 { 0.1738, 0.0049 },
48 { 0.1736, 0.0049 },
49 { 0.1733, 0.0048 },
50 { 0.1730, 0.0048 },
51 { 0.1726, 0.0048 },
52 { 0.1721, 0.0048 },
53 { 0.1714, 0.0051 },
54 { 0.1703, 0.0058 },
55 { 0.1689, 0.0069 },
56 { 0.1669, 0.0086 },
57 { 0.1644, 0.0109 },
58 { 0.1611, 0.0138 },
59 { 0.1566, 0.0177 },
60 { 0.1510, 0.0227 },
61 { 0.1440, 0.0297 },
62 { 0.1355, 0.0399 },
63 { 0.1241, 0.0578 },
64 { 0.1096, 0.0868 },
65 { 0.0913, 0.1327 },
66 { 0.0687, 0.2007 },
67 { 0.0454, 0.2950 },
68 { 0.0235, 0.4127 },
69 { 0.0082, 0.5384 },
70 { 0.0039, 0.6548 },
71 { 0.0139, 0.7502 },
72 { 0.0389, 0.8120 },
73 { 0.0743, 0.8338 },
74 { 0.1142, 0.8262 },
75 { 0.1547, 0.8059 },
76 { 0.1929, 0.7816 },
77 { 0.2296, 0.7543 },
78 { 0.2658, 0.7243 },
79 { 0.3016, 0.6923 },
80 { 0.3373, 0.6589 },
81 { 0.3731, 0.6245 },
82 { 0.4087, 0.5896 },
83 { 0.4441, 0.5547 },
84 { 0.4788, 0.5202 },
85 { 0.5125, 0.4866 },
86 { 0.5448, 0.4544 },
87 { 0.5752, 0.4242 },
88 { 0.6029, 0.3965 },
89 { 0.6270, 0.3725 },
90 { 0.6482, 0.3514 },
91 { 0.6658, 0.3340 },
92 { 0.6801, 0.3197 },
93 { 0.6915, 0.3083 },
94 { 0.7006, 0.2993 },
95 { 0.7079, 0.2920 },
96 { 0.7140, 0.2859 },
97 { 0.7190, 0.2809 },
98 { 0.7230, 0.2770 },
99 { 0.7260, 0.2740 },
100 { 0.7283, 0.2717 },
101 { 0.7300, 0.2700 },
102 { 0.7311, 0.2689 },
103 { 0.7320, 0.2680 },
104 { 0.7327, 0.2673 },
105 { 0.7334, 0.2666 },
106 { 0.7340, 0.2660 },
107 { 0.7344, 0.2656 },
108 { 0.7346, 0.2654 },
109 { 0.7347, 0.2653 },
110 { 0.7347, 0.2653 },
111 { 0.7347, 0.2653 },
112 { 0.7347, 0.2653 },
113 { 0.7347, 0.2653 },
114 { 0.7347, 0.2653 },
115 { 0.7347, 0.2653 },
116 { 0.7347, 0.2653 },
117 { 0.7347, 0.2653 },
118 { 0.7347, 0.2653 },
119 { 0.7347, 0.2653 },
120 { 0.7347, 0.2653 },
121 { 0.7347, 0.2653 },
122 { 0.7347, 0.2653 },
123 { 0.7347, 0.2653 },
124 { 0.7347, 0.2653 },
125 { 0.7347, 0.2653 } // 780 nm
126};
127
128class Q_DECL_HIDDEN KisCIETongueWidget::Private
129{
130public:
131
132 bool profileDataAvailable {false};
133 bool needUpdatePixmap {false};
134 bool cieTongueNeedsUpdate {true};
135 bool uncalibratedColor {false};
136
137 int xBias {0};
138 int yBias {0};
139 int pxcols {0};
140 int pxrows {0};
141
142 double gridside {0.0};
143
144 QPainter painter;
145
146 QPixmap pixmap;
147 QPixmap cietongue;
148 QPixmap gamutMap;
149
150 QVector <double> Primaries {9};
151 QVector <double> whitePoint {3};
152 QPolygonF gamut;
153 model colorModel {model::RGBA};
154};
155
157 QWidget(parent), d(new Private)
158{
159 d->Primaries.resize(9);
160 d->Primaries.fill(0.0);
161 d->whitePoint.resize(3);
162 d->whitePoint<<0.34773<<0.35952<<1.0;
163 d->gamut = QPolygonF();
164}
165
170
171int KisCIETongueWidget::grids(double val) const
172{
173 return (int) floor(val * d->gridside + 0.5);
174}
175
176void KisCIETongueWidget::setProfileData(QVector <double> p, QVector <double> w, bool profileData)
177{
178 d->profileDataAvailable = profileData;
179 if (profileData){
180 d->Primaries= p;
181
182 d->whitePoint = w;
183 d->needUpdatePixmap = true;
184 } else {
185 return;
186 }
187}
188void KisCIETongueWidget::setGamut(QPolygonF gamut)
189{
190 d->gamut=gamut;
191}
192void KisCIETongueWidget::setRGBData(QVector <double> whitepoint, QVector <double> colorants)
193{
194 if (colorants.size()==9){
195 d->Primaries= colorants;
196
197 d->whitePoint = whitepoint;
198 d->needUpdatePixmap = true;
199 d->colorModel = KisCIETongueWidget::RGBA;
200 d->profileDataAvailable = true;
201 } else {
202 return;
203 }
204}
205void KisCIETongueWidget::setCMYKData(QVector <double> whitepoint)
206{
207 if (whitepoint.size()==3){
208 //d->Primaries= colorants;
209
210 d->whitePoint = whitepoint;
211 d->needUpdatePixmap = true;
212 d->colorModel = KisCIETongueWidget::CMYKA;
213 d->profileDataAvailable = true;
214 } else {
215 return;
216 }
217}
218void KisCIETongueWidget::setXYZData(QVector <double> whitepoint)
219{
220 if (whitepoint.size()==3){
221 d->whitePoint = whitepoint;
222 d->needUpdatePixmap = true;
223 d->colorModel = KisCIETongueWidget::XYZA;
224 d->profileDataAvailable = true;
225 } else {
226 return;
227 }
228}
229void KisCIETongueWidget::setGrayData(QVector <double> whitepoint)
230{
231 if (whitepoint.size()==3){
232 d->whitePoint = whitepoint;
233 d->needUpdatePixmap = true;
234 d->colorModel = KisCIETongueWidget::GRAYA;
235 d->profileDataAvailable = true;
236 } else {
237 return;
238 }
239}
240void KisCIETongueWidget::setLABData(QVector <double> whitepoint)
241{
242 if (whitepoint.size()==3){
243 d->whitePoint = whitepoint;
244 d->needUpdatePixmap = true;
245 d->colorModel = KisCIETongueWidget::LABA;
246 d->profileDataAvailable = true;
247 } else {
248 return;
249 }
250}
251void KisCIETongueWidget::setYCbCrData(QVector <double> whitepoint)
252{
253 if (whitepoint.size()==3){
254 d->whitePoint = whitepoint;
255 d->needUpdatePixmap = true;
256 d->colorModel = KisCIETongueWidget::YCbCrA;
257 d->profileDataAvailable = true;
258 } else {
259 return;
260 }
261}
262
264{
265 d->profileDataAvailable = dataAvailable;
266}
267void KisCIETongueWidget::mapPoint(int& icx, int& icy, QPointF xy)
268{
269 icx = (int) floor((xy.x() * (d->pxcols - 1)) + .5);
270 icy = (int) floor(((d->pxrows - 1) - xy.y() * (d->pxrows - 1)) + .5);
271}
272
273void KisCIETongueWidget::biasedLine(int x1, int y1, int x2, int y2)
274{
275 d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2);
276}
277
278void KisCIETongueWidget::biasedText(int x, int y, const QString& txt)
279{
280 d->painter.drawText(QPoint(d->xBias + x, y), txt);
281}
282
284{
285 // Get xyz components scaled from coordinates
286
287 double cx = ((double)x) / (d->pxcols * devicePixelRatioF() - 1);
288 double cy = 1.0 - ((double)y) / (d->pxrows * devicePixelRatioF() - 1);
289 double cz = 1.0 - cx - cy;
290
291 // Project xyz to XYZ space. Note that in this
292 // particular case we are substituting XYZ with xyz
293
294 //Need to use KoColor here.
295 const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "U8");
296 quint8 data[4];
297 data[0]= cx*255;
298 data[1]= cy*255;
299 data[2]= cz*255;
300 data[3]= 1.0*255;
301 KoColor colXYZ(data, xyzColorSpace);
302 QColor colRGB = colXYZ.toQColor();
303 return qRgb(colRGB.red(), colRGB.green(), colRGB.blue());
304}
305
307{
308 int lx=0, ly=0;
309 int fx=0, fy=0;
310
311 for (int x = 380; x <= 700; x += 5) {
312 int ix = (x - 380) / 5;
313
314 QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]);
315 int icx, icy;
316 mapPoint(icx, icy, p);
317
318 if (x > 380) {
319 biasedLine(lx, ly, icx, icy);
320 }
321 else {
322 fx = icx;
323 fy = icy;
324 }
325
326 lx = icx;
327 ly = icy;
328
329 }
330
331 biasedLine(lx, ly, fx, fy);
332}
333
335{
336 QImage Img = d->cietongue.toImage();
337 Img.setDevicePixelRatio(devicePixelRatioF());
338
339 int x;
340
341 for (int y = 0; y < d->pxrows * devicePixelRatioF(); ++y) {
342 int xe = 0;
343
344 // Find horizontal extents of tongue on this line.
345
346 for (x = 0; x < d->pxcols * devicePixelRatioF(); ++x) {
347 if (QColor(Img.pixel(x + d->xBias, y)) != QColor(Qt::black))
348 {
349 for (xe = (d->pxcols * devicePixelRatioF()) - 1; xe >= x;
350 --xe) {
351 if (QColor(Img.pixel(xe + d->xBias, y)) != QColor(Qt::black))
352 {
353 break;
354 }
355 }
356
357 break;
358 }
359 }
360
361 if (x < d->pxcols * devicePixelRatioF()) {
362 for ( ; x <= xe; ++x)
363 {
364 QRgb Color = colorByCoord(x, y);
365 Img.setPixel(x + d->xBias, y, Color);
366 }
367 }
368 }
369
370 d->cietongue = QPixmap::fromImage(Img, Qt::AvoidDither);
371 d->cietongue.setDevicePixelRatio(devicePixelRatioF());
372}
373
375{
376 QFont font;
377 font.setPointSize(6);
378 d->painter.setFont(font);
379
380 d->painter.setPen(qRgb(255, 255, 255));
381
382 biasedLine(0, 0, 0, d->pxrows - 1);
383 biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1);
384
385 for (int y = 1; y <= 9; y += 1)
386 {
387 QString s;
388 int xstart = (y * (d->pxcols - 1)) / 10;
389 int ystart = (y * (d->pxrows - 1)) / 10;
390
391 QTextStream(&s) << y;
392 biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4));
393 biasedText(xstart - grids(11), d->pxrows + grids(15), s);
394
395 QTextStream(&s) << 10 - y;
396 biasedLine(0, ystart, grids(3), ystart);
397 biasedText(grids(-25), ystart + grids(5), s);
398 }
399}
400
402{
403 d->painter.setPen(qRgb(128, 128, 128));
404 d->painter.setOpacity(0.5);
405
406 for (int y = 1; y <= 9; y += 1)
407 {
408 int xstart = (y * (d->pxcols - 1)) / 10;
409 int ystart = (y * (d->pxrows - 1)) / 10;
410
411 biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1);
412 biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart);
413 }
414 d->painter.setOpacity(1.0);
415}
416
418{
419 QFont font;
420 font.setPointSize(5);
421 d->painter.setFont(font);
422
423 for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10)
424 {
425 QString wl;
426 int bx = 0, by = 0, tx, ty;
427
428 if (x < 520)
429 {
430 bx = grids(-22);
431 by = grids(2);
432 }
433 else if (x < 535)
434 {
435 bx = grids(-8);
436 by = grids(-6);
437 }
438 else
439 {
440 bx = grids(4);
441 }
442
443 int ix = (x - 380) / 5;
444
445 QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]);
446
447 int icx, icy;
448 mapPoint(icx, icy, p);
449
450 tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0));
451 ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2)));
452
453 d->painter.setPen(qRgb(255, 255, 255));
454 biasedLine(icx, icy, tx, ty);
455
456 QRgb Color = colorByCoord(icx, icy);
457 d->painter.setPen(Color);
458
459 QTextStream(&wl) << x;
460 biasedText(icx+bx, icy+by, wl);
461 }
462}
463
464void KisCIETongueWidget::drawSmallEllipse(QPointF xy, int r, int g, int b, int sz)
465{
466 int icx, icy;
467
468 mapPoint(icx, icy, xy);
469 d->painter.save();
470 d->painter.setRenderHint(QPainter::Antialiasing);
471 d->painter.setPen(qRgb(r, g, b));
472 d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz);
473 d->painter.setPen(qRgb(r/2, g/2, b/2));
474 int sz2 = sz-2;
475 d->painter.drawEllipse(icx + d->xBias- sz2/2, icy-sz2/2, sz2, sz2);
476 d->painter.restore();
477}
478
480{
481 d->painter.save();
482 d->painter.setPen(qRgb(80, 80, 80));
483 d->painter.setRenderHint(QPainter::Antialiasing);
484 if (d->colorModel ==KisCIETongueWidget::RGBA) {
485 drawSmallEllipse((QPointF(d->Primaries[0],d->Primaries[1])), 255, 128, 128, 6);
486 drawSmallEllipse((QPointF(d->Primaries[3],d->Primaries[4])), 128, 255, 128, 6);
487 drawSmallEllipse((QPointF(d->Primaries[6],d->Primaries[7])), 128, 128, 255, 6);
488
489 int x1, y1, x2, y2, x3, y3;
490
491 mapPoint(x1, y1, (QPointF(d->Primaries[0],d->Primaries[1])) );
492 mapPoint(x2, y2, (QPointF(d->Primaries[3],d->Primaries[4])) );
493 mapPoint(x3, y3, (QPointF(d->Primaries[6],d->Primaries[7])) );
494
495 biasedLine(x1, y1, x2, y2);
496 biasedLine(x2, y2, x3, y3);
497 biasedLine(x3, y3, x1, y1);
498 } /*else if (d->colorModel ==CMYK){
499 for (i=0; i<d->Primaries.size();i+++){
500 drawSmallEllipse((QPointF(d->Primaries[0],d->Primaries[1])), 160, 160, 160, 6);//greyscale for now
501 //int x1, y1, x2, y2;
502 //mapPoint(x1, y1, (QPointF(d->Primaries[i],d->Primaries[i+1])) );
503 //mapPoint(x2, y2, (QPointF(d->Primaries[i+3],d->Primaries[i+4])) );
504 //biasedLine(x1, y1, x2, y2);
505 }
506 }
507 */
508
509 d->painter.restore();
510}
511
513{
514 drawSmallEllipse(QPointF (d->whitePoint[0],d->whitePoint[1]), 255, 255, 255, 8);
515}
516
518{
519 d->gamutMap = QPixmap(size() * devicePixelRatioF());
520 d->gamutMap.setDevicePixelRatio(devicePixelRatioF());
521 d->gamutMap.fill(Qt::black);
522 QPainter gamutPaint;
523 gamutPaint.begin(&d->gamutMap);
524 QPainterPath path;
525 //gamutPaint.setCompositionMode(QPainter::CompositionMode_Clear);
526 gamutPaint.setRenderHint(QPainter::Antialiasing);
527 path.setFillRule(Qt::WindingFill);
528 gamutPaint.setBrush(Qt::white);
529 gamutPaint.setPen(Qt::white);
530 int x, y = 0;
531 if (!d->gamut.empty()) {
532 gamutPaint.setOpacity(0.5);
533 if (d->colorModel == KisCIETongueWidget::RGBA) {
534 mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) );
535 path.moveTo(QPointF(x + d->xBias,y));
536 mapPoint(x, y, (QPointF(d->Primaries[3],d->Primaries[4])) );
537 path.lineTo(QPointF(x + d->xBias,y));
538 mapPoint(x, y, (QPointF(d->Primaries[6],d->Primaries[7])) );
539 path.lineTo(QPointF(x + d->xBias,y));
540 mapPoint(x, y, (QPointF(d->Primaries[0],d->Primaries[1])) );
541 path.lineTo(QPointF(x + d->xBias,y));
542 }
543 gamutPaint.drawPath(path);
544 gamutPaint.setOpacity(1.0);
545 foreach (QPointF Point, d->gamut) {
546 mapPoint(x, y, Point);
547 gamutPaint.drawEllipse(x + d->xBias- 2, y-2, 4, 4);
548 //Point.setX(x);
549 //Point.setY(y);
550 //path.lineTo(Point);
551 }
552 }
553
554 gamutPaint.end();
555 d->painter.save();
556 d->painter.setOpacity(0.5);
557 d->painter.setCompositionMode(QPainter::CompositionMode_Multiply);
558 d->painter.drawPixmap(0, 0, d->gamutMap);
559 d->painter.setOpacity(1.0);
560 d->painter.restore();
561}
562
564{
565 d->needUpdatePixmap = false;
566 d->pixmap = QPixmap(size() * devicePixelRatioF());
567 d->pixmap.setDevicePixelRatio(devicePixelRatioF());
568
569 if (d->cieTongueNeedsUpdate){
570 // Draw the CIE tongue curve. I don't see why we need to redraw it every time the whitepoint and such changes so we cache it.
571 d->cieTongueNeedsUpdate = false;
572 d->cietongue = QPixmap(size() * devicePixelRatioF());
573 d->cietongue.setDevicePixelRatio(devicePixelRatioF());
574 d->cietongue.fill(Qt::black);
575 d->painter.begin(&d->cietongue);
576
577 int pixcols = static_cast<int>(d->cietongue.width()
578 / d->cietongue.devicePixelRatioF());
579 int pixrows = static_cast<int>(d->cietongue.height()
580 / d->cietongue.devicePixelRatioF());
581
582 d->gridside = (qMin(pixcols, pixrows)) / 512.0;
583 d->xBias = grids(32);
584 d->yBias = grids(20);
585 d->pxcols = pixcols - d->xBias;
586 d->pxrows = pixrows - d->yBias;
587
588 d->painter.setBackground(QBrush(qRgb(0, 0, 0)));
589 d->painter.setPen(qRgb(255, 255, 255));
590
592 d->painter.end();
593
594 fillTongue();
595
596 d->painter.begin(&d->cietongue);
598 drawLabels();
600 d->painter.end();
601 }
602 d->pixmap = d->cietongue;
603
604 d->painter.begin(&d->pixmap);
605 //draw whitepoint and colorants
606 if (d->whitePoint[2] > 0.0)
607 {
609 }
610
611 if (d->Primaries[2] != 0.0)
612 {
614 }
615 drawGamut();
616
617 d->painter.end();
618}
619
621{
622 QPainter p(this);
623
624 // Widget is disable : drawing grayed frame.
625
626 if ( !isEnabled() )
627 {
628 p.fillRect(0, 0, width(), height(),
629 palette().color(QPalette::Disabled, QPalette::Window));
630
631 QPen pen(palette().color(QPalette::Disabled, QPalette::WindowText));
632 pen.setStyle(Qt::SolidLine);
633 pen.setWidth(1);
634
635 p.setPen(pen);
636 p.drawRect(0, 0, width(), height());
637
638 return;
639 }
640
641
642 // No profile data to show, or RAW file
643
644 if (!d->profileDataAvailable)
645 {
646 p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window));
647 QPen pen(palette().color(QPalette::Active, QPalette::Text));
648 pen.setStyle(Qt::SolidLine);
649 pen.setWidth(1);
650
651 p.setPen(pen);
652 p.drawRect(0, 0, width(), height());
653
654 if (d->uncalibratedColor)
655 {
656 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
657 i18n("Uncalibrated color space"));
658 }
659 else
660 {
661 p.setPen(Qt::red);
662 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
663 i18n("No profile available..."));
664 }
665
666 return;
667 }
668
669 // Create CIE tongue if needed
670 if (d->needUpdatePixmap)
671 {
672 updatePixmap();
673 }
674
675 // draw prerendered tongue
676 p.drawPixmap(0, 0, d->pixmap);
677}
678
679void KisCIETongueWidget::resizeEvent(QResizeEvent* event)
680{
681 QWidget::resizeEvent(event);
682 d->needUpdatePixmap = true;
683 d->cieTongueNeedsUpdate = true;
684}
const Params2D p
void biasedText(int x, int y, const QString &txt)
void biasedLine(int x1, int y1, int x2, int y2)
void setGamut(QPolygonF gamut)
void drawSmallEllipse(QPointF xy, int r, int g, int b, int sz)
QRgb colorByCoord(double x, double y)
void setLABData(QVector< double > whitepoint)
void setGrayData(QVector< double > whitepoint)
void mapPoint(int &icx, int &icy, QPointF xy)
void setRGBData(QVector< double > whitepoint, QVector< double > colorants)
void setProfileData(QVector< double > p, QVector< double > w, bool profileData=false)
void paintEvent(QPaintEvent *) override
void setYCbCrData(QVector< double > whitepoint)
void resizeEvent(QResizeEvent *event) override
void setXYZData(QVector< double > whitepoint)
void setCMYKData(QVector< double > whitepoint)
void setProfileDataAvailable(bool dataAvailable)
int grids(double val) const
KisCIETongueWidget(QWidget *parent=nullptr)
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
static const double spectral_chromaticity[81][3]
unsigned int QRgb
rgba palette[MAX_PALETTE]
Definition palette.c:35
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()