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