Commit c2fcdced authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fixed a pressure bug in Wash-mode of painting

When the pressure drops, the opacity drops as well. In this moment, to
avoid the artifacts while painting the ALPHA_DARKEN op should work in a
reversed way. That is the contents of the layer should be painted on the
top of the brush dab. To allow this, we now keep the average value of the
opacity of several last dabs to be able to reverse the composite op.

This is tested to work best with spacings 0.1-0.2. With other spacing
values, probably, one needs to correct exponent value in
ParameterInfo::updateOpacityAndAverage().

CCBUG:320651
parent 5ea3de42
......@@ -353,7 +353,7 @@ void benchmarkCompositeOp(const KoCompositeOp *op, const QString &postfix)
#ifdef HAVE_VC
template<class Compositor>
void checkRounding()
void checkRounding(qreal opacity, qreal flow, qreal averageOpacity = -1)
{
QVector<Tile> tiles =
generateTiles(2, 0, 0, ALPHA_RANDOM, ALPHA_RANDOM);
......@@ -370,11 +370,29 @@ void checkRounding()
quint8 *dst2 = tiles[1].dst;
quint8 *msk2 = tiles[1].mask;
KoCompositeOp::ParameterInfo params;
params.opacity = opacity;
params.flow = flow;
if (averageOpacity >= 0.0) {
params._lastOpacityData = averageOpacity;
params.lastOpacity = &params._lastOpacityData;
}
params.channelFlags = QBitArray();
typename Compositor::OptionalParams optionalParams(params);
for (int i = 0; i < numBlocks; i++) {
Compositor::template compositeVector<true,true, VC_IMPL>(src1, dst1, msk1, 0.5, 0.3);
Compositor::template compositeVector<true,true, VC_IMPL>(src1, dst1, msk1, params.opacity, optionalParams);
for (int j = 0; j < vecSize; j++) {
Compositor::template compositeOnePixelScalar<true, VC_IMPL>(src2, dst2, msk2, 0.5, 0.3, QBitArray());
//if (8 * i + j == 7080) {
// qDebug() << "src: " << src2[0] << src2[1] << src2[2] << src2[3];
// qDebug() << "dst: " << dst2[0] << dst2[1] << dst2[2] << dst2[3];
// qDebug() << "msk:" << msk2[0];
//}
Compositor::template compositeOnePixelScalar<true, VC_IMPL>(src2, dst2, msk2, params.opacity, optionalParams);
if(!comparePixels(dst1, dst2, 0)) {
qDebug() << "Wrong rounding in pixel:" << 8 * i + j;
......@@ -402,17 +420,45 @@ void checkRounding()
#endif
void KisCompositionBenchmark::checkRoundingAlphaDarken()
void KisCompositionBenchmark::checkRoundingAlphaDarken_05_03()
{
#ifdef HAVE_VC
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >(0.5,0.3);
#endif
}
void KisCompositionBenchmark::checkRoundingAlphaDarken_05_05()
{
#ifdef HAVE_VC
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >(0.5,0.5);
#endif
}
void KisCompositionBenchmark::checkRoundingAlphaDarken_05_07()
{
#ifdef HAVE_VC
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >(0.5,0.7);
#endif
}
void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10()
{
#ifdef HAVE_VC
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >(0.5,1.0);
#endif
}
void KisCompositionBenchmark::checkRoundingAlphaDarken_05_10_08()
{
#ifdef HAVE_VC
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >();
checkRounding<AlphaDarkenCompositor32<quint8, quint32> >(0.5,1.0,0.8);
#endif
}
void KisCompositionBenchmark::checkRoundingOver()
{
#ifdef HAVE_VC
checkRounding<OverCompositor32<quint8, quint32, false, true> >();
checkRounding<OverCompositor32<quint8, quint32, false, true> >(0.5, 0.3);
#endif
}
......
......@@ -25,7 +25,12 @@ class KisCompositionBenchmark : public QObject
{
Q_OBJECT
private slots:
void checkRoundingAlphaDarken();
void checkRoundingAlphaDarken_05_03();
void checkRoundingAlphaDarken_05_05();
void checkRoundingAlphaDarken_05_07();
void checkRoundingAlphaDarken_05_10();
void checkRoundingAlphaDarken_05_10_08();
void checkRoundingOver();
void compareAlphaDarkenOps();
......
......@@ -149,8 +149,7 @@ void KisPainter::init()
d->maskImageHeight = 255;
d->mirrorHorizontaly = false;
d->mirrorVerticaly = false;
d->paramInfo.opacity = 1.0f;
d->paramInfo.flow = 1.0f;
d->paramInfo = KoCompositeOp::ParameterInfo();
d->renderingIntent = KoColorConversionTransformation::InternalRenderingIntent;
d->conversionFlags = KoColorConversionTransformation::InternalConversionFlags;
}
......@@ -2345,6 +2344,11 @@ quint8 KisPainter::flow() const
return quint8(d->paramInfo.flow * 255.0f);
}
void KisPainter::setOpacityUpdateAverage(quint8 opacity)
{
d->paramInfo.updateOpacityAndAverage(float(opacity) / 255.0f);
}
void KisPainter::setOpacity(quint8 opacity)
{
d->paramInfo.opacity = float(opacity) / 255.0f;
......
......@@ -676,6 +676,13 @@ public:
quint8 flow() const;
/**
* Sets the opacity of the painting and recalculates the
* mean opacity of the stroke. This mean value is used to
* make ALPHA_DARKEN painting look correct
*/
void setOpacityUpdateAverage(quint8 opacity);
/// Set the opacity which is used in painting (like filling polygons)
void setOpacity(quint8 opacity);
......
......@@ -82,9 +82,9 @@ void KisFlowOpacityOption::setOpacity(qreal opacity)
void KisFlowOpacityOption::apply(KisPainter* painter, const KisPaintInformation& info)
{
if(m_paintActionType == WASH && m_nodeHasIndirectPaintingSupport)
painter->setOpacity(quint8(getDynamicOpacity(info) * 255.0));
painter->setOpacityUpdateAverage(quint8(getDynamicOpacity(info) * 255.0));
else
painter->setOpacity(quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0));
painter->setOpacityUpdateAverage(quint8(getStaticOpacity() * getDynamicOpacity(info) * 255.0));
painter->setFlow(quint8(getFlow() * 255.0));
}
......@@ -63,7 +63,7 @@ quint8 KisPressureOpacityOption::apply(KisPainter * painter, const KisPaintInfor
qreal opacity = (qreal)(origOpacity * computeValue(info));
quint8 opacity2 = (quint8)qRound(qBound<qreal>(OPACITY_TRANSPARENT_U8, opacity, OPACITY_OPAQUE_U8));
painter->setOpacity(opacity2);
painter->setOpacityUpdateAverage(opacity2);
return origOpacity;
}
......
......@@ -42,6 +42,19 @@ QString KoCompositeOp::categoryHSV() { return i18n("HSV"); }
QString KoCompositeOp::categoryMix() { return i18n("Mix"); }
QString KoCompositeOp::categoryMisc() { return i18n("Misc"); }
void KoCompositeOp::ParameterInfo::updateOpacityAndAverage(float value) {
const float exponent = 0.1;
opacity = value;
if (*lastOpacity < opacity) {
lastOpacity = &opacity;
} else {
_lastOpacityData = exponent * opacity + (1.0 - exponent) * (*lastOpacity);
lastOpacity = &_lastOpacityData;
}
}
struct KoCompositeOp::Private {
const KoColorSpace * colorSpace;
QString id;
......
......@@ -54,8 +54,15 @@ public:
static QString categoryMix();
static QString categoryMisc();
struct ParameterInfo
struct PIGMENTCMS_EXPORT ParameterInfo
{
ParameterInfo()
: opacity(1.0f),
flow(1.0f),
lastOpacity(&opacity)
{
}
quint8* dstRowStart;
qint32 dstRowStride;
const quint8* srcRowStart;
......@@ -66,7 +73,11 @@ public:
qint32 cols;
float opacity;
float flow;
float _lastOpacityData;
float* lastOpacity;
QBitArray channelFlags;
void updateOpacityAndAverage(float value);
};
public:
......
......@@ -87,9 +87,24 @@ public:
}
if(alpha_pos != -1) {
channels_type alpha1 = unionShapeOpacity(srcAlpha, dstAlpha); // alpha with 0% flow
channels_type alpha2 = (opacity > dstAlpha) ? lerp(dstAlpha, opacity, mskAlpha) : dstAlpha; // alpha with 100% flow
dst[alpha_pos] = lerp(alpha1, alpha2, flow);
channels_type fullFlowAlpha;
channels_type averageOpacity = mul(flow, scale<channels_type>(*params.lastOpacity));
if (averageOpacity > opacity) {
channels_type reverseBlend = KoColorSpaceMaths<channels_type>::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 = unionShapeOpacity(srcAlpha, dstAlpha);
dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow);
}
dst[alpha_pos] = dstAlpha;
}
src += srcInc;
......
......@@ -27,6 +27,18 @@
template<typename channels_type, typename pixel_type>
struct AlphaDarkenCompositor32 {
struct OptionalParams {
OptionalParams(const KoCompositeOp::ParameterInfo& params)
: flow(params.flow),
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.
......@@ -40,16 +52,15 @@ struct AlphaDarkenCompositor32 {
* of a streaming vector. Unaligned writes are really expensive.
* o This function is *never* used if HAVE_VC is not present
*/
template<bool haveMask, bool src_aligned, Vc::Implementation _impl>
static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, float flow)
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 * flow);
Vc::float_v flow_norm_vec(flow);
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));
......@@ -124,15 +135,38 @@ struct AlphaDarkenCompositor32 {
dst_c3(not_empty_dst_pixels_mask) = dst_blend * (src_c3 - dst_c3) + dst_c3;
}
Vc::float_v alpha1 = src_alpha + dst_alpha -
dst_blend * dst_alpha;
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;
}
}
Vc::float_m alpha2_mask = opacity_vec > dst_alpha;
Vc::float_v opt1 = (opacity_vec - dst_alpha) * msk_norm_alpha + dst_alpha;
Vc::float_v alpha2;
alpha2(!alpha2_mask) = dst_alpha;
alpha2(alpha2_mask) = opt1;
dst_alpha = (alpha2 - alpha1) * flow_norm_vec + alpha1;
if (oparams.flow == 1.0) {
dst_alpha = fullFlowAlpha;
} else {
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);
}
......@@ -141,10 +175,8 @@ struct AlphaDarkenCompositor32 {
* Composes one pixel of the source into the destination
*/
template <bool haveMask, Vc::Implementation _impl>
static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, float flow, const QBitArray &channelFlags)
static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const OptionalParams &oparams)
{
Q_UNUSED(channelFlags);
using namespace Arithmetic;
const qint32 alpha_pos = 3;
......@@ -157,11 +189,7 @@ struct AlphaDarkenCompositor32 {
float srcAlphaNorm;
float mskAlphaNorm;
/**
* FIXME: precalculate this value on a higher level for
* not doing it on every cycle
*/
opacity *= flow;
opacity = oparams.premultipliedOpacity;
if (haveMask) {
mskAlphaNorm = float(*mask) * uint8Rec2 * src[alpha_pos];
......@@ -181,9 +209,28 @@ struct AlphaDarkenCompositor32 {
*d = *s;
}
float alpha1 = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm); // alpha with 0% flow
float alpha2 = (opacity > dstAlphaNorm) ? lerp(dstAlphaNorm, opacity, mskAlphaNorm) : dstAlphaNorm; // alpha with 100% flow
dst[alpha_pos] = quint8(lerp(alpha1, alpha2, flow) * uint8Max);
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 = unionShapeOpacity(srcAlphaNorm, dstAlphaNorm);
dstAlpha = lerp(zeroFlowAlpha, fullFlowAlpha, flow) * uint8Max;
}
dst[alpha_pos] = quint8(dstAlpha);
}
};
......
......@@ -28,13 +28,19 @@
template<typename channels_type, typename pixel_type, bool alphaLocked, bool allChannelsFlag>
struct OverCompositor32 {
// \see docs in AlphaDarkenCompositor32
struct OptionalParams {
OptionalParams(const KoCompositeOp::ParameterInfo& params)
: channelFlags(params.channelFlags)
{
}
const QBitArray &channelFlags;
};
// \see docs in AlphaDarkenCompositor32
template<bool haveMask, bool src_aligned, Vc::Implementation _impl>
static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, float flow)
static ALWAYS_INLINE void compositeVector(const quint8 *src, quint8 *dst, const quint8 *mask, float opacity, const OptionalParams &oparams)
{
Q_UNUSED(flow);
Q_UNUSED(oparams);
Vc::float_v src_alpha;
Vc::float_v dst_alpha;
......@@ -118,10 +124,8 @@ struct OverCompositor32 {
}
template <bool haveMask, Vc::Implementation _impl>
static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, float flow, const QBitArray &channelFlags)
static ALWAYS_INLINE void compositeOnePixelScalar(const channels_type *src, channels_type *dst, const quint8 *mask, float opacity, const OptionalParams &oparams)
{
Q_UNUSED(flow);
using namespace Arithmetic;
const qint32 alpha_pos = 3;
......@@ -172,6 +176,8 @@ struct OverCompositor32 {
dst[2] = KoStreamedMath<_impl>::lerp_mixed_u8_float(dst[2], src[2], srcBlendNorm);
}
} else {
const QBitArray &channelFlags = oparams.channelFlags;
if (srcBlendNorm == 1.0) {
if(channelFlags.at(0)) dst[0] = src[0];
if(channelFlags.at(1)) dst[1] = src[1];
......
......@@ -51,6 +51,7 @@ template<bool useMask, bool useFlow, class Compositor>
quint8* dstRowStart = params.dstRowStart;
const quint8* maskRowStart = params.maskRowStart;
const quint8* srcRowStart = params.srcRowStart;
typename Compositor::OptionalParams optionalParams(params);
for(quint32 r=params.rows; r>0; --r) {
const quint8 *mask = maskRowStart;
......@@ -60,7 +61,7 @@ template<bool useMask, bool useFlow, class Compositor>
int blockRest = params.cols;
for(int i = 0; i < blockRest; i++) {
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, params.flow, params.channelFlags);
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, optionalParams);
src += srcLinearInc;
dst += linearInc;
......@@ -197,6 +198,7 @@ template<bool useMask, bool useFlow, class Compositor>
quint8* dstRowStart = params.dstRowStart;
const quint8* maskRowStart = params.maskRowStart;
const quint8* srcRowStart = params.srcRowStart;
typename Compositor::OptionalParams optionalParams(params);
if (!params.srcRowStride) {
quint32 *buf = Vc::malloc<quint32, Vc::AlignOnVector>(vectorSize);
......@@ -244,7 +246,7 @@ template<bool useMask, bool useFlow, class Compositor>
}
for(int i = 0; i < blockAlign; i++) {
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, params.flow, params.channelFlags);
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, optionalParams);
src += srcLinearInc;
dst += linearInc;
......@@ -254,7 +256,7 @@ template<bool useMask, bool useFlow, class Compositor>
}
for (int i = 0; i < blockAlignedVector; i++) {
Compositor::template compositeVector<useMask, true, _impl>(src, dst, mask, params.opacity, params.flow);
Compositor::template compositeVector<useMask, true, _impl>(src, dst, mask, params.opacity, optionalParams);
src += srcVectorInc;
dst += vectorInc;
......@@ -264,7 +266,7 @@ template<bool useMask, bool useFlow, class Compositor>
}
for (int i = 0; i < blockUnalignedVector; i++) {
Compositor::template compositeVector<useMask, false, _impl>(src, dst, mask, params.opacity, params.flow);
Compositor::template compositeVector<useMask, false, _impl>(src, dst, mask, params.opacity, optionalParams);
src += srcVectorInc;
dst += vectorInc;
......@@ -275,7 +277,7 @@ template<bool useMask, bool useFlow, class Compositor>
for(int i = 0; i < blockRest; i++) {
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, params.flow, params.channelFlags);
Compositor::template compositeOnePixelScalar<useMask, _impl>(src, dst, mask, params.opacity, optionalParams);
src += srcLinearInc;
dst += linearInc;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment