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->colorModel == KisCIETongueWidget::RGBA) {
532 gamutPaint.save();
533 gamutPaint.setOpacity(0.5);
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 gamutPaint.drawPath(path);
543 gamutPaint.restore();
544 }
545 if (!d->gamut.empty()) {
546
547 gamutPaint.setOpacity(1.0);
548 foreach (QPointF Point, d->gamut) {
549 mapPoint(x, y, Point);
550 gamutPaint.drawEllipse(x + d->xBias- 2, y-2, 4, 4);
551 //Point.setX(x);
552 //Point.setY(y);
553 //path.lineTo(Point);
554 }
555 }
556
557 gamutPaint.end();
558 d->painter.save();
559 d->painter.setOpacity(0.5);
560 d->painter.setCompositionMode(QPainter::CompositionMode_Multiply);
561 d->painter.drawPixmap(0, 0, d->gamutMap);
562 d->painter.setOpacity(1.0);
563 d->painter.restore();
564}
565
567{
568 d->needUpdatePixmap = false;
569 d->pixmap = QPixmap(size() * devicePixelRatioF());
570 d->pixmap.setDevicePixelRatio(devicePixelRatioF());
571
572 if (d->cieTongueNeedsUpdate){
573 // 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.
574 d->cieTongueNeedsUpdate = false;
575 d->cietongue = QPixmap(size() * devicePixelRatioF());
576 d->cietongue.setDevicePixelRatio(devicePixelRatioF());
577 d->cietongue.fill(Qt::black);
578 d->painter.begin(&d->cietongue);
579
580 int pixcols = static_cast<int>(d->cietongue.width()
581 / d->cietongue.devicePixelRatioF());
582 int pixrows = static_cast<int>(d->cietongue.height()
583 / d->cietongue.devicePixelRatioF());
584
585 d->gridside = (qMin(pixcols, pixrows)) / 512.0;
586 d->xBias = grids(32);
587 d->yBias = grids(20);
588 d->pxcols = pixcols - d->xBias;
589 d->pxrows = pixrows - d->yBias;
590
591 d->painter.setBackground(QBrush(qRgb(0, 0, 0)));
592 d->painter.setPen(qRgb(255, 255, 255));
593
595 d->painter.end();
596
597 fillTongue();
598
599 d->painter.begin(&d->cietongue);
601 drawLabels();
603 d->painter.end();
604 }
605 d->pixmap = d->cietongue;
606
607 d->painter.begin(&d->pixmap);
608 //draw whitepoint and colorants
609 if (d->whitePoint[2] > 0.0)
610 {
612 }
613
614 if (d->Primaries[2] != 0.0)
615 {
617 }
618 drawGamut();
619
620 d->painter.end();
621}
622
624{
625 QPainter p(this);
626
627 // Widget is disable : drawing grayed frame.
628
629 if ( !isEnabled() )
630 {
631 p.fillRect(0, 0, width(), height(),
632 palette().color(QPalette::Disabled, QPalette::Window));
633
634 QPen pen(palette().color(QPalette::Disabled, QPalette::WindowText));
635 pen.setStyle(Qt::SolidLine);
636 pen.setWidth(1);
637
638 p.setPen(pen);
639 p.drawRect(0, 0, width(), height());
640
641 return;
642 }
643
644
645 // No profile data to show, or RAW file
646
647 if (!d->profileDataAvailable)
648 {
649 p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window));
650 QPen pen(palette().color(QPalette::Active, QPalette::Text));
651 pen.setStyle(Qt::SolidLine);
652 pen.setWidth(1);
653
654 p.setPen(pen);
655 p.drawRect(0, 0, width(), height());
656
657 if (d->uncalibratedColor)
658 {
659 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
660 i18n("Uncalibrated color space"));
661 }
662 else
663 {
664 p.setPen(Qt::red);
665 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
666 i18n("No profile available..."));
667 }
668
669 return;
670 }
671
672 // Create CIE tongue if needed
673 if (d->needUpdatePixmap)
674 {
675 updatePixmap();
676 }
677
678 // draw prerendered tongue
679 p.drawPixmap(0, 0, d->pixmap);
680}
681
682void KisCIETongueWidget::resizeEvent(QResizeEvent* event)
683{
684 QWidget::resizeEvent(event);
685 d->needUpdatePixmap = true;
686 d->cieTongueNeedsUpdate = true;
687}
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()