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

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