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_