diff --git a/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h b/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h index 51d5d9cfa6..894781692b 100644 --- a/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h +++ b/libs/pigment/compositeops/KoCompositeOpAlphaDarken.h @@ -1,125 +1,125 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOCOMPOSITEOPALPHADARKEN_H_ #define KOCOMPOSITEOPALPHADARKEN_H_ #include "KoCompositeOpFunctions.h" #include "KoCompositeOpBase.h" #include /** * A template version of the alphadarken composite operation to use in colorspaces */ template class KoCompositeOpAlphaDarken: public KoCompositeOp { typedef typename Traits::channels_type channels_type; static const qint32 channels_nb = Traits::channels_nb; static const qint32 alpha_pos = Traits::alpha_pos; public: KoCompositeOpAlphaDarken(const KoColorSpace* cs): KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) { } using KoCompositeOp::composite; void composite(const KoCompositeOp::ParameterInfo& params) const override { if(params.maskRowStart != 0) genericComposite(params); else genericComposite(params); } template void genericComposite(const KoCompositeOp::ParameterInfo& params) const { using namespace Arithmetic; qint32 srcInc = (params.srcRowStride == 0) ? 0 : channels_nb; channels_type flow = scale(params.flow); - channels_type opacity = scale(params.opacity); + channels_type opacity = mul(flow, scale(params.opacity)); quint8* dstRowStart = params.dstRowStart; const quint8* srcRowStart = params.srcRowStart; const quint8* maskRowStart = params.maskRowStart; for(quint32 r=params.rows; r>0; --r) { const channels_type* src = reinterpret_cast(srcRowStart); channels_type* dst = reinterpret_cast(dstRowStart); const quint8* mask = maskRowStart; for(qint32 c=params.cols; c>0; --c) { channels_type srcAlpha = (alpha_pos == -1) ? unitValue() : src[alpha_pos]; channels_type dstAlpha = (alpha_pos == -1) ? unitValue() : dst[alpha_pos]; channels_type mskAlpha = useMask ? mul(scale(*mask), srcAlpha) : srcAlpha; srcAlpha = mul(mskAlpha, opacity); if(dstAlpha != zeroValue()) { for(qint32 i=0; i (*params.lastOpacity); + channels_type averageOpacity = mul(flow, scale(*params.lastOpacity)); if (averageOpacity > opacity) { channels_type reverseBlend = KoColorSpaceMaths::divide(dstAlpha, averageOpacity); fullFlowAlpha = averageOpacity > dstAlpha ? lerp(srcAlpha, averageOpacity, reverseBlend) : dstAlpha; } else { fullFlowAlpha = opacity > dstAlpha ? lerp(dstAlpha, opacity, mskAlpha) : dstAlpha; } if (params.flow == 1.0) { dstAlpha = fullFlowAlpha; } else { - channels_type zeroFlowAlpha = dstAlpha; + channels_type zeroFlowAlpha = unionShapeOpacity(srcAlpha, dstAlpha); dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow); } dst[alpha_pos] = dstAlpha; } src += srcInc; dst += channels_nb; if(useMask) ++mask; } srcRowStart += params.srcRowStride; dstRowStart += params.dstRowStride; maskRowStart += params.maskRowStride; } } }; #endif // KOCOMPOSITEOPALPHADARKEN_H_ diff --git a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h index 367357d927..4c020aba19 100644 --- a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h +++ b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken128.h @@ -1,218 +1,228 @@ /* * Copyright (c) 2016 Thorsten Zachmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H #define KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H #include "KoCompositeOpBase.h" #include "KoCompositeOpRegistry.h" #include "KoStreamedMath.h" template struct AlphaDarkenCompositor128 { struct OptionalParams { OptionalParams(const KoCompositeOp::ParameterInfo& params) : flow(params.flow) - , averageOpacity(*params.lastOpacity) + , averageOpacity(*params.lastOpacity * params.flow) + , premultipliedOpacity(params.opacity * params.flow) { } float flow; float averageOpacity; + float premultipliedOpacity; }; struct Pixel { channels_type red; channels_type green; channels_type blue; channels_type alpha; }; /** * This is a vector equivalent of compositeOnePixelScalar(). It is considered * to process Vc::float_v::size() pixels in a single pass. * * o the \p haveMask parameter points whether the real (non-null) mask * pointer is passed to the function. * o the \p src pointer may be aligned to vector boundary or may be * not. In case not, it must be pointed with a special parameter * \p src_aligned. * o the \p dst pointer must always(!) be aligned to the boundary * of a streaming vector. Unaligned writes are really expensive. * o This function is *never* used if HAVE_VC is not present */ template static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { const Pixel *sp = reinterpret_cast(src); Pixel *dp = reinterpret_cast(dst); Vc::float_v src_c1; Vc::float_v src_c2; Vc::float_v src_c3; Vc::float_v src_alpha; const Vc::float_v::IndexType indexes(Vc::IndexesFromZero); Vc::InterleavedMemoryWrapper data(const_cast(sp)); tie(src_c1, src_c2, src_c3, src_alpha) = data[indexes]; Vc::float_v msk_norm_alpha; if (haveMask) { const Vc::float_v uint8Rec1((float)1.0 / 255.0); Vc::float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask); msk_norm_alpha = mask_vec * uint8Rec1 * src_alpha; } else { msk_norm_alpha = src_alpha; } - Vc::float_v opacity_vec(opacity); + // we don't use directly passed value + Q_UNUSED(opacity); + + // instead we should use opacity premultiplied by flow + opacity = oparams.premultipliedOpacity; + Vc::float_v opacity_vec(oparams.premultipliedOpacity); src_alpha = msk_norm_alpha * opacity_vec; const Vc::float_v zeroValue(KoColorSpaceMathsTraits::zeroValue); Vc::float_v dst_c1; Vc::float_v dst_c2; Vc::float_v dst_c3; Vc::float_v dst_alpha; Vc::InterleavedMemoryWrapper dataDest(dp); tie(dst_c1, dst_c2, dst_c3, dst_alpha) = dataDest[indexes]; Vc::float_m empty_dst_pixels_mask = dst_alpha == zeroValue; if (!empty_dst_pixels_mask.isFull()) { if (empty_dst_pixels_mask.isEmpty()) { dst_c1 = (src_c1 - dst_c1) * src_alpha + dst_c1; dst_c2 = (src_c2 - dst_c2) * src_alpha + dst_c2; dst_c3 = (src_c3 - dst_c3) * src_alpha + dst_c3; } else { dst_c1(empty_dst_pixels_mask) = src_c1; dst_c2(empty_dst_pixels_mask) = src_c2; dst_c3(empty_dst_pixels_mask) = src_c3; Vc::float_m not_empty_dst_pixels_mask = !empty_dst_pixels_mask; dst_c1(not_empty_dst_pixels_mask) = (src_c1 - dst_c1) * src_alpha + dst_c1; dst_c2(not_empty_dst_pixels_mask) = (src_c2 - dst_c2) * src_alpha + dst_c2; dst_c3(not_empty_dst_pixels_mask) = (src_c3 - dst_c3) * src_alpha + dst_c3; } } else { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } Vc::float_v fullFlowAlpha(dst_alpha); if (oparams.averageOpacity > opacity) { Vc::float_v average_opacity_vec(oparams.averageOpacity); Vc::float_m fullFlowAlpha_mask = average_opacity_vec > dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = (average_opacity_vec - src_alpha) * (dst_alpha / average_opacity_vec) + src_alpha; } else { Vc::float_m fullFlowAlpha_mask = opacity_vec > dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha; } if (oparams.flow == 1.0) { dst_alpha = fullFlowAlpha; } else { - Vc::float_v zeroFlowAlpha = dst_alpha; + Vc::float_v zeroFlowAlpha = src_alpha + dst_alpha - src_alpha * dst_alpha; Vc::float_v flow_norm_vec(oparams.flow); dst_alpha = (fullFlowAlpha - zeroFlowAlpha) * flow_norm_vec + zeroFlowAlpha; } dataDest[indexes] = tie(dst_c1, dst_c2, dst_c3, dst_alpha); } /** * Composes one pixel of the source into the destination */ template static ALWAYS_INLINE void compositeOnePixelScalar(const quint8 *s, quint8 *d, const quint8 *mask, float opacity, const OptionalParams &oparams) { using namespace Arithmetic; const qint32 alpha_pos = 3; const channels_type *src = reinterpret_cast(s); channels_type *dst = reinterpret_cast(d); float dstAlphaNorm = dst[alpha_pos]; const float uint8Rec1 = 1.0 / 255.0; float mskAlphaNorm = haveMask ? float(*mask) * uint8Rec1 * src[alpha_pos] : src[alpha_pos]; + Q_UNUSED(opacity); + opacity = oparams.premultipliedOpacity; + float srcAlphaNorm = mskAlphaNorm * opacity; if (dstAlphaNorm != 0) { dst[0] = lerp(dst[0], src[0], srcAlphaNorm); dst[1] = lerp(dst[1], src[1], srcAlphaNorm); dst[2] = lerp(dst[2], src[2], srcAlphaNorm); } else { const pixel_type *s = reinterpret_cast(src); pixel_type *d = reinterpret_cast(dst); *d = *s; } float flow = oparams.flow; float averageOpacity = oparams.averageOpacity; float fullFlowAlpha; if (averageOpacity > opacity) { fullFlowAlpha = averageOpacity > dstAlphaNorm ? lerp(srcAlphaNorm, averageOpacity, dstAlphaNorm / averageOpacity) : dstAlphaNorm; } else { fullFlowAlpha = opacity > dstAlphaNorm ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm; } if (flow == 1.0) { dst[alpha_pos] = fullFlowAlpha; } else { - float zeroFlowAlpha = dstAlphaNorm; + float zeroFlowAlpha = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm); dst[alpha_pos] = lerp(zeroFlowAlpha, fullFlowAlpha, flow); } } }; /** * An optimized version of a composite op for the use in 16 byte * colorspaces with alpha channel placed at the last byte of * the pixel: C1_C2_C3_A. */ template class KoOptimizedCompositeOpAlphaDarken128 : public KoCompositeOp { public: KoOptimizedCompositeOpAlphaDarken128(const KoColorSpace* cs) : KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) {} using KoCompositeOp::composite; virtual void composite(const KoCompositeOp::ParameterInfo& params) const { if(params.maskRowStart) { KoStreamedMath<_impl>::template genericComposite128 >(params); } else { KoStreamedMath<_impl>::template genericComposite128 >(params); } } }; #endif // KOOPTIMIZEDCOMPOSITEOPALPHADARKEN128_H diff --git a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h index 6932d1aac3..41de2b363d 100644 --- a/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h +++ b/libs/pigment/compositeops/KoOptimizedCompositeOpAlphaDarken32.h @@ -1,258 +1,269 @@ /* * Copyright (c) 2006 Cyrille Berger * Copyright (c) 2011 Silvio Heinrich * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_ #define KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_ #include "KoCompositeOpBase.h" #include "KoCompositeOpRegistry.h" #include #include "KoStreamedMath.h" template struct AlphaDarkenCompositor32 { struct OptionalParams { OptionalParams(const KoCompositeOp::ParameterInfo& params) : flow(params.flow), - averageOpacity(*params.lastOpacity) + averageOpacity(*params.lastOpacity * params.flow), + premultipliedOpacity(params.opacity * params.flow) { } float flow; float averageOpacity; + float premultipliedOpacity; }; /** * This is a vector equivalent of compositeOnePixelScalar(). It is considered * to process Vc::float_v::size() pixels in a single pass. * * o the \p haveMask parameter points whether the real (non-null) mask * pointer is passed to the function. * o the \p src pointer may be aligned to vector boundary or may be * not. In case not, it must be pointed with a special parameter * \p src_aligned. * o the \p dst pointer must always(!) be aligned to the boundary * of a streaming vector. Unaligned writes are really expensive. * o This function is *never* used if HAVE_VC is not present */ template static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { Vc::float_v src_alpha; Vc::float_v dst_alpha; - Vc::float_v opacity_vec(255.0 * opacity); + // we don't use directly passed value + Q_UNUSED(opacity); + + // instead we should use opacity premultiplied by flow + opacity = oparams.premultipliedOpacity; + Vc::float_v opacity_vec(255.0 * oparams.premultipliedOpacity); Vc::float_v average_opacity_vec(255.0 * oparams.averageOpacity); Vc::float_v flow_norm_vec(oparams.flow); Vc::float_v uint8MaxRec2((float)1.0 / (255.0 * 255.0)); Vc::float_v uint8MaxRec1((float)1.0 / 255.0); Vc::float_v uint8Max((float)255.0); Vc::float_v zeroValue(Vc::Zero); Vc::float_v msk_norm_alpha; src_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(src); if (haveMask) { Vc::float_v mask_vec = KoStreamedMath<_impl>::fetch_mask_8(mask); msk_norm_alpha = src_alpha * mask_vec * uint8MaxRec2; } else { msk_norm_alpha = src_alpha * uint8MaxRec1; } dst_alpha = KoStreamedMath<_impl>::template fetch_alpha_32(dst); src_alpha = msk_norm_alpha * opacity_vec; Vc::float_m empty_dst_pixels_mask = dst_alpha == zeroValue; Vc::float_v src_c1; Vc::float_v src_c2; Vc::float_v src_c3; Vc::float_v dst_c1; Vc::float_v dst_c2; Vc::float_v dst_c3; KoStreamedMath<_impl>::template fetch_colors_32(src, src_c1, src_c2, src_c3); bool srcAlphaIsZero = (src_alpha == zeroValue).isFull(); if (srcAlphaIsZero) return; bool dstAlphaIsZero = empty_dst_pixels_mask.isFull(); Vc::float_v dst_blend = src_alpha * uint8MaxRec1; bool srcAlphaIsUnit = (src_alpha == uint8Max).isFull(); if (dstAlphaIsZero) { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } else if (srcAlphaIsUnit) { bool dstAlphaIsUnit = (dst_alpha == uint8Max).isFull(); if (dstAlphaIsUnit) { memcpy(dst, src, 4 * Vc::float_v::size()); return; } else { dst_c1 = src_c1; dst_c2 = src_c2; dst_c3 = src_c3; } } else if (empty_dst_pixels_mask.isEmpty()) { KoStreamedMath<_impl>::template fetch_colors_32(dst, dst_c1, dst_c2, dst_c3); dst_c1 = dst_blend * (src_c1 - dst_c1) + dst_c1; dst_c2 = dst_blend * (src_c2 - dst_c2) + dst_c2; dst_c3 = dst_blend * (src_c3 - dst_c3) + dst_c3; } else { KoStreamedMath<_impl>::template fetch_colors_32(dst, dst_c1, dst_c2, dst_c3); dst_c1(empty_dst_pixels_mask) = src_c1; dst_c2(empty_dst_pixels_mask) = src_c2; dst_c3(empty_dst_pixels_mask) = src_c3; Vc::float_m not_empty_dst_pixels_mask = !empty_dst_pixels_mask; dst_c1(not_empty_dst_pixels_mask) = dst_blend * (src_c1 - dst_c1) + dst_c1; dst_c2(not_empty_dst_pixels_mask) = dst_blend * (src_c2 - dst_c2) + dst_c2; dst_c3(not_empty_dst_pixels_mask) = dst_blend * (src_c3 - dst_c3) + dst_c3; } Vc::float_v fullFlowAlpha; if (oparams.averageOpacity > opacity) { Vc::float_m fullFlowAlpha_mask = average_opacity_vec > dst_alpha; if (fullFlowAlpha_mask.isEmpty()) { fullFlowAlpha = dst_alpha; } else { Vc::float_v reverse_blend = dst_alpha / average_opacity_vec; Vc::float_v opt1 = (average_opacity_vec - src_alpha) * reverse_blend + src_alpha; fullFlowAlpha(!fullFlowAlpha_mask) = dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = opt1; } } else { Vc::float_m fullFlowAlpha_mask = opacity_vec > dst_alpha; if (fullFlowAlpha_mask.isEmpty()) { fullFlowAlpha = dst_alpha; } else { Vc::float_v opt1 = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha; fullFlowAlpha(!fullFlowAlpha_mask) = dst_alpha; fullFlowAlpha(fullFlowAlpha_mask) = opt1; } } if (oparams.flow == 1.0) { dst_alpha = fullFlowAlpha; } else { - Vc::float_v zeroFlowAlpha = dst_alpha; + Vc::float_v zeroFlowAlpha = src_alpha + dst_alpha - + dst_blend * dst_alpha; dst_alpha = (fullFlowAlpha - zeroFlowAlpha) * flow_norm_vec + zeroFlowAlpha; } KoStreamedMath<_impl>::write_channels_32(dst, dst_alpha, dst_c1, dst_c2, dst_c3); } /** * Composes one pixel of the source into the destination */ template static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const OptionalParams &oparams) { using namespace Arithmetic; const qint32 alpha_pos = 3; const float uint8Rec1 = 1.0 / 255.0; const float uint8Rec2 = 1.0 / (255.0 * 255.0); const float uint8Max = 255.0; quint8 dstAlphaInt = dst[alpha_pos]; float dstAlphaNorm = dstAlphaInt ? dstAlphaInt * uint8Rec1 : 0.0; float srcAlphaNorm; float mskAlphaNorm; + Q_UNUSED(opacity); + opacity = oparams.premultipliedOpacity; + if (haveMask) { mskAlphaNorm = float(*mask) * uint8Rec2 * src[alpha_pos]; srcAlphaNorm = mskAlphaNorm * opacity; } else { mskAlphaNorm = src[alpha_pos] * uint8Rec1; srcAlphaNorm = mskAlphaNorm * opacity; } if (dstAlphaInt != 0) { dst[0] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[0], src[0], srcAlphaNorm); dst[1] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[1], src[1], srcAlphaNorm); dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcAlphaNorm); } else { const pixel_type *s = reinterpret_cast(src); pixel_type *d = reinterpret_cast(dst); *d = *s; } float flow = oparams.flow; float averageOpacity = oparams.averageOpacity; float fullFlowAlpha; if (averageOpacity > opacity) { fullFlowAlpha = averageOpacity > dstAlphaNorm ? lerp(srcAlphaNorm, averageOpacity, dstAlphaNorm / averageOpacity) : dstAlphaNorm; } else { fullFlowAlpha = opacity > dstAlphaNorm ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm; } float dstAlpha; if (flow == 1.0) { dstAlpha = fullFlowAlpha * uint8Max; } else { - float zeroFlowAlpha = dstAlphaNorm; + float zeroFlowAlpha = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm); dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow) * uint8Max; } dst[alpha_pos] = KoStreamedMath<_impl>::round_float_to_uint(dstAlpha); } }; /** * An optimized version of a composite op for the use in 4 byte * colorspaces with alpha channel placed at the last byte of * the pixel: C1_C2_C3_A. */ template class KoOptimizedCompositeOpAlphaDarken32 : public KoCompositeOp { public: KoOptimizedCompositeOpAlphaDarken32(const KoColorSpace* cs) : KoCompositeOp(cs, COMPOSITE_ALPHA_DARKEN, i18n("Alpha darken"), KoCompositeOp::categoryMix()) {} using KoCompositeOp::composite; virtual void composite(const KoCompositeOp::ParameterInfo& params) const { if(params.maskRowStart) { KoStreamedMath<_impl>::template genericComposite32 >(params); } else { KoStreamedMath<_impl>::template genericComposite32 >(params); } } }; #endif // KOOPTIMIZEDCOMPOSITEOPALPHADARKEN32_H_