Perceptual Color

chromalightnessimageparameters.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own headers
5// First the interface, which forces the header to be self-contained.
6#include "chromalightnessimageparameters.h"
7
8#include "asyncimagerendercallback.h"
9#include "helperconversion.h"
10#include "helpermath.h"
11#include "rgbcolorspace.h"
12#include <lcms2.h>
13#include <qbitarray.h>
14#include <qimage.h>
15#include <qnamespace.h>
16#include <qrgb.h>
17
18namespace PerceptualColor
19{
20
21/** @brief Equal operator
22 *
23 * @param other The object to compare with.
24 *
25 * @returns <tt>true</tt> if equal, <tt>false</tt> otherwise. */
26bool ChromaLightnessImageParameters::operator==(const ChromaLightnessImageParameters &other) const
27{
28 return ( //
29 (hue == other.hue) //
30 && (imageSizePhysical == other.imageSizePhysical) //
31 && (rgbColorSpace == other.rgbColorSpace) //
32 );
33}
34
35/** @brief Unequal operator
36 *
37 * @param other The object to compare with.
38 *
39 * @returns <tt>true</tt> if unequal, <tt>false</tt> otherwise. */
40bool ChromaLightnessImageParameters::operator!=(const ChromaLightnessImageParameters &other) const
41{
42 return !(*this == other);
43}
44
45/** @brief Render an image.
46 *
47 * The function will render the image with the given parameters,
48 * and deliver the result by means of <tt>callbackObject</tt>.
49 *
50 * This function is thread-safe as long as each call of this function
51 * uses different <tt>variantParameters</tt> and <tt>callbackObject</tt>.
52 *
53 * @param variantParameters A <tt>QVariant</tt> that contains the
54 * image parameters.
55 * @param callbackObject Pointer to the object for the callbacks.
56 *
57 * @todo Interlacing support.
58 *
59 * @todo Could we get better performance? Even online tools like
60 * https://bottosson.github.io/misc/colorpicker/#ff2a00 or
61 * https://oklch.evilmartians.io/#65.4,0.136,146.7,100 get quite good
62 * performance. How do they do that? */
63void ChromaLightnessImageParameters::render(const QVariant &variantParameters, AsyncImageRenderCallback &callbackObject)
64{
65 if (!variantParameters.canConvert<ChromaLightnessImageParameters>()) {
66 return;
67 }
68 const ChromaLightnessImageParameters parameters = //
69 variantParameters.value<ChromaLightnessImageParameters>();
70
71 // From Qt Example’s documentation:
72 //
73 // “If we discover […] that restart has been set
74 // to true (by render()), we break out […] immediately […].
75 // Similarly, if we discover that abort has been set
76 // to true (by the […] destructor), we return from the
77 // function immediately […].”
78 if (callbackObject.shouldAbort()) {
79 return;
80 }
81 // Create a new QImage with correct image size.
82 QImage myImage(QSize(parameters.imageSizePhysical), //
84 // A mask for the gamut.
85 // In-gamut pixel are true, out-of-gamut pixel are false.
86 QBitArray m_mask(parameters.imageSizePhysical.width() //
87 * parameters.imageSizePhysical.height(),
88 // Initial boolean value of all bits (true/false):
89 false);
90 // Test if image size is empty.
91 if (myImage.size().isEmpty()) {
92 // The image must be non-empty (otherwise, our algorithm would
93 // crash because of a division by 0).
94 callbackObject.deliverInterlacingPass( //
95 myImage, //
96 QVariant::fromValue(parameters), //
97 AsyncImageRenderCallback::InterlacingState::Final);
98 return;
99 }
100
101 myImage.fill(Qt::transparent); // Initialize background color
102
103 // Initialization
104 cmsCIELCh cielchD50;
105 QRgb rgbColor;
106 int x;
107 int y;
108 const auto imageHeight = parameters.imageSizePhysical.height();
109 const auto imageWidth = parameters.imageSizePhysical.width();
110
111 // Paint the gamut.
112 cielchD50.h = normalizedAngle360(parameters.hue);
113 for (y = 0; y < imageHeight; ++y) {
114 if (callbackObject.shouldAbort()) {
115 return;
116 }
117 cielchD50.L = 100 - (y + 0.5) * 100.0 / imageHeight;
118 for (x = 0; x < imageWidth; ++x) {
119 // Using the same scale as on the y axis. floating point
120 // division thanks to 100 which is a "cmsFloat64Number"
121 cielchD50.C = (x + 0.5) * 100.0 / imageHeight;
122 rgbColor = //
123 parameters.rgbColorSpace->fromCielabD50ToQRgbOrTransparent( //
124 toCmsLab(cielchD50));
125 if (qAlpha(rgbColor) != 0) {
126 // The pixel is within the gamut
127 myImage.setPixelColor(x, y, rgbColor);
128 m_mask.setBit(maskIndex(x, y, parameters.imageSizePhysical), //
129 true);
130 // If color is out-of-gamut: We have chroma on the x axis and
131 // lightness on the y axis. We are drawing the pixmap line per
132 // line, so we go for given lightness from low chroma to high
133 // chroma. Because of the nature of many gamuts, if once in a
134 // line we have an out-of-gamut value, often all other pixels
135 // that are more at the right will be out-of-gamut also. So we
136 // could optimize our code and break here. But as we are not
137 // sure about this: It’s just likely, but not always correct.
138 // We do not know the gamut at compile time, so
139 // for the moment we do not optimize the code.
140 }
141 }
142 }
143 callbackObject.deliverInterlacingPass( //
144 myImage, //
145 QVariant::fromValue(parameters), //
146 AsyncImageRenderCallback::InterlacingState::Final);
147}
148
149} // namespace PerceptualColor
The namespace of this library.
Format_ARGB32_Premultiplied
transparent
bool canConvert() const const
QVariant fromValue(T &&value)
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:36 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.