File indexing completed on 2024-12-22 04:11:40

0001 /*
0002  * SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
0003  * SPDX-FileCopyrightText: 2011 Silvio Heinrich <plassy@web.de>
0004  *  SPDX-FileCopyrightText: 2022 L. E. Segovia <amy@amyspark.me>
0005  *
0006  * SPDX-License-Identifier: LGPL-2.0-or-later
0007  */
0008 
0009 #ifndef KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_
0010 #define KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_
0011 
0012 #include "KoCompositeOpBase.h"
0013 #include "KoCompositeOpRegistry.h"
0014 #include "KoStreamedMath.h"
0015 #include "KoAlphaDarkenParamsWrapper.h"
0016 
0017 template<typename channels_type, typename pixel_type, typename ParamsWrapperT>
0018 struct AlphaDarkenCompositor32 {
0019     using ParamsWrapper = ParamsWrapperT;
0020 
0021     /**
0022      * This is a vector equivalent of compositeOnePixelScalar(). It is considered
0023      * to process float_v::size pixels in a single pass.
0024      *
0025      * o the \p haveMask parameter points whether the real (non-null) mask
0026      *   pointer is passed to the function.
0027      * o the \p src pointer may be aligned to vector boundary or may be
0028      *   not. In case not, it must be pointed with a special parameter
0029      *   \p src_aligned.
0030      * o the \p dst pointer must always(!) be aligned to the boundary
0031      *   of a streaming vector. Unaligned writes are really expensive.
0032      * o This function is *never* used if HAVE_XSIMD is not present
0033      */
0034     template<bool haveMask, bool src_aligned, typename _impl>
0035     static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const ParamsWrapper &oparams)
0036     {
0037         using float_v = typename KoStreamedMath<_impl>::float_v;
0038         using float_m = typename float_v::batch_bool_type;
0039 
0040         // we don't use directly passed value
0041         Q_UNUSED(opacity);
0042 
0043         // instead we use value calculated by ParamsWrapper
0044         opacity = oparams.opacity;
0045         const float_v opacity_vec(255.0f * opacity);
0046 
0047         const float_v average_opacity_vec(255.0 * oparams.averageOpacity);
0048         const float_v flow_norm_vec(oparams.flow);
0049 
0050         const float_v uint8MaxRec2(1.0f / (255.0f * 255.0f));
0051         const float_v uint8MaxRec1(1.0f / 255.0f);
0052         const float_v uint8Max(255.0f);
0053         const float_v zeroValue(0);
0054 
0055         float_v src_alpha = KoStreamedMath<_impl>::template fetch_alpha_32<src_aligned>(src);
0056 
0057         const float_v msk_norm_alpha = [&]() {
0058             if (haveMask) {
0059                 const float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask);
0060                 return src_alpha * mask_vec * uint8MaxRec2;
0061             } else {
0062                 return src_alpha * uint8MaxRec1;
0063             }
0064         }();
0065 
0066         float_v dst_alpha = KoStreamedMath<_impl>::template fetch_alpha_32<true>(dst);
0067         src_alpha = msk_norm_alpha * opacity_vec;
0068 
0069         const float_m empty_dst_pixels_mask = dst_alpha == zeroValue;
0070 
0071         float_v src_c1;
0072         float_v src_c2;
0073         float_v src_c3;
0074 
0075         float_v dst_c1;
0076         float_v dst_c2;
0077         float_v dst_c3;
0078 
0079         KoStreamedMath<_impl>::template fetch_colors_32<src_aligned>(src, src_c1, src_c2, src_c3);
0080 
0081         const bool srcAlphaIsZero = xsimd::all(src_alpha == zeroValue);
0082         if (srcAlphaIsZero) return;
0083 
0084         const bool dstAlphaIsZero = xsimd::all(empty_dst_pixels_mask);
0085 
0086         const float_v dst_blend = src_alpha * uint8MaxRec1;
0087 
0088         const bool srcAlphaIsUnit = xsimd::all(src_alpha == uint8Max);
0089 
0090         if (dstAlphaIsZero) {
0091             dst_c1 = src_c1;
0092             dst_c2 = src_c2;
0093             dst_c3 = src_c3;
0094         } else if (srcAlphaIsUnit) {
0095             const bool dstAlphaIsUnit = xsimd::all(dst_alpha == uint8Max);
0096             if (dstAlphaIsUnit) {
0097                 memcpy(dst, src, 4 * float_v::size);
0098                 return;
0099             } else {
0100                 dst_c1 = src_c1;
0101                 dst_c2 = src_c2;
0102                 dst_c3 = src_c3;
0103             }
0104         } else if (xsimd::none(empty_dst_pixels_mask)) {
0105             KoStreamedMath<_impl>::template fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
0106             dst_c1 = dst_blend * (src_c1 - dst_c1) + dst_c1;
0107             dst_c2 = dst_blend * (src_c2 - dst_c2) + dst_c2;
0108             dst_c3 = dst_blend * (src_c3 - dst_c3) + dst_c3;
0109         } else {
0110             KoStreamedMath<_impl>::template fetch_colors_32<true>(dst, dst_c1, dst_c2, dst_c3);
0111             dst_c1 = xsimd::select(empty_dst_pixels_mask, src_c1, dst_blend * (src_c1 - dst_c1) + dst_c1);
0112             dst_c2 = xsimd::select(empty_dst_pixels_mask, src_c2, dst_blend * (src_c2 - dst_c2) + dst_c2);
0113             dst_c3 = xsimd::select(empty_dst_pixels_mask, src_c3, dst_blend * (src_c3 - dst_c3) + dst_c3);
0114         }
0115 
0116         const float_v fullFlowAlpha = [&]() {
0117             if (oparams.averageOpacity > opacity) {
0118                 const float_m fullFlowAlpha_mask = average_opacity_vec > dst_alpha;
0119 
0120                 if (xsimd::none(fullFlowAlpha_mask)) {
0121                     return dst_alpha;
0122                 } else {
0123                     const float_v reverse_blend = dst_alpha / average_opacity_vec;
0124                     const float_v opt1 = (average_opacity_vec - src_alpha) * reverse_blend + src_alpha;
0125                     return xsimd::select(fullFlowAlpha_mask, opt1, dst_alpha);
0126                 }
0127             } else {
0128                 const float_m fullFlowAlpha_mask = opacity_vec > dst_alpha;
0129 
0130                 if (xsimd::none(fullFlowAlpha_mask)) {
0131                     return dst_alpha;
0132                 } else {
0133                     const float_v opt1 = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha;
0134                     return xsimd::select(fullFlowAlpha_mask, opt1, dst_alpha);
0135                 }
0136             }
0137         }();
0138 
0139         dst_alpha = [&]() {
0140             if (oparams.flow == 1.0) {
0141                 return fullFlowAlpha;
0142             } else {
0143                 const float_v zeroFlowAlpha = ParamsWrapper::calculateZeroFlowAlpha(src_alpha, dst_alpha, uint8MaxRec1);
0144                 return (fullFlowAlpha - zeroFlowAlpha) * flow_norm_vec + zeroFlowAlpha;
0145             }
0146         }();
0147 
0148         KoStreamedMath<_impl>::write_channels_32(dst, dst_alpha, dst_c1, dst_c2, dst_c3);
0149     }
0150 
0151     /**
0152      * Composes one pixel of the source into the destination
0153      */
0154     template <bool haveMask, typename _impl>
0155     static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const ParamsWrapper &oparams)
0156     {
0157         using namespace Arithmetic;
0158         const qint32 alpha_pos = 3;
0159 
0160         const float uint8Rec1 = 1.0f / 255.0f;
0161         const float uint8Rec2 = 1.0f / (255.0f * 255.0f);
0162         const float uint8Max = 255.0;
0163 
0164         // we don't use directly passed value
0165         Q_UNUSED(opacity);
0166 
0167         // instead we use value calculated by ParamsWrapper
0168         opacity = oparams.opacity;
0169 
0170         const quint8 dstAlphaInt = dst[alpha_pos];
0171         const float dstAlphaNorm = dstAlphaInt ? dstAlphaInt * uint8Rec1 : 0.0;
0172         const float mskAlphaNorm = [&]() {
0173             if (haveMask) {
0174                 return float(*mask) * uint8Rec2 * src[alpha_pos];
0175             } else {
0176                 return src[alpha_pos] * uint8Rec1;
0177             }
0178         }();
0179         const float srcAlphaNorm = mskAlphaNorm * opacity;
0180 
0181         if (dstAlphaInt != 0) {
0182             dst[0] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[0], src[0], srcAlphaNorm);
0183             dst[1] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[1], src[1], srcAlphaNorm);
0184             dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcAlphaNorm);
0185         } else {
0186             const auto *s = reinterpret_cast<const pixel_type*>(src);
0187             auto *d = reinterpret_cast<pixel_type*>(dst);
0188             *d = *s;
0189         }
0190 
0191 
0192         const float flow = oparams.flow;
0193         const float averageOpacity = oparams.averageOpacity;
0194 
0195         const float fullFlowAlpha = [&]() {
0196             if (averageOpacity > opacity) {
0197                 return averageOpacity > dstAlphaNorm ? lerp(srcAlphaNorm, averageOpacity, dstAlphaNorm / averageOpacity) : dstAlphaNorm;
0198             } else {
0199                 return opacity > dstAlphaNorm ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm;
0200             }
0201         }();
0202 
0203         const float dstAlpha = [&](){
0204             if (flow == 1.0f) {
0205                 return fullFlowAlpha * uint8Max;
0206             } else {
0207                 const float zeroFlowAlpha = ParamsWrapper::calculateZeroFlowAlpha(srcAlphaNorm, dstAlphaNorm);
0208                 return lerp(zeroFlowAlpha, fullFlowAlpha, flow) * uint8Max;
0209             }
0210         }();
0211 
0212         dst[alpha_pos] = KoStreamedMath<_impl>::round_float_to_u8(dstAlpha);
0213     }
0214 };
0215 
0216 /**
0217  * An optimized version of a composite op for the use in 4 byte
0218  * colorspaces with alpha channel placed at the last byte of
0219  * the pixel: C1_C2_C3_A.
0220  */
0221 template<typename _impl, class ParamsWrapper>
0222 class KoOptimizedCompositeOpAlphaDarken32Impl : public KoCompositeOp
0223 {
0224 public:
0225     KoOptimizedCompositeOpAlphaDarken32Impl(const KoColorSpace* cs)
0226         : KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, KoCompositeOp::categoryMix()) {}
0227 
0228     using KoCompositeOp::composite;
0229 
0230     void composite(const KoCompositeOp::ParameterInfo& params) const override
0231     {
0232         if(params.maskRowStart) {
0233             KoStreamedMath<_impl>::template genericComposite32<true, true, AlphaDarkenCompositor32<quint8, quint32, ParamsWrapper> >(params);
0234         } else {
0235             KoStreamedMath<_impl>::template genericComposite32<false, true, AlphaDarkenCompositor32<quint8, quint32, ParamsWrapper> >(params);
0236         }
0237     }
0238 };
0239 
0240 template<typename _impl = xsimd::current_arch>
0241 class KoOptimizedCompositeOpAlphaDarkenHard32 :
0242         public KoOptimizedCompositeOpAlphaDarken32Impl<_impl, KoAlphaDarkenParamsWrapperHard>
0243 {
0244 public:
0245     KoOptimizedCompositeOpAlphaDarkenHard32(const KoColorSpace *cs)
0246         : KoOptimizedCompositeOpAlphaDarken32Impl<_impl, KoAlphaDarkenParamsWrapperHard>(cs) {
0247     }
0248 };
0249 
0250 template<typename _impl = xsimd::current_arch>
0251 class KoOptimizedCompositeOpAlphaDarkenCreamy32 :
0252         public KoOptimizedCompositeOpAlphaDarken32Impl<_impl, KoAlphaDarkenParamsWrapperCreamy>
0253 {
0254 public:
0255     KoOptimizedCompositeOpAlphaDarkenCreamy32(const KoColorSpace *cs)
0256         : KoOptimizedCompositeOpAlphaDarken32Impl<_impl, KoAlphaDarkenParamsWrapperCreamy>(cs) {
0257     }
0258 };
0259 
0260 
0261 #endif // KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_