Commit 27cccdcd authored by Dmitry Kazakov's avatar Dmitry Kazakov

Fix composite copy to use classical OVER algorithm on semi-transparent data

Now when source and destination have non-transparent pixels
we use straightforward OVER algorithm that does expected things:

1) Premultiplies src and dst
2) Blends them using opacity and mask
3) Unmultiplies the value to write into the destination

This algo is even more complex than the one used in COMPOSIRTE_OVER
(yes, we use approximation) and works about 50% slower than OVER.

But since most of the time COMPOSITE_COPY is applied without mask the
slowdown will be hardly seen.

BUG:333227
parent efd412df
...@@ -127,7 +127,8 @@ KoCanvasResourceManager* utils::createResourceManager(KisImageWSP image, ...@@ -127,7 +127,8 @@ KoCanvasResourceManager* utils::createResourceManager(KisImageWSP image,
utils::StrokeTester::StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFilename) utils::StrokeTester::StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFilename)
: m_name(name), : m_name(name),
m_imageSize(imageSize), m_imageSize(imageSize),
m_presetFilename(presetFilename) m_presetFilename(presetFilename),
m_numIterations(1)
{ {
} }
...@@ -135,6 +136,11 @@ utils::StrokeTester::~StrokeTester() ...@@ -135,6 +136,11 @@ utils::StrokeTester::~StrokeTester()
{ {
} }
void utils::StrokeTester::setNumIterations(int value)
{
m_numIterations = value;
}
void utils::StrokeTester::test() void utils::StrokeTester::test()
{ {
testOneStroke(false, false, false); testOneStroke(false, false, false);
...@@ -240,39 +246,45 @@ QImage utils::StrokeTester::doStroke(bool cancelled, ...@@ -240,39 +246,45 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
{ {
KisImageSP image = utils::createImage(0, m_imageSize); KisImageSP image = utils::createImage(0, m_imageSize);
KoCanvasResourceManager *manager = utils::createResourceManager(image, 0, m_presetFilename); KoCanvasResourceManager *manager = utils::createResourceManager(image, 0, m_presetFilename);
KisNodeSP currentNode;
KisPainter *painter = new KisPainter(); for (int i = 0; i < m_numIterations; i++) {
KisResourcesSnapshotSP resources = modifyResourceManager(manager, image, i);
new KisResourcesSnapshot(image,
image->postExecutionUndoAdapter(),
manager);
if(externalLayer) { KisPainter *painter = new KisPainter();
KisNodeSP externalNode = new KisPaintLayer(0, "extlyr", OPACITY_OPAQUE_U8, image->colorSpace()); KisResourcesSnapshotSP resources =
resources->setCurrentNode(externalNode); new KisResourcesSnapshot(image,
Q_ASSERT(resources->currentNode() == externalNode); image->postExecutionUndoAdapter(),
} manager);
initImage(image, resources->currentNode()); if(externalLayer) {
KisNodeSP externalNode = new KisPaintLayer(0, "extlyr", OPACITY_OPAQUE_U8, image->colorSpace());
resources->setCurrentNode(externalNode);
Q_ASSERT(resources->currentNode() == externalNode);
}
KisStrokeStrategy *stroke = createStroke(indirectPainting, resources, painter, image); initImage(image, resources->currentNode(), i);
m_strokeId = image->startStroke(stroke);
addPaintingJobs(image, resources, painter);
if(!cancelled) { KisStrokeStrategy *stroke = createStroke(indirectPainting, resources, painter, image);
image->endStroke(m_strokeId); m_strokeId = image->startStroke(stroke);
} addPaintingJobs(image, resources, painter, i);
else {
image->cancelStroke(m_strokeId);
}
image->waitForDone(); if(!cancelled) {
image->endStroke(m_strokeId);
}
else {
image->cancelStroke(m_strokeId);
}
image->waitForDone();
currentNode = resources->currentNode();
}
QImage resultImage; QImage resultImage;
if(needQImage) { if(needQImage) {
KisPaintDeviceSP device = testUpdates ? KisPaintDeviceSP device = testUpdates ?
image->projection() : image->projection() :
resources->currentNode()->paintDevice(); currentNode->paintDevice();
resultImage = device->convertToQImage(0, 0, 0, image->width(), image->height()); resultImage = device->convertToQImage(0, 0, 0, image->width(), image->height());
} }
...@@ -282,8 +294,45 @@ QImage utils::StrokeTester::doStroke(bool cancelled, ...@@ -282,8 +294,45 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
return resultImage; return resultImage;
} }
void utils::StrokeTester::modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image, int iteration)
{
Q_UNUSED(iteration);
modifyResourceManager(manager, image);
}
void utils::StrokeTester::modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image)
{
Q_UNUSED(manager);
Q_UNUSED(image);
}
void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode, int iteration)
{
Q_UNUSED(iteration);
initImage(image, activeNode);
}
void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode) void utils::StrokeTester::initImage(KisImageWSP image, KisNodeSP activeNode)
{ {
Q_UNUSED(image); Q_UNUSED(image);
Q_UNUSED(activeNode); Q_UNUSED(activeNode);
} }
void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
KisPainter *painter, int iteration)
{
Q_UNUSED(iteration);
addPaintingJobs(image, resources, painter);
}
void utils::StrokeTester::addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
KisPainter *painter)
{
Q_UNUSED(image);
Q_UNUSED(resources);
Q_UNUSED(painter);
}
...@@ -46,11 +46,23 @@ namespace utils { ...@@ -46,11 +46,23 @@ namespace utils {
void test(); void test();
void benchmark(); void benchmark();
void setNumIterations(int value);
protected: protected:
KisStrokeId strokeId() { KisStrokeId strokeId() {
return m_strokeId; return m_strokeId;
} }
virtual void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image, int iteration);
virtual void initImage(KisImageWSP image, KisNodeSP activeNode, int iteration);
// overload
virtual void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image);
// overload
virtual void initImage(KisImageWSP image, KisNodeSP activeNode); virtual void initImage(KisImageWSP image, KisNodeSP activeNode);
virtual KisStrokeStrategy* createStroke(bool indirectPainting, virtual KisStrokeStrategy* createStroke(bool indirectPainting,
...@@ -60,7 +72,12 @@ namespace utils { ...@@ -60,7 +72,12 @@ namespace utils {
virtual void addPaintingJobs(KisImageWSP image, virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources, KisResourcesSnapshotSP resources,
KisPainter *painter) = 0; KisPainter *painter, int iteration);
// overload
virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
KisPainter *painter);
private: private:
void testOneStroke(bool cancelled, bool indirectPainting, void testOneStroke(bool cancelled, bool indirectPainting,
...@@ -81,6 +98,7 @@ namespace utils { ...@@ -81,6 +98,7 @@ namespace utils {
QString m_name; QString m_name;
QSize m_imageSize; QSize m_imageSize;
QString m_presetFilename; QString m_presetFilename;
int m_numIterations;
}; };
} }
......
...@@ -38,6 +38,9 @@ public: ...@@ -38,6 +38,9 @@ public:
} }
protected: protected:
using utils::StrokeTester::initImage;
using utils::StrokeTester::addPaintingJobs;
void initImage(KisImageWSP image, KisNodeSP activeNode) { void initImage(KisImageWSP image, KisNodeSP activeNode) {
QImage src(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png"); QImage src(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png");
activeNode->original()->convertFromQImage(src, 0); activeNode->original()->convertFromQImage(src, 0);
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
#include <qtest_kde.h> #include <qtest_kde.h>
#include <KoCompositeOpRegistry.h> #include <KoCompositeOpRegistry.h>
#include <KoColor.h>
#include "stroke_testing_utils.h" #include "stroke_testing_utils.h"
#include "strokes/freehand_stroke.h" #include "strokes/freehand_stroke.h"
#include "kis_resources_snapshot.h" #include "kis_resources_snapshot.h"
...@@ -32,11 +33,33 @@ class FreehandStrokeTester : public utils::StrokeTester ...@@ -32,11 +33,33 @@ class FreehandStrokeTester : public utils::StrokeTester
{ {
public: public:
FreehandStrokeTester(const QString &presetFilename) FreehandStrokeTester(const QString &presetFilename)
: StrokeTester("freehand", QSize(500, 500), presetFilename) : StrokeTester("freehand", QSize(500, 500), presetFilename),
m_flipLineDirection(false)
{ {
} }
void setFlipLineDirection(bool value) {
m_flipLineDirection = value;
setNumIterations(2);
}
void setPaintColor(const QColor &color) {
m_paintColor.reset(new QColor(color));
}
protected: protected:
void modifyResourceManager(KoCanvasResourceManager *manager,
KisImageWSP image,
int iteration) {
if (m_paintColor && iteration > 0) {
QVariant i;
i.setValue(KoColor(*m_paintColor, image->colorSpace()));
manager->setResource(KoCanvasResourceManager::ForegroundColor, i);
}
}
KisStrokeStrategy* createStroke(bool indirectPainting, KisStrokeStrategy* createStroke(bool indirectPainting,
KisResourcesSnapshotSP resources, KisResourcesSnapshotSP resources,
KisPainter *painter, KisPainter *painter,
...@@ -50,7 +73,7 @@ protected: ...@@ -50,7 +73,7 @@ protected:
return new FreehandStrokeStrategy(indirectPainting, COMPOSITE_ALPHA_DARKEN, resources, m_painterInfo, kundo2_noi18n("Freehand Stroke")); return new FreehandStrokeStrategy(indirectPainting, COMPOSITE_ALPHA_DARKEN, resources, m_painterInfo, kundo2_noi18n("Freehand Stroke"));
} }
void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, KisPainter *painter) { void addPaintingJobs(KisImageWSP image, KisResourcesSnapshotSP resources, KisPainter *painter, int iteration) {
Q_ASSERT(painter == m_painterInfo->painter); Q_ASSERT(painter == m_painterInfo->painter);
Q_UNUSED(painter); Q_UNUSED(painter);
...@@ -58,16 +81,24 @@ protected: ...@@ -58,16 +81,24 @@ protected:
KisPaintInformation pi1; KisPaintInformation pi1;
KisPaintInformation pi2; KisPaintInformation pi2;
pi1 = KisPaintInformation(QPointF(200, 200)); if (!iteration) {
pi2 = KisPaintInformation(QPointF(300, 300)); pi1 = KisPaintInformation(QPointF(200, 200));
pi2 = KisPaintInformation(QPointF(300, 300));
} else {
pi1 = KisPaintInformation(QPointF(200, 300));
pi2 = KisPaintInformation(QPointF(300, 200));
}
image->addJob(strokeId(), image->addJob(strokeId(),
new FreehandStrokeStrategy::Data(resources->currentNode(), new FreehandStrokeStrategy::Data(resources->currentNode(),
m_painterInfo, pi1, pi2)); m_painterInfo, pi1, pi2));
} }
private: private:
FreehandStrokeStrategy::PainterInfo *m_painterInfo; FreehandStrokeStrategy::PainterInfo *m_painterInfo;
bool m_flipLineDirection;
QScopedPointer<QColor> m_paintColor;
}; };
void FreehandStrokeTest::testAutobrushStroke() void FreehandStrokeTest::testAutobrushStroke()
...@@ -100,5 +131,13 @@ void FreehandStrokeTest::testAutoTextured38() ...@@ -100,5 +131,13 @@ void FreehandStrokeTest::testAutoTextured38()
tester.test(); tester.test();
} }
void FreehandStrokeTest::testMixDullCompositioning()
{
FreehandStrokeTester tester("Mix_dull.kpp");
tester.setFlipLineDirection(true);
tester.setPaintColor(Qt::red);
tester.test();
}
QTEST_KDEMAIN(FreehandStrokeTest, GUI) QTEST_KDEMAIN(FreehandStrokeTest, GUI)
#include "freehand_stroke_test.moc" #include "freehand_stroke_test.moc"
...@@ -32,6 +32,7 @@ private slots: ...@@ -32,6 +32,7 @@ private slots:
void testColorSmudgeStroke(); void testColorSmudgeStroke();
void testAutoTextured17(); void testAutoTextured17();
void testAutoTextured38(); void testAutoTextured38();
void testMixDullCompositioning();
}; };
#endif /* __FREEHAND_STROKE_TEST_H */ #endif /* __FREEHAND_STROKE_TEST_H */
...@@ -278,6 +278,14 @@ public: ...@@ -278,6 +278,14 @@ public:
inline static dst_compositetype clamp(dst_compositetype val) { inline static dst_compositetype clamp(dst_compositetype val) {
return qBound<dst_compositetype>(KoColorSpaceMathsTraits<_Tdst>::min, val, KoColorSpaceMathsTraits<_Tdst>::max); return qBound<dst_compositetype>(KoColorSpaceMathsTraits<_Tdst>::min, val, KoColorSpaceMathsTraits<_Tdst>::max);
} }
/**
* Clamps the composite type on higher border only. That is a fast path
* for scale-only transformations
*/
inline static _Tdst clampAfterScale(dst_compositetype val) {
return qMin<dst_compositetype>(val, KoColorSpaceMathsTraits<_Tdst>::max);
}
}; };
//------------------------------ double specialization ------------------------------// //------------------------------ double specialization ------------------------------//
......
...@@ -52,17 +52,23 @@ public: ...@@ -52,17 +52,23 @@ public:
using namespace Arithmetic; using namespace Arithmetic;
opacity = mul(maskAlpha, opacity); opacity = mul(maskAlpha, opacity);
channels_type newAlpha = zeroValue<channels_type>();
if(dstAlpha == zeroValue<channels_type>() || if(dstAlpha == zeroValue<channels_type>() ||
opacity == unitValue<channels_type>()) { opacity == unitValue<channels_type>()) {
newAlpha = lerp(dstAlpha, srcAlpha, opacity);
// don't blend if the color of the destination is undefined (has zero opacity) // don't blend if the color of the destination is undefined (has zero opacity)
// copy the source channel instead // copy the source channel instead
for(qint32 i=0; i<channels_nb; ++i) for(qint32 i=0; i<channels_nb; ++i)
if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i))) if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i)))
dst[i] = src[i]; dst[i] = src[i];
}
else { } else if (opacity == zeroValue<channels_type>()) {
newAlpha = dstAlpha;
} else {
/** /**
* In case the mask is not opaque, we should also pre-blend * In case the mask is not opaque, we should also pre-blend
* the source pixel alpha channel to the mask. Otherwise * the source pixel alpha channel to the mask. Otherwise
...@@ -70,16 +76,33 @@ public: ...@@ -70,16 +76,33 @@ public:
* be mixed into destination event when the source alpha * be mixed into destination event when the source alpha
* is negligible. * is negligible.
*/ */
channels_type srcBlend = mul(opacity, srcAlpha);
newAlpha = lerp(dstAlpha, srcAlpha, opacity);
// blend the color channels // blend the color channels
for(qint32 i=0; i<channels_nb; ++i) for(qint32 i=0; i<channels_nb; ++i) {
if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i))) if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i))) {
dst[i] = lerp(dst[i], src[i], srcBlend);
/**
* We use the most fundamental OVER algorithm here,
* which miltiplies, blends and then unmultiplies the
* channels
*/
typedef typename KoColorSpaceMathsTraits<channels_type>::compositetype composite_type;
channels_type dstMult = mul(dst[i], dstAlpha);
channels_type srcMult = mul(src[i], srcAlpha);
channels_type blendedValue = lerp(dstMult, srcMult, opacity);
composite_type normedValue = KoColorSpaceMaths<channels_type>::divide(blendedValue, newAlpha);
dst[i] = KoColorSpaceMaths<channels_type>::clampAfterScale(normedValue);
}
}
} }
// blend the alpha channel return newAlpha;
return lerp(dstAlpha, srcAlpha, opacity);
} }
}; };
......
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