File indexing completed on 2024-05-12 15:59:37

0001 /*
0002  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2007 Emanuele Tamponi <emanuele@valinor.it>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #ifndef KOMIXCOLORSOPIMPL_H
0009 #define KOMIXCOLORSOPIMPL_H
0010 
0011 #include "KoMixColorsOp.h"
0012 
0013 #include <QtGlobal>
0014 #include <type_traits>
0015 #include <KisCppQuirks.h>
0016 #include <KoColorSpaceMaths.h>
0017 #include "kis_debug.h"
0018 #include "kis_global.h"
0019 
0020 //#define SANITY_CHECKS
0021 
0022 template <typename T>
0023 static inline T safeDivideWithRound(T dividend,
0024                                     std::enable_if_t<std::is_floating_point<T>::value, T> divisor) {
0025     return dividend / divisor;
0026 }
0027 
0028 template <typename T>
0029 static inline T safeDivideWithRound(T dividend,
0030                                     std::enable_if_t<std::is_integral<T>::value, T> divisor) {
0031     return (dividend + divisor / 2) / divisor;
0032 }
0033 
0034 
0035 
0036 template<class _CSTrait>
0037 class KoMixColorsOpImpl : public KoMixColorsOp
0038 {
0039 public:
0040     KoMixColorsOpImpl() {
0041     }
0042     ~KoMixColorsOpImpl() override { }
0043 
0044     Mixer* createMixer() const override;
0045 
0046     void mixColors(const quint8 * const* colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum = 255) const override {
0047         mixColorsImpl(ArrayOfPointers(colors), WeightsWrapper(weights, weightSum), nColors, dst);
0048     }
0049 
0050     void mixColors(const quint8 *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum = 255) const override {
0051         mixColorsImpl(PointerToArray(colors, _CSTrait::pixelSize), WeightsWrapper(weights, weightSum), nColors, dst);
0052     }
0053 
0054     void mixColors(const quint8 * const* colors, int nColors, quint8 *dst) const override {
0055         mixColorsImpl(ArrayOfPointers(colors), NoWeightsSurrogate(nColors), nColors, dst);
0056     }
0057 
0058     void mixColors(const quint8 *colors, int nColors, quint8 *dst) const override {
0059         mixColorsImpl(PointerToArray(colors, _CSTrait::pixelSize), NoWeightsSurrogate(nColors), nColors, dst);
0060     }
0061 
0062     void mixTwoColorArrays(const quint8* colorsA, const quint8* colorsB, int nColors, qreal weight, quint8* dst) const override {
0063         const quint8* pixelA = colorsA;
0064         const quint8* pixelB = colorsB;
0065         weight = qBound(0.0, weight, 1.0);
0066         for (int i = 0; i < nColors; i++) {
0067             const quint8* colors[2];
0068             colors[0] = pixelA;
0069             colors[1] = pixelB;
0070             qint16 weights[2];
0071             weights[1] = qRound(weight * 255.0);
0072             weights[0] = 255 - weights[1];
0073             mixColorsImpl(ArrayOfPointers(colors), WeightsWrapper(weights, 255), 2, dst);
0074 
0075             pixelA += _CSTrait::pixelSize;
0076             pixelB += _CSTrait::pixelSize;
0077             dst += _CSTrait::pixelSize;
0078         }
0079     }
0080 
0081     void mixArrayWithColor(const quint8* colorArray, const quint8* color, int nColors, qreal weight, quint8* dst) const override {
0082         const quint8* pixelA = colorArray;
0083         weight = qBound(0.0, weight, 1.0);
0084         for (int i = 0; i < nColors; i++) {
0085             const quint8* colors[2];
0086             colors[0] = pixelA;
0087             colors[1] = color;
0088             qint16 weights[2];
0089             weights[1] = qRound(weight * 255.0);
0090             weights[0] = 255 - weights[1];
0091             mixColorsImpl(ArrayOfPointers(colors), WeightsWrapper(weights, 255), 2, dst);
0092 
0093             pixelA += _CSTrait::pixelSize;
0094             dst += _CSTrait::pixelSize;
0095         }
0096     }
0097 
0098 private:
0099     class MixerImpl;
0100 
0101     struct ArrayOfPointers {
0102         ArrayOfPointers(const quint8 * const* colors)
0103             : m_colors(colors)
0104         {
0105         }
0106 
0107         const quint8* getPixel() const {
0108             return *m_colors;
0109         }
0110 
0111         void nextPixel() {
0112             m_colors++;
0113         }
0114 
0115     private:
0116         const quint8 * const * m_colors;
0117     };
0118 
0119     struct PointerToArray {
0120         PointerToArray(const quint8 *colors, int pixelSize)
0121             : m_colors(colors),
0122               m_pixelSize(pixelSize)
0123         {
0124         }
0125 
0126         const quint8* getPixel() const {
0127             return m_colors;
0128         }
0129 
0130         void nextPixel() {
0131             m_colors += m_pixelSize;
0132         }
0133 
0134     private:
0135         const quint8 *m_colors;
0136         const int m_pixelSize;
0137     };
0138 
0139     struct WeightsWrapper
0140     {
0141         typedef typename KoColorSpaceMathsTraits<typename _CSTrait::channels_type>::mixtype mixtype;
0142 
0143         WeightsWrapper(const qint16 *weights, int weightSum)
0144             : m_weights(weights), m_sumOfWeights(weightSum)
0145         {
0146         }
0147 
0148         inline void nextPixel() {
0149             m_weights++;
0150         }
0151 
0152         inline void premultiplyAlphaWithWeight(mixtype &alpha) const {
0153             alpha *= *m_weights;
0154         }
0155 
0156         inline int normalizeFactor() const {
0157             return m_sumOfWeights;
0158         }
0159 
0160     private:
0161         const qint16 *m_weights;
0162         int m_sumOfWeights {0};
0163     };
0164 
0165     struct NoWeightsSurrogate
0166     {
0167         typedef typename KoColorSpaceMathsTraits<typename _CSTrait::channels_type>::mixtype mixtype;
0168 
0169         NoWeightsSurrogate(int numPixels)
0170             : m_numPixles(numPixels)
0171         {
0172         }
0173 
0174         inline void nextPixel() {
0175         }
0176 
0177         inline void premultiplyAlphaWithWeight(mixtype &) const {
0178         }
0179 
0180         inline int normalizeFactor() const {
0181             return m_numPixles;
0182         }
0183 
0184     private:
0185         const int m_numPixles;
0186     };
0187 
0188     class MixDataResult {
0189         using channels_type = typename _CSTrait::channels_type;
0190         using mix_type = typename KoColorSpaceMathsTraits<channels_type>::mixtype;
0191         using MathsTraits = KoColorSpaceMathsTraits<channels_type>;
0192 
0193         mix_type totals[_CSTrait::channels_nb];
0194         mix_type totalAlpha = 0;
0195         qint64 normalizeFactor = 0;
0196 
0197 #ifdef SANITY_CHECKS
0198         qint64 m_numPixels = 0;
0199 #endif
0200 
0201     public:
0202         MixDataResult() {
0203             memset(totals, 0, sizeof(totals));
0204         }
0205 
0206         void computeMixedColor(quint8 *dst) {
0207 #ifdef SANITY_CHECKS
0208 
0209             const mix_type maxSaneNumPixels =
0210                 std::numeric_limits<mix_type>::max() / pow2(mix_type(MathsTraits::unitValue));
0211 
0212             if (m_numPixels > maxSaneNumPixels) {
0213                 qWarning() << "SANITY CHECK FAILED: KoMixColorOp got too many pixels to mix, the containing type may overflow";
0214                 qWarning() << "    " << ppVar(m_numPixels);
0215                 qWarning() << "    " << ppVar(maxSaneNumPixels);
0216             }
0217 #endif
0218 
0219             channels_type* dstColor = _CSTrait::nativeArray(dst);
0220 
0221             /**
0222              * FIXME: The following code relies on the unit value for floating point spaces being 1.0
0223              * We should be using the division functions in KoColorSpaceMaths for this, but right now
0224              * it is not clear how to call these functions.
0225              **/
0226             if (totalAlpha > 0) {
0227 
0228                 for (int i = 0; i < (int)_CSTrait::channels_nb; i++) {
0229                     if (i != _CSTrait::alpha_pos) {
0230 
0231                         mix_type v = safeDivideWithRound(totals[i], totalAlpha);
0232 
0233                         if (v > MathsTraits::max) {
0234                             v = MathsTraits::max;
0235                         }
0236                         if (v < MathsTraits::min) {
0237                             v = MathsTraits::min;
0238                         }
0239                         dstColor[ i ] = v;
0240                     }
0241                 }
0242 
0243                 if (_CSTrait::alpha_pos != -1) {
0244                     mix_type v = safeDivideWithRound(totalAlpha, normalizeFactor);
0245 
0246                     if (v > MathsTraits::max) {
0247                         v = MathsTraits::max;
0248                     }
0249                     if (v < MathsTraits::min) {
0250                         v = MathsTraits::min;
0251                     }
0252                     dstColor[ _CSTrait::alpha_pos ] = v;
0253                 }
0254             } else {
0255                 memset(dst, 0, sizeof(channels_type) * _CSTrait::channels_nb);
0256             }
0257         }
0258 
0259         template<class AbstractSource, class WeightsWrapper>
0260         void accumulateColors(AbstractSource source, WeightsWrapper weightsWrapper, int nColors) {
0261             // Compute the total for each channel by summing each colors multiplied by the weightlabcache
0262 
0263 #ifdef SANITY_CHECKS
0264             m_numPixels += nColors;
0265 #endif
0266 
0267             while (nColors--) {
0268                 const channels_type* color = _CSTrait::nativeArray(source.getPixel());
0269                 mix_type alphaTimesWeight;
0270 
0271                 if (_CSTrait::alpha_pos != -1) {
0272                     alphaTimesWeight = color[_CSTrait::alpha_pos];
0273                 } else {
0274                     alphaTimesWeight = MathsTraits::unitValue;
0275                 }
0276 
0277                 weightsWrapper.premultiplyAlphaWithWeight(alphaTimesWeight);
0278 
0279                 for (int i = 0; i < (int)_CSTrait::channels_nb; i++) {
0280                     if (i != _CSTrait::alpha_pos) {
0281                         totals[i] += color[i] * alphaTimesWeight;
0282                     }
0283                 }
0284 
0285                 totalAlpha += alphaTimesWeight;
0286                 source.nextPixel();
0287                 weightsWrapper.nextPixel();
0288             }
0289 
0290             normalizeFactor += weightsWrapper.normalizeFactor();
0291         }
0292 
0293         qint64 currentWeightsSum() const
0294         {
0295             return normalizeFactor;
0296         }
0297     };
0298 
0299     template<class AbstractSource, class WeightsWrapper>
0300     void mixColorsImpl(AbstractSource source, WeightsWrapper weightsWrapper, int nColors, quint8 *dst) const {
0301         MixDataResult result;
0302         result.accumulateColors(source, weightsWrapper, nColors);
0303         result.computeMixedColor(dst);
0304     }
0305 
0306 };
0307 
0308 template<class _CSTrait>
0309 class KoMixColorsOpImpl<_CSTrait>::MixerImpl : public KoMixColorsOp::Mixer
0310 {
0311 public:
0312     MixerImpl()
0313     {
0314     }
0315 
0316     void accumulate(const quint8 *data, const qint16 *weights, int weightSum, int nPixels) override
0317     {
0318         result.accumulateColors(PointerToArray(data, _CSTrait::pixelSize), WeightsWrapper(weights, weightSum), nPixels);
0319     }
0320 
0321     void accumulateAverage(const quint8 *data, int nPixels) override
0322     {
0323         result.accumulateColors(PointerToArray(data, _CSTrait::pixelSize), NoWeightsSurrogate(nPixels), nPixels);
0324     }
0325 
0326     void computeMixedColor(quint8 *data) override
0327     {
0328         result.computeMixedColor(data);
0329     }
0330 
0331     qint64 currentWeightsSum() const override
0332     {
0333         return result.currentWeightsSum();
0334     }
0335 
0336 private:
0337     MixDataResult result;
0338 };
0339 
0340 template<class _CSTrait>
0341 KoMixColorsOp::Mixer *KoMixColorsOpImpl<_CSTrait>::createMixer() const
0342 {
0343     return new MixerImpl();
0344 }
0345 
0346 #endif