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 <KoColorimetryUtils::xyY> Primaries;
152 QPolygonF gamut;
153 model colorModel {model::RGBA};
154};
155
157 QWidget(parent), d(new Private)
158{
159 d->whitePoint = {0.34773, 0.35952, 1.0};
160 d->gamut = QPolygonF();
161}
162
167
168int KisCIETongueWidget::grids(double val) const
169{
170 return (int) floor(val * d->gridside + 0.5);
171}
172
173void KisCIETongueWidget::setProfileData(QVector <KoColorimetryUtils::xyY> p, KoColorimetryUtils::xyY w, bool profileData)
174{
175 d->profileDataAvailable = profileData;
176 if (profileData){
177 d->Primaries = p;
178
179 d->whitePoint = w;
180 d->needUpdatePixmap = true;
181 } else {
182 return;
183 }
184}
185void KisCIETongueWidget::setGamut(QPolygonF gamut)
186{
187 d->gamut=gamut;
188}
189void KisCIETongueWidget::setRGBData(KoColorimetryUtils::xyY whitepoint, QVector <KoColorimetryUtils::xyY> colorants)
190{
191 if (colorants.size()==3){
192 d->Primaries= colorants;
193
194 d->whitePoint = whitepoint;
195 d->needUpdatePixmap = true;
196 d->colorModel = KisCIETongueWidget::RGBA;
197 d->profileDataAvailable = true;
198 } else {
199 return;
200 }
201}
203{
204 //d->Primaries= colorants;
205
206 d->whitePoint = whitepoint;
207 d->needUpdatePixmap = true;
208 d->colorModel = KisCIETongueWidget::CMYKA;
209 d->profileDataAvailable = true;
210}
212{
213 d->whitePoint = whitepoint;
214 d->needUpdatePixmap = true;
215 d->colorModel = KisCIETongueWidget::XYZA;
216 d->profileDataAvailable = true;
217}
219{
220 d->whitePoint = whitepoint;
221 d->needUpdatePixmap = true;
222 d->colorModel = KisCIETongueWidget::GRAYA;
223 d->profileDataAvailable = true;
224}
226{
227 d->whitePoint = whitepoint;
228 d->needUpdatePixmap = true;
229 d->colorModel = KisCIETongueWidget::LABA;
230 d->profileDataAvailable = true;
231}
233{
234 d->whitePoint = whitepoint;
235 d->needUpdatePixmap = true;
236 d->colorModel = KisCIETongueWidget::YCbCrA;
237 d->profileDataAvailable = true;
238}
239
241{
242 d->profileDataAvailable = dataAvailable;
243}
244void KisCIETongueWidget::mapPoint(int& icx, int& icy, QPointF xy)
245{
246 icx = (int) floor((xy.x() * (d->pxcols - 1)) + .5);
247 icy = (int) floor(((d->pxrows - 1) - xy.y() * (d->pxrows - 1)) + .5);
248}
249
250void KisCIETongueWidget::biasedLine(int x1, int y1, int x2, int y2)
251{
252 d->painter.drawLine(x1 + d->xBias, y1, x2 + d->xBias, y2);
253}
254
255void KisCIETongueWidget::biasedText(int x, int y, const QString& txt)
256{
257 d->painter.drawText(QPoint(d->xBias + x, y), txt);
258}
259
261{
262 // Get xyz components scaled from coordinates
263
264 double cx = ((double)x) / (d->pxcols * devicePixelRatioF() - 1);
265 double cy = 1.0 - ((double)y) / (d->pxrows * devicePixelRatioF() - 1);
266 double cz = 1.0 - cx - cy;
267
268 // Project xyz to XYZ space. Note that in this
269 // particular case we are substituting XYZ with xyz
270
271 //Need to use KoColor here.
272 const KoColorSpace* xyzColorSpace = KoColorSpaceRegistry::instance()->colorSpace("XYZA", "U8");
273 quint8 data[4];
274 data[0]= cx*255;
275 data[1]= cy*255;
276 data[2]= cz*255;
277 data[3]= 1.0*255;
278 KoColor colXYZ(data, xyzColorSpace);
279 QColor colRGB = colXYZ.toQColor();
280 return qRgb(colRGB.red(), colRGB.green(), colRGB.blue());
281}
282
284{
285 int lx=0, ly=0;
286 int fx=0, fy=0;
287
288 for (int x = 380; x <= 700; x += 5) {
289 int ix = (x - 380) / 5;
290
291 QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]);
292 int icx, icy;
293 mapPoint(icx, icy, p);
294
295 if (x > 380) {
296 biasedLine(lx, ly, icx, icy);
297 }
298 else {
299 fx = icx;
300 fy = icy;
301 }
302
303 lx = icx;
304 ly = icy;
305
306 }
307
308 biasedLine(lx, ly, fx, fy);
309}
310
312{
313 QImage Img = d->cietongue.toImage();
314 Img.setDevicePixelRatio(devicePixelRatioF());
315
316 int x;
317
318 for (int y = 0; y < d->pxrows * devicePixelRatioF(); ++y) {
319 int xe = 0;
320
321 // Find horizontal extents of tongue on this line.
322
323 for (x = 0; x < d->pxcols * devicePixelRatioF(); ++x) {
324 if (QColor(Img.pixel(x + d->xBias, y)) != QColor(Qt::black))
325 {
326 for (xe = (d->pxcols * devicePixelRatioF()) - 1; xe >= x;
327 --xe) {
328 if (QColor(Img.pixel(xe + d->xBias, y)) != QColor(Qt::black))
329 {
330 break;
331 }
332 }
333
334 break;
335 }
336 }
337
338 if (x < d->pxcols * devicePixelRatioF()) {
339 for ( ; x <= xe; ++x)
340 {
341 QRgb Color = colorByCoord(x, y);
342 Img.setPixel(x + d->xBias, y, Color);
343 }
344 }
345 }
346
347 d->cietongue = QPixmap::fromImage(Img, Qt::AvoidDither);
348 d->cietongue.setDevicePixelRatio(devicePixelRatioF());
349}
350
352{
353 QFont font;
354 font.setPointSize(6);
355 d->painter.setFont(font);
356
357 d->painter.setPen(qRgb(255, 255, 255));
358
359 biasedLine(0, 0, 0, d->pxrows - 1);
360 biasedLine(0, d->pxrows-1, d->pxcols-1, d->pxrows - 1);
361
362 for (int y = 1; y <= 9; y += 1)
363 {
364 QString s;
365 int xstart = (y * (d->pxcols - 1)) / 10;
366 int ystart = (y * (d->pxrows - 1)) / 10;
367
368 QTextStream(&s) << y;
369 biasedLine(xstart, d->pxrows - grids(1), xstart, d->pxrows - grids(4));
370 biasedText(xstart - grids(11), d->pxrows + grids(15), s);
371
372 QTextStream(&s) << 10 - y;
373 biasedLine(0, ystart, grids(3), ystart);
374 biasedText(grids(-25), ystart + grids(5), s);
375 }
376}
377
379{
380 d->painter.setPen(qRgb(128, 128, 128));
381 d->painter.setOpacity(0.5);
382
383 for (int y = 1; y <= 9; y += 1)
384 {
385 int xstart = (y * (d->pxcols - 1)) / 10;
386 int ystart = (y * (d->pxrows - 1)) / 10;
387
388 biasedLine(xstart, grids(4), xstart, d->pxrows - grids(4) - 1);
389 biasedLine(grids(7), ystart, d->pxcols-1-grids(7), ystart);
390 }
391 d->painter.setOpacity(1.0);
392}
393
395{
396 QFont font;
397 font.setPointSize(5);
398 d->painter.setFont(font);
399
400 for (int x = 450; x <= 650; x += (x > 470 && x < 600) ? 5 : 10)
401 {
402 QString wl;
403 int bx = 0, by = 0, tx, ty;
404
405 if (x < 520)
406 {
407 bx = grids(-22);
408 by = grids(2);
409 }
410 else if (x < 535)
411 {
412 bx = grids(-8);
413 by = grids(-6);
414 }
415 else
416 {
417 bx = grids(4);
418 }
419
420 int ix = (x - 380) / 5;
421
422 QPointF p(spectral_chromaticity[ix][0], spectral_chromaticity[ix][1]);
423
424 int icx, icy;
425 mapPoint(icx, icy, p);
426
427 tx = icx + ((x < 520) ? grids(-2) : ((x >= 535) ? grids(2) : 0));
428 ty = icy + ((x < 520) ? 0 : ((x >= 535) ? grids(-1) : grids(-2)));
429
430 d->painter.setPen(qRgb(255, 255, 255));
431 biasedLine(icx, icy, tx, ty);
432
433 QRgb Color = colorByCoord(icx, icy);
434 d->painter.setPen(Color);
435
436 QTextStream(&wl) << x;
437 biasedText(icx+bx, icy+by, wl);
438 }
439}
440
441void KisCIETongueWidget::drawSmallEllipse(QPointF xy, int r, int g, int b, int sz)
442{
443 int icx, icy;
444
445 mapPoint(icx, icy, xy);
446 d->painter.save();
447 d->painter.setRenderHint(QPainter::Antialiasing);
448 d->painter.setPen(qRgb(r, g, b));
449 d->painter.drawEllipse(icx + d->xBias- sz/2, icy-sz/2, sz, sz);
450 d->painter.setPen(qRgb(r/2, g/2, b/2));
451 int sz2 = sz-2;
452 d->painter.drawEllipse(icx + d->xBias- sz2/2, icy-sz2/2, sz2, sz2);
453 d->painter.restore();
454}
455
457 return QPointF(xy.x, xy.y);
458}
459
461{
462 d->painter.save();
463 d->painter.setPen(qRgb(80, 80, 80));
464 d->painter.setRenderHint(QPainter::Antialiasing);
465 if (d->colorModel ==KisCIETongueWidget::RGBA && d->Primaries.size() == 3) {
466 drawSmallEllipse(xyYToQPointF(d->Primaries[0]), 255, 128, 128, 6);
467 drawSmallEllipse(xyYToQPointF(d->Primaries[1]), 128, 255, 128, 6);
468 drawSmallEllipse(xyYToQPointF(d->Primaries[2]), 128, 128, 255, 6);
469
470 int x1, y1, x2, y2, x3, y3;
471
472 mapPoint(x1, y1, xyYToQPointF(d->Primaries[0]));
473 mapPoint(x2, y2, xyYToQPointF(d->Primaries[1]));
474 mapPoint(x3, y3, xyYToQPointF(d->Primaries[2]));
475
476 biasedLine(x1, y1, x2, y2);
477 biasedLine(x2, y2, x3, y3);
478 biasedLine(x3, y3, x1, y1);
479 } /*else if (d->colorModel ==CMYK){
480 for (i=0; i<d->Primaries.size();i+++){
481 drawSmallEllipse((QPointF(d->Primaries[0],d->Primaries[1])), 160, 160, 160, 6);//greyscale for now
482 //int x1, y1, x2, y2;
483 //mapPoint(x1, y1, (QPointF(d->Primaries[i],d->Primaries[i+1])) );
484 //mapPoint(x2, y2, (QPointF(d->Primaries[i+3],d->Primaries[i+4])) );
485 //biasedLine(x1, y1, x2, y2);
486 }
487 }
488 */
489
490 d->painter.restore();
491}
492
494{
495 drawSmallEllipse(xyYToQPointF(d->whitePoint), 255, 255, 255, 8);
496}
497
499{
500 d->gamutMap = QPixmap(size() * devicePixelRatioF());
501 d->gamutMap.setDevicePixelRatio(devicePixelRatioF());
502 d->gamutMap.fill(Qt::black);
503 QPainter gamutPaint;
504 gamutPaint.begin(&d->gamutMap);
505 QPainterPath path;
506 //gamutPaint.setCompositionMode(QPainter::CompositionMode_Clear);
507 gamutPaint.setRenderHint(QPainter::Antialiasing);
508 path.setFillRule(Qt::WindingFill);
509 gamutPaint.setBrush(Qt::white);
510 gamutPaint.setPen(Qt::white);
511 int x, y = 0;
512 if (d->colorModel == KisCIETongueWidget::RGBA) {
513 gamutPaint.save();
514 gamutPaint.setOpacity(0.5);
515 mapPoint(x, y, xyYToQPointF(d->Primaries[0]) );
516 path.moveTo(QPointF(x + d->xBias,y));
517 mapPoint(x, y, xyYToQPointF(d->Primaries[1]) );
518 path.lineTo(QPointF(x + d->xBias,y));
519 mapPoint(x, y, xyYToQPointF(d->Primaries[2]) );
520 path.lineTo(QPointF(x + d->xBias,y));
521 mapPoint(x, y, xyYToQPointF(d->Primaries[0]) );
522 path.lineTo(QPointF(x + d->xBias,y));
523 gamutPaint.drawPath(path);
524 gamutPaint.restore();
525 }
526 if (!d->gamut.empty()) {
527
528 gamutPaint.setOpacity(1.0);
529 foreach (QPointF Point, d->gamut) {
530 mapPoint(x, y, Point);
531 gamutPaint.drawEllipse(x + d->xBias- 2, y-2, 4, 4);
532 //Point.setX(x);
533 //Point.setY(y);
534 //path.lineTo(Point);
535 }
536 }
537
538 gamutPaint.end();
539 d->painter.save();
540 d->painter.setOpacity(0.5);
541 d->painter.setCompositionMode(QPainter::CompositionMode_Multiply);
542 d->painter.drawPixmap(0, 0, d->gamutMap);
543 d->painter.setOpacity(1.0);
544 d->painter.restore();
545}
546
548{
549 d->needUpdatePixmap = false;
550 d->pixmap = QPixmap(size() * devicePixelRatioF());
551 d->pixmap.setDevicePixelRatio(devicePixelRatioF());
552
553 if (d->cieTongueNeedsUpdate){
554 // 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.
555 d->cieTongueNeedsUpdate = false;
556 d->cietongue = QPixmap(size() * devicePixelRatioF());
557 d->cietongue.setDevicePixelRatio(devicePixelRatioF());
558 d->cietongue.fill(Qt::black);
559 d->painter.begin(&d->cietongue);
560
561 int pixcols = static_cast<int>(d->cietongue.width()
562 / d->cietongue.devicePixelRatioF());
563 int pixrows = static_cast<int>(d->cietongue.height()
564 / d->cietongue.devicePixelRatioF());
565
566 d->gridside = (qMin(pixcols, pixrows)) / 512.0;
567 d->xBias = grids(32);
568 d->yBias = grids(20);
569 d->pxcols = pixcols - d->xBias;
570 d->pxrows = pixrows - d->yBias;
571
572 d->painter.setBackground(QBrush(qRgb(0, 0, 0)));
573 d->painter.setPen(qRgb(255, 255, 255));
574
576 d->painter.end();
577
578 fillTongue();
579
580 d->painter.begin(&d->cietongue);
582 drawLabels();
584 d->painter.end();
585 }
586 d->pixmap = d->cietongue;
587
588 d->painter.begin(&d->pixmap);
589 //draw whitepoint and colorants
590 if (d->whitePoint.Y > 0.0)
591 {
593 }
594
595 if (!d->Primaries.isEmpty())
596 {
598 }
599 drawGamut();
600
601 d->painter.end();
602}
603
605{
606 QPainter p(this);
607
608 // Widget is disable : drawing grayed frame.
609
610 if ( !isEnabled() )
611 {
612 p.fillRect(0, 0, width(), height(),
613 palette().color(QPalette::Disabled, QPalette::Window));
614
615 QPen pen(palette().color(QPalette::Disabled, QPalette::WindowText));
616 pen.setStyle(Qt::SolidLine);
617 pen.setWidth(1);
618
619 p.setPen(pen);
620 p.drawRect(0, 0, width(), height());
621
622 return;
623 }
624
625
626 // No profile data to show, or RAW file
627
628 if (!d->profileDataAvailable)
629 {
630 p.fillRect(0, 0, width(), height(), palette().color(QPalette::Active, QPalette::Window));
631 QPen pen(palette().color(QPalette::Active, QPalette::Text));
632 pen.setStyle(Qt::SolidLine);
633 pen.setWidth(1);
634
635 p.setPen(pen);
636 p.drawRect(0, 0, width(), height());
637
638 if (d->uncalibratedColor)
639 {
640 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
641 i18n("Uncalibrated color space"));
642 }
643 else
644 {
645 p.setPen(Qt::red);
646 p.drawText(0, 0, width(), height(), Qt::AlignCenter,
647 i18n("No profile available..."));
648 }
649
650 return;
651 }
652
653 // Create CIE tongue if needed
654 if (d->needUpdatePixmap)
655 {
656 updatePixmap();
657 }
658
659 // draw prerendered tongue
660 p.drawPixmap(0, 0, d->pixmap);
661}
662
663void KisCIETongueWidget::resizeEvent(QResizeEvent* event)
664{
665 QWidget::resizeEvent(event);
666 d->needUpdatePixmap = true;
667 d->cieTongueNeedsUpdate = true;
668}
const Params2D p
void biasedText(int x, int y, const QString &txt)
void setGrayData(KoColorimetryUtils::xyY whitepoint)
void biasedLine(int x1, int y1, int x2, int y2)
QVector< KoColorimetryUtils::xyY > Primaries
void setRGBData(KoColorimetryUtils::xyY whitepoint, QVector< KoColorimetryUtils::xyY > colorants)
void setXYZData(KoColorimetryUtils::xyY whitepoint)
void setGamut(QPolygonF gamut)
void drawSmallEllipse(QPointF xy, int r, int g, int b, int sz)
QRgb colorByCoord(double x, double y)
void setCMYKData(KoColorimetryUtils::xyY whitepoint)
void mapPoint(int &icx, int &icy, QPointF xy)
KoColorimetryUtils::xyY whitePoint
void paintEvent(QPaintEvent *) override
void resizeEvent(QResizeEvent *event) override
void setYCbCrData(KoColorimetryUtils::xyY whitepoint)
void setProfileDataAvailable(bool dataAvailable)
void setLABData(KoColorimetryUtils::xyY whitepoint)
int grids(double val) const
void setProfileData(QVector< KoColorimetryUtils::xyY > p, KoColorimetryUtils::xyY w, bool profileData=false)
KisCIETongueWidget(QWidget *parent=nullptr)
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
QPointF xyYToQPointF(KoColorimetryUtils::xyY xy)
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()