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,
utils::StrokeTester::StrokeTester(const QString &name, const QSize &imageSize, const QString &presetFilename)
: m_name(name),
m_imageSize(imageSize),
m_presetFilename(presetFilename)
m_presetFilename(presetFilename),
m_numIterations(1)
{
}
......@@ -135,6 +136,11 @@ utils::StrokeTester::~StrokeTester()
{
}
void utils::StrokeTester::setNumIterations(int value)
{
m_numIterations = value;
}
void utils::StrokeTester::test()
{
testOneStroke(false, false, false);
......@@ -240,6 +246,10 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
{
KisImageSP image = utils::createImage(0, m_imageSize);
KoCanvasResourceManager *manager = utils::createResourceManager(image, 0, m_presetFilename);
KisNodeSP currentNode;
for (int i = 0; i < m_numIterations; i++) {
modifyResourceManager(manager, image, i);
KisPainter *painter = new KisPainter();
KisResourcesSnapshotSP resources =
......@@ -253,11 +263,11 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
Q_ASSERT(resources->currentNode() == externalNode);
}
initImage(image, resources->currentNode());
initImage(image, resources->currentNode(), i);
KisStrokeStrategy *stroke = createStroke(indirectPainting, resources, painter, image);
m_strokeId = image->startStroke(stroke);
addPaintingJobs(image, resources, painter);
addPaintingJobs(image, resources, painter, i);
if(!cancelled) {
image->endStroke(m_strokeId);
......@@ -267,12 +277,14 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
}
image->waitForDone();
currentNode = resources->currentNode();
}
QImage resultImage;
if(needQImage) {
KisPaintDeviceSP device = testUpdates ?
image->projection() :
resources->currentNode()->paintDevice();
currentNode->paintDevice();
resultImage = device->convertToQImage(0, 0, 0, image->width(), image->height());
}
......@@ -282,8 +294,45 @@ QImage utils::StrokeTester::doStroke(bool cancelled,
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)
{
Q_UNUSED(image);
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 {
void test();
void benchmark();
void setNumIterations(int value);
protected:
KisStrokeId 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 KisStrokeStrategy* createStroke(bool indirectPainting,
......@@ -60,7 +72,12 @@ namespace utils {
virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
KisPainter *painter) = 0;
KisPainter *painter, int iteration);
// overload
virtual void addPaintingJobs(KisImageWSP image,
KisResourcesSnapshotSP resources,
KisPainter *painter);
private:
void testOneStroke(bool cancelled, bool indirectPainting,
......@@ -81,6 +98,7 @@ namespace utils {
QString m_name;
QSize m_imageSize;
QString m_presetFilename;
int m_numIterations;
};
}
......
......@@ -38,6 +38,9 @@ public:
}
protected:
using utils::StrokeTester::initImage;
using utils::StrokeTester::addPaintingJobs;
void initImage(KisImageWSP image, KisNodeSP activeNode) {
QImage src(QString(FILES_DATA_DIR) + QDir::separator() + "lena.png");
activeNode->original()->convertFromQImage(src, 0);
......
......@@ -20,6 +20,7 @@
#include <qtest_kde.h>
#include <KoCompositeOpRegistry.h>
#include <KoColor.h>
#include "stroke_testing_utils.h"
#include "strokes/freehand_stroke.h"
#include "kis_resources_snapshot.h"
......@@ -32,11 +33,33 @@ class FreehandStrokeTester : public utils::StrokeTester
{
public:
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:
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,
KisResourcesSnapshotSP resources,
KisPainter *painter,
......@@ -50,7 +73,7 @@ protected:
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_UNUSED(painter);
......@@ -58,16 +81,24 @@ protected:
KisPaintInformation pi1;
KisPaintInformation pi2;
if (!iteration) {
pi1 = KisPaintInformation(QPointF(200, 200));
pi2 = KisPaintInformation(QPointF(300, 300));
} else {
pi1 = KisPaintInformation(QPointF(200, 300));
pi2 = KisPaintInformation(QPointF(300, 200));
}
image->addJob(strokeId(),
new FreehandStrokeStrategy::Data(resources->currentNode(),
m_painterInfo, pi1, pi2));
}
private:
FreehandStrokeStrategy::PainterInfo *m_painterInfo;
bool m_flipLineDirection;
QScopedPointer<QColor> m_paintColor;
};
void FreehandStrokeTest::testAutobrushStroke()
......@@ -100,5 +131,13 @@ void FreehandStrokeTest::testAutoTextured38()
tester.test();
}
void FreehandStrokeTest::testMixDullCompositioning()
{
FreehandStrokeTester tester("Mix_dull.kpp");
tester.setFlipLineDirection(true);
tester.setPaintColor(Qt::red);
tester.test();
}
QTEST_KDEMAIN(FreehandStrokeTest, GUI)
#include "freehand_stroke_test.moc"
......@@ -32,6 +32,7 @@ private slots:
void testColorSmudgeStroke();
void testAutoTextured17();
void testAutoTextured38();
void testMixDullCompositioning();
};
#endif /* __FREEHAND_STROKE_TEST_H */
......@@ -278,6 +278,14 @@ public:
inline static dst_compositetype clamp(dst_compositetype val) {
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 ------------------------------//
......
......@@ -52,17 +52,23 @@ public:
using namespace Arithmetic;
opacity = mul(maskAlpha, opacity);
channels_type newAlpha = zeroValue<channels_type>();
if(dstAlpha == zeroValue<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)
// copy the source channel instead
for(qint32 i=0; i<channels_nb; ++i)
if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(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
* the source pixel alpha channel to the mask. Otherwise
......@@ -70,16 +76,33 @@ public:
* be mixed into destination event when the source alpha
* is negligible.
*/
channels_type srcBlend = mul(opacity, srcAlpha);
newAlpha = lerp(dstAlpha, srcAlpha, opacity);
// blend the color channels
for(qint32 i=0; i<channels_nb; ++i)
if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i)))
dst[i] = lerp(dst[i], src[i], srcBlend);
for(qint32 i=0; i<channels_nb; ++i) {
if(i != alpha_pos && (allChannelFlags || channelFlags.testBit(i))) {
/**
* 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 lerp(dstAlpha, srcAlpha, opacity);
return newAlpha;
}
};
......
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