File indexing completed on 2024-05-12 04:44:30

0001 // SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
0002 // SPDX-License-Identifier: BSD-2-Clause OR MIT
0003 
0004 // Own headers
0005 // First the interface, which forces the header to be self-contained.
0006 #include "chromalightnessimageparameters.h"
0007 
0008 #include "asyncimagerendercallback.h"
0009 #include "helperconversion.h"
0010 #include "helpermath.h"
0011 #include "rgbcolorspace.h"
0012 #include <lcms2.h>
0013 #include <qbitarray.h>
0014 #include <qimage.h>
0015 #include <qnamespace.h>
0016 #include <qrgb.h>
0017 
0018 namespace PerceptualColor
0019 {
0020 
0021 /** @brief Equal operator
0022  *
0023  * @param other The object to compare with.
0024  *
0025  * @returns <tt>true</tt> if equal, <tt>false</tt> otherwise. */
0026 bool ChromaLightnessImageParameters::operator==(const ChromaLightnessImageParameters &other) const
0027 {
0028     return ( //
0029         (hue == other.hue) //
0030         && (imageSizePhysical == other.imageSizePhysical) //
0031         && (rgbColorSpace == other.rgbColorSpace) //
0032     );
0033 }
0034 
0035 /** @brief Unequal operator
0036  *
0037  * @param other The object to compare with.
0038  *
0039  * @returns <tt>true</tt> if unequal, <tt>false</tt> otherwise. */
0040 bool ChromaLightnessImageParameters::operator!=(const ChromaLightnessImageParameters &other) const
0041 {
0042     return !(*this == other);
0043 }
0044 
0045 /** @brief Render an image.
0046  *
0047  * The function will render the image with the given parameters,
0048  * and deliver the result by means of <tt>callbackObject</tt>.
0049  *
0050  * This function is thread-safe as long as each call of this function
0051  * uses different <tt>variantParameters</tt> and <tt>callbackObject</tt>.
0052  *
0053  * @param variantParameters A <tt>QVariant</tt> that contains the
0054  *        image parameters.
0055  * @param callbackObject Pointer to the object for the callbacks.
0056  *
0057  * @todo Interlacing support.
0058  *
0059  * @todo Could we get better performance? Even online tools like
0060  * https://bottosson.github.io/misc/colorpicker/#ff2a00 or
0061  * https://oklch.evilmartians.io/#65.4,0.136,146.7,100 get quite good
0062  * performance. How do they do that? */
0063 void ChromaLightnessImageParameters::render(const QVariant &variantParameters, AsyncImageRenderCallback &callbackObject)
0064 {
0065     if (!variantParameters.canConvert<ChromaLightnessImageParameters>()) {
0066         return;
0067     }
0068     const ChromaLightnessImageParameters parameters = //
0069         variantParameters.value<ChromaLightnessImageParameters>();
0070 
0071     // From Qt Example’s documentation:
0072     //
0073     //     “If we discover […] that restart has been set
0074     //      to true (by render()), we break out […] immediately […].
0075     //      Similarly, if we discover that abort has been set
0076     //      to true (by the […] destructor), we return from the
0077     //      function immediately […].”
0078     if (callbackObject.shouldAbort()) {
0079         return;
0080     }
0081     // Create a new QImage with correct image size.
0082     QImage myImage(QSize(parameters.imageSizePhysical), //
0083                    QImage::Format_ARGB32_Premultiplied);
0084     // A mask for the gamut.
0085     // In-gamut pixel are true, out-of-gamut pixel are false.
0086     QBitArray m_mask(parameters.imageSizePhysical.width() //
0087                          * parameters.imageSizePhysical.height(),
0088                      // Initial boolean value of all bits (true/false):
0089                      false);
0090     // Test if image size is empty.
0091     if (myImage.size().isEmpty()) {
0092         // The image must be non-empty (otherwise, our algorithm would
0093         // crash because of a division by 0).
0094         callbackObject.deliverInterlacingPass( //
0095             myImage, //
0096             QVariant::fromValue(parameters), //
0097             AsyncImageRenderCallback::InterlacingState::Final);
0098         return;
0099     }
0100 
0101     myImage.fill(Qt::transparent); // Initialize background color
0102 
0103     // Initialization
0104     cmsCIELCh cielchD50;
0105     QRgb rgbColor;
0106     int x;
0107     int y;
0108     const auto imageHeight = parameters.imageSizePhysical.height();
0109     const auto imageWidth = parameters.imageSizePhysical.width();
0110 
0111     // Paint the gamut.
0112     cielchD50.h = normalizedAngle360(parameters.hue);
0113     for (y = 0; y < imageHeight; ++y) {
0114         if (callbackObject.shouldAbort()) {
0115             return;
0116         }
0117         cielchD50.L = 100 - (y + 0.5) * 100.0 / imageHeight;
0118         for (x = 0; x < imageWidth; ++x) {
0119             // Using the same scale as on the y axis. floating point
0120             // division thanks to 100 which is a "cmsFloat64Number"
0121             cielchD50.C = (x + 0.5) * 100.0 / imageHeight;
0122             rgbColor = //
0123                 parameters.rgbColorSpace->fromCielabD50ToQRgbOrTransparent( //
0124                     toCmsLab(cielchD50));
0125             if (qAlpha(rgbColor) != 0) {
0126                 // The pixel is within the gamut
0127                 myImage.setPixelColor(x, y, rgbColor);
0128                 m_mask.setBit(maskIndex(x, y, parameters.imageSizePhysical), //
0129                               true);
0130                 // If color is out-of-gamut: We have chroma on the x axis and
0131                 // lightness on the y axis. We are drawing the pixmap line per
0132                 // line, so we go for given lightness from low chroma to high
0133                 // chroma. Because of the nature of many gamuts, if once in a
0134                 // line we have an out-of-gamut value, often all other pixels
0135                 // that are more at the right will be out-of-gamut also. So we
0136                 // could optimize our code and break here. But as we are not
0137                 // sure about this: It’s just likely, but not always correct.
0138                 // We do not know the gamut at compile time, so
0139                 // for the moment we do not optimize the code.
0140             }
0141         }
0142     }
0143     callbackObject.deliverInterlacingPass( //
0144         myImage, //
0145         QVariant::fromValue(parameters), //
0146         AsyncImageRenderCallback::InterlacingState::Final);
0147 }
0148 
0149 } // namespace PerceptualColor