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

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 KO_CONVOLUTION_OP_IMPL_H
0009 #define KO_CONVOLUTION_OP_IMPL_H
0010 
0011 #include "DebugPigment.h"
0012 #include "KoColorSpaceMaths.h"
0013 #include "KoConvolutionOp.h"
0014 #include "KoColorSpaceTraits.h"
0015 
0016 template<class _CSTrait>
0017 class KoConvolutionOpImpl : public KoConvolutionOp
0018 {
0019     typedef typename KoColorSpaceMathsTraits<typename _CSTrait::channels_type>::compositetype compositetype;
0020     typedef typename _CSTrait::channels_type channels_type;
0021 public:
0022 
0023     KoConvolutionOpImpl() { }
0024 
0025     ~KoConvolutionOpImpl() override { }
0026 
0027     /**
0028      * Calculates a weighted average of the pixels, mentioned in @p colors
0029      * using weight values from @p kernelValues
0030      *
0031      * Note:
0032      * It behaves in a quite unclear way, when at least one pixel is
0033      * fully transparent. There are three cases:
0034      * Case A) None of the pixels is fully transparent.
0035      *    * Every color channel AND alpha channel of @p dst stores a sum
0036      *      of the corresponding channels from @p colors, divided by @p factor
0037      *      and incremented by @p offset
0038      * Case B) At least one pixel of @p colors is transparent and @p factor
0039      * stores a weight of the kernel (sum of it's items).
0040      *    * Every color channel of @p dst stores a sum of the corresponding
0041      *      channels from non-transparent pixels, divided by a weight
0042      *      of non-transparent pixels and incremented by @p offset.
0043      *    * Alpha channel of @p dst stores a sum of the corresponding
0044      *      channels from non-transparent pixels, divided by a weight
0045      *      of all the pixels (equals to @p factor) and incremented
0046      *      by @p offset.
0047      * Case C) At least one pixel of @p colors is transparent and @p factor
0048      * is set to an arbitrary value.
0049      *    * Every color channel of @p dst stores a sum of the corresponding
0050      *      channels from non-transparent pixels, divided by a "scaled
0051      *      down factor" and incremented by @p offset. "Scaled
0052      *      down factor" is calculated in the following way:
0053      *
0054      *                                   [weight of non-transparent pixels]
0055      *      scaledDownFactor = @p factor * ----------------------------------
0056      *                                       [weight of all the pixels]
0057      *
0058      *    * Alpha channel of @p dst stores a sum of the corresponding
0059      *      channels from non-transparent pixels, divided by unscaled
0060      *      @p factor and incremented by @p offset.
0061      */
0062 
0063     void convolveColors(const quint8* const* colors, const qreal* kernelValues, quint8 *dst, qreal factor, qreal offset, qint32 nPixels, const QBitArray & channelFlags) const override {
0064 
0065         // Create and initialize to 0 the array of totals
0066         qreal totals[_CSTrait::channels_nb];
0067 
0068         qreal totalWeight = 0;
0069         qreal totalWeightTransparent = 0;
0070 
0071         memset(totals, 0, sizeof(qreal) * _CSTrait::channels_nb);
0072 
0073         for (; nPixels--; colors++, kernelValues++) {
0074             qreal weight = *kernelValues;
0075             const channels_type* color = _CSTrait::nativeArray(*colors);
0076             if (weight != 0) {
0077                 if (_CSTrait::opacityU8(*colors) == 0) {
0078                     totalWeightTransparent += weight;
0079                 } else {
0080                     for (uint i = 0; i < _CSTrait::channels_nb; i++) {
0081                         totals[i] += color[i] * weight;
0082                     }
0083                 }
0084                 totalWeight += weight;
0085             }
0086         }
0087 
0088         typename _CSTrait::channels_type* dstColor = _CSTrait::nativeArray(dst);
0089 
0090         bool allChannels = channelFlags.isEmpty();
0091         Q_ASSERT(allChannels || channelFlags.size() == (int)_CSTrait::channels_nb);
0092         if (totalWeightTransparent == 0) {
0093             // Case A)
0094             for (uint i = 0; i < _CSTrait::channels_nb; i++) {
0095                 if (allChannels || channelFlags.testBit(i)) {
0096                     compositetype v = totals[i] / factor + offset;
0097                     dstColor[ i ] = CLAMP(v, KoColorSpaceMathsTraits<channels_type>::min,
0098                                           KoColorSpaceMathsTraits<channels_type>::max);
0099                 }
0100             }
0101         } else if (totalWeightTransparent != totalWeight) {
0102             if (totalWeight == factor) {
0103                 // Case B)
0104                 qint64 a = (totalWeight - totalWeightTransparent);
0105                 for (uint i = 0; i < _CSTrait::channels_nb; i++) {
0106                     if (allChannels || channelFlags.testBit(i)) {
0107                         if (i == (uint)_CSTrait::alpha_pos) {
0108                             compositetype v = totals[i] / totalWeight + offset;
0109                             dstColor[ i ] = CLAMP(v, KoColorSpaceMathsTraits<channels_type>::min,
0110                                                   KoColorSpaceMathsTraits<channels_type>::max);
0111                         } else {
0112                             compositetype v = totals[i] / a + offset;
0113                             dstColor[ i ] = CLAMP(v, KoColorSpaceMathsTraits<channels_type>::min,
0114                                                   KoColorSpaceMathsTraits<channels_type>::max);
0115                         }
0116                     }
0117                 }
0118             } else {
0119                 // Case C)
0120                 qreal a = qreal(totalWeight) / (factor * (totalWeight - totalWeightTransparent));     // use qreal as it easily saturate
0121                 for (uint i = 0; i < _CSTrait::channels_nb; i++) {
0122                     if (allChannels || channelFlags.testBit(i)) {
0123                         if (i == (uint)_CSTrait::alpha_pos) {
0124                             compositetype v = totals[i] / factor + offset;
0125                             dstColor[ i ] = CLAMP(v, KoColorSpaceMathsTraits<channels_type>::min,
0126                                                   KoColorSpaceMathsTraits<channels_type>::max);
0127                         } else {
0128                             compositetype v = (compositetype)(totals[i] * a + offset);
0129                             dstColor[ i ] = CLAMP(v, KoColorSpaceMathsTraits<channels_type>::min,
0130                                                   KoColorSpaceMathsTraits<channels_type>::max);
0131                         }
0132                     }
0133                 }
0134             }
0135         }
0136 
0137     }
0138 };
0139 
0140 #endif