File indexing completed on 2024-06-09 04:23:31

0001 /*
0002  * SPDX-FileCopyrightText: 2015 Thorsten Zachmann <zachmann@kde.org>
0003  * SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  */
0007 
0008 #ifndef KOOPTIMIZEDCOMPOSITEOPOVER128_H_
0009 #define KOOPTIMIZEDCOMPOSITEOPOVER128_H_
0010 
0011 #include "KoCompositeOpBase.h"
0012 #include "KoCompositeOpRegistry.h"
0013 #include "KoStreamedMath.h"
0014 
0015 #define NATIVE_OPACITY_OPAQUE KoColorSpaceMathsTraits<channels_type>::unitValue
0016 #define NATIVE_OPACITY_TRANSPARENT KoColorSpaceMathsTraits<channels_type>::zeroValue
0017 
0018 #define INFO_DEBUG 0
0019 
0020 template<typename channels_type, bool alphaLocked, bool allChannelsFlag>
0021 struct OverCompositor128 {
0022     struct ParamsWrapper {
0023         ParamsWrapper(const KoCompositeOp::ParameterInfo& params)
0024             : channelFlags(params.channelFlags)
0025         {
0026         }
0027         const QBitArray &channelFlags;
0028     };
0029 
0030     struct Pixel {
0031         channels_type red;
0032         channels_type green;
0033         channels_type blue;
0034         channels_type alpha;
0035     };
0036 
0037     // \see docs in AlphaDarkenCompositor32
0038     template<bool haveMask, bool src_aligned, typename _impl>
0039     static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const ParamsWrapper &oparams)
0040     {
0041 #if INFO_DEBUG
0042         static quint32 countTotal = 0;
0043         static quint32 countOne = 0;
0044         static quint32 countTwo = 0;
0045         static quint32 countThree = 0;
0046         static quint32 countFour = 0;
0047 
0048         if (++countTotal % 250000 == 0) {
0049             qInfo() << "count" << countOne << countTwo << countThree << countFour << countTotal << opacity;
0050         }
0051 #endif
0052         using float_v = typename KoStreamedMath<_impl>::float_v;
0053         using float_m = typename float_v::batch_bool_type;
0054 
0055         Q_UNUSED(oparams);
0056 
0057         float_v src_alpha;
0058         float_v dst_alpha;
0059 
0060         float_v src_c1;
0061         float_v src_c2;
0062         float_v src_c3;
0063 
0064         PixelWrapper<channels_type, _impl> dataWrapper;
0065         dataWrapper.read(src, src_c1, src_c2, src_c3, src_alpha);
0066 
0067         //bool haveOpacity = opacity != 1.0;
0068         const float_v opacity_norm_vec(opacity);
0069         src_alpha *= opacity_norm_vec;
0070 
0071         if (haveMask) {
0072             const float_v uint8MaxRec1((float)1.0 / 255);
0073             float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask);
0074             src_alpha *= mask_vec * uint8MaxRec1;
0075         }
0076 
0077         const float_v zeroValue(static_cast<float>(NATIVE_OPACITY_TRANSPARENT));
0078         // The source cannot change the colors in the destination,
0079         // since its fully transparent
0080         if (xsimd::all(src_alpha == zeroValue)) {
0081 #if INFO_DEBUG
0082             countFour++;
0083 #endif
0084             return;
0085         }
0086 
0087         float_v dst_c1;
0088         float_v dst_c2;
0089         float_v dst_c3;
0090 
0091         dataWrapper.read(dst, dst_c1, dst_c2, dst_c3, dst_alpha);
0092 
0093         float_v src_blend;
0094         float_v new_alpha;
0095 
0096         const float_v oneValue(1.0f);
0097         if (xsimd::all(dst_alpha == oneValue)) {
0098             new_alpha = dst_alpha;
0099             src_blend = src_alpha;
0100         } else if (xsimd::all(dst_alpha == zeroValue)) {
0101             new_alpha = src_alpha;
0102             src_blend = oneValue;
0103         } else {
0104             /**
0105              * The value of new_alpha can have *some* zero values,
0106              * which will result in NaN values while division.
0107              */
0108             new_alpha = dst_alpha + (oneValue - dst_alpha) * src_alpha;
0109             const float_m mask = (new_alpha == zeroValue);
0110             src_blend = src_alpha / new_alpha;
0111             src_blend = xsimd::set_zero(src_blend, mask);
0112         }
0113 
0114         if (!xsimd::all(src_blend == oneValue)) {
0115 #if INFO_DEBUG
0116             ++countOne;
0117 #endif
0118 
0119             dst_c1 = src_blend * (src_c1 - dst_c1) + dst_c1;
0120             dst_c2 = src_blend * (src_c2 - dst_c2) + dst_c2;
0121             dst_c3 = src_blend * (src_c3 - dst_c3) + dst_c3;
0122 
0123             dataWrapper.write(dst, dst_c1, dst_c2, dst_c3, new_alpha);
0124         } else {
0125 #if INFO_DEBUG
0126                 ++countTwo;
0127 #endif
0128             dataWrapper.write(dst, src_c1, src_c2, src_c3, new_alpha);
0129         }
0130     }
0131 
0132     template<bool haveMask, typename _impl>
0133     static ALWAYS_INLINE void compositeOnePixelScalar(const quint8 *src,
0134                                                       quint8 *dst,
0135                                                       const quint8 *mask,
0136                                                       float opacity,
0137                                                       const ParamsWrapper &oparams)
0138     {
0139         using namespace Arithmetic;
0140         const qint32 alpha_pos = 3;
0141 
0142         const auto *s = reinterpret_cast<const channels_type*>(src);
0143         auto *d = reinterpret_cast<channels_type*>(dst);
0144 
0145         float srcAlpha = s[alpha_pos];
0146         PixelWrapper<channels_type, _impl>::normalizeAlpha(srcAlpha);
0147         srcAlpha *= opacity;
0148 
0149         if (haveMask) {
0150             const float uint8Rec1 = 1.0f / 255.0f;
0151             srcAlpha *= float(*mask) * uint8Rec1;
0152         }
0153 
0154 #if INFO_DEBUG
0155         static int xx = 0;
0156         bool display = xx > 45 && xx < 50;
0157         if (display) {
0158             qInfo() << "O" << s[alpha_pos] << srcAlpha << haveMask << opacity;
0159         }
0160 #endif
0161 
0162         if (srcAlpha != 0.0f) {
0163 
0164             float dstAlpha = d[alpha_pos];
0165             PixelWrapper<channels_type, _impl>::normalizeAlpha(dstAlpha);
0166             float srcBlendNorm = 0.0f;
0167 
0168             if (alphaLocked || dstAlpha == 1.0f) {
0169                 srcBlendNorm = srcAlpha;
0170             } else if (dstAlpha == 0.0f) {
0171                 dstAlpha = srcAlpha;
0172                 srcBlendNorm = 1.0f;
0173 
0174                 if (!allChannelsFlag) {
0175                     KoStreamedMathFunctions::clearPixel<sizeof(Pixel)>(dst);
0176                 }
0177             } else {
0178                 dstAlpha += (1.0f - dstAlpha) * srcAlpha;
0179                 srcBlendNorm = srcAlpha / dstAlpha;
0180             }
0181 
0182 #if INFO_DEBUG
0183             if (display) {
0184                 qInfo() << "params" << srcBlendNorm << allChannelsFlag << alphaLocked << dstAlpha << haveMask;
0185             }
0186 #endif
0187             if(allChannelsFlag) {
0188                 if (srcBlendNorm == 1.0f) {
0189                     if (!alphaLocked) {
0190                         KoStreamedMathFunctions::copyPixel<sizeof(Pixel)>(src, dst);
0191                     } else {
0192                         d[0] = s[0];
0193                         d[1] = s[1];
0194                         d[2] = s[2];
0195                     }
0196                 } else if (srcBlendNorm != 0.0f){
0197 #if INFO_DEBUG
0198                     if (display) {
0199                         qInfo() << "calc" << s[0] << d[0] << srcBlendNorm * (s[0] - d[0]) + d[0] << s[0] - d[0] << srcBlendNorm * (s[0] - d[0]) << srcBlendNorm;
0200                     }
0201 #endif
0202 
0203                     d[0] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[0], s[0], srcBlendNorm);
0204                     d[1] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[1], s[1], srcBlendNorm);
0205                     d[2] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[2], s[2], srcBlendNorm);
0206                 }
0207             } else {
0208                 const QBitArray &channelFlags = oparams.channelFlags;
0209 
0210                 if (srcBlendNorm == 1.0f) {
0211                     if(channelFlags.at(0)) d[0] = s[0];
0212                     if(channelFlags.at(1)) d[1] = s[1];
0213                     if(channelFlags.at(2)) d[2] = s[2];
0214                 } else if (srcBlendNorm != 0.0f) {
0215                     if(channelFlags.at(0)) d[0] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[0], s[0], srcBlendNorm);
0216                     if(channelFlags.at(1)) d[1] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[1], s[1], srcBlendNorm);;
0217                     if(channelFlags.at(2)) d[2] = PixelWrapper<channels_type, _impl>::lerpMixedUintFloat(d[2], s[2], srcBlendNorm);;
0218                 }
0219             }
0220 
0221             if (!alphaLocked) {
0222                 PixelWrapper<channels_type, _impl>::denormalizeAlpha(dstAlpha);
0223                 d[alpha_pos] = PixelWrapper<channels_type, _impl>::roundFloatToUint(dstAlpha);
0224             }
0225 #if INFO_DEBUG
0226             if (display) {
0227                 qInfo() << "result" << d[0] << d[1] << d[2] << d[3];
0228             }
0229             ++xx;
0230 #endif
0231         }
0232     }
0233 };
0234 
0235 /**
0236  * An optimized version of a composite op for the use in 16 byte
0237  * colorspaces with alpha channel placed at the last byte of
0238  * the pixel: C1_C2_C3_A.
0239  */
0240 template<typename _impl>
0241 class KoOptimizedCompositeOpOver128 : public KoCompositeOp
0242 {
0243 public:
0244     KoOptimizedCompositeOpOver128(const KoColorSpace* cs)
0245         : KoCompositeOp(cs, COMPOSITE_OVER, KoCompositeOp::categoryMix()) {}
0246 
0247     using KoCompositeOp::composite;
0248 
0249     void composite(const KoCompositeOp::ParameterInfo& params) const override
0250     {
0251         if(params.maskRowStart) {
0252             composite<true>(params);
0253         } else {
0254             composite<false>(params);
0255         }
0256     }
0257 
0258     template <bool haveMask>
0259     inline void composite(const KoCompositeOp::ParameterInfo& params) const {
0260         if (params.channelFlags.isEmpty() ||
0261             params.channelFlags == QBitArray(4, true)) {
0262 
0263             KoStreamedMath<_impl>::template genericComposite128<haveMask, false, OverCompositor128<float, false, true> >(params);
0264         } else {
0265             const bool allChannelsFlag =
0266                 params.channelFlags.at(0) &&
0267                 params.channelFlags.at(1) &&
0268                 params.channelFlags.at(2);
0269 
0270             const bool alphaLocked =
0271                 !params.channelFlags.at(3);
0272 
0273             if (allChannelsFlag && alphaLocked) {
0274                 KoStreamedMath<_impl>::template genericComposite128_novector<haveMask, false, OverCompositor128<float, true, true> >(params);
0275             } else if (!allChannelsFlag && !alphaLocked) {
0276                 KoStreamedMath<_impl>::template genericComposite128_novector<haveMask, false, OverCompositor128<float, false, false> >(params);
0277             } else /*if (!allChannelsFlag && alphaLocked) */{
0278                 KoStreamedMath<_impl>::template genericComposite128_novector<haveMask, false, OverCompositor128<float, true, false> >(params);
0279             }
0280         }
0281     }
0282 };
0283 
0284 template<typename _impl>
0285 class KoOptimizedCompositeOpOverU64 : public KoCompositeOp
0286 {
0287 public:
0288     KoOptimizedCompositeOpOverU64(const KoColorSpace* cs)
0289         : KoCompositeOp(cs, COMPOSITE_OVER, KoCompositeOp::categoryMix()) {}
0290 
0291     using KoCompositeOp::composite;
0292 
0293     void composite(const KoCompositeOp::ParameterInfo& params) const override
0294     {
0295         if(params.maskRowStart) {
0296             composite<true>(params);
0297         } else {
0298             composite<false>(params);
0299         }
0300     }
0301 
0302     template <bool haveMask>
0303     inline void composite(const KoCompositeOp::ParameterInfo& params) const {
0304         if (params.channelFlags.isEmpty() ||
0305             params.channelFlags == QBitArray(4, true)) {
0306 
0307             KoStreamedMath<_impl>::template genericComposite64<haveMask, false, OverCompositor128<quint16, false, true> >(params);
0308         } else {
0309             const bool allChannelsFlag =
0310                 params.channelFlags.at(0) &&
0311                 params.channelFlags.at(1) &&
0312                 params.channelFlags.at(2);
0313 
0314             const bool alphaLocked =
0315                 !params.channelFlags.at(3);
0316 
0317             if (allChannelsFlag && alphaLocked) {
0318                 KoStreamedMath<_impl>::template genericComposite64_novector<haveMask, false, OverCompositor128<quint16, true, true> >(params);
0319             } else if (!allChannelsFlag && !alphaLocked) {
0320                 KoStreamedMath<_impl>::template genericComposite64_novector<haveMask, false, OverCompositor128<quint16, false, false> >(params);
0321             } else /*if (!allChannelsFlag && alphaLocked) */{
0322                 KoStreamedMath<_impl>::template genericComposite64_novector<haveMask, false, OverCompositor128<quint16, true, false> >(params);
0323             }
0324         }
0325     }
0326 };
0327 
0328 #endif // KOOPTIMIZEDCOMPOSITEOPOVER128_H_