[graphics/krita] /: Feature: HDR gradients

L. E. Segovia null at kde.org
Wed Feb 10 18:57:41 GMT 2021


Git commit 4cfda40a29f9ee7548ab5d2f7320f2fca8674f65 by L. E. Segovia.
Committed on 10/02/2021 at 18:50.
Pushed by lsegovia into branch 'master'.

Feature: HDR gradients

This commit adds suport in Krita for rendering of HDR, high-bit-depth
gradients, and dithering for all output bit depths.

Firstly, all gradient operations are now upgraded; this includes color
mixing ops (step values, upgraded to 16-bit signed integer), and
gradients' default bit depth, which was upgraded to 16-bit integer
or the image's bit depth, whichever is higher.

Secondly, this commit implements a fully SSE+ vectorizable dithering
operator, based on the Pixman low-level graphics library's
implementation (MIT-licensed) available at the following commits:

https://gitlab.freedesktop.org/pixman/pixman/-/commit/ddcc41b999562efdd9f88daa51ffbf39782748b5
https://gitlab.freedesktop.org/pixman/pixman/-/commit/98b5ec74ca14448349ef6a33a663ad19d446ed6b
https://gitlab.freedesktop.org/pixman/pixman/-/commit/cb2ec4268fbde0df3b588ce5cbe2e43e0465452

This version follows closely the original paper of Ulichney's:

Robert A. Ulichney. "Void-and-cluster method for dither array
generation", Proc. SPIE 1913, Human Vision, Visual Processing, and
Digital Display IV, (8 September 1993); doi:10.1117/12.152707

Based on Pixman's work, there are two available options
(but three in code, see below):

- a no-op downsampler

- blue-noise Ulichney's dithering, 64x64, which is the default when
enabled

- Bayer's ordered dithering, 8x8, which has been left as a backup.

The dither operator works by upcasting the pixel to normalized,
floating point color space; applying modulated noise with scale
1 / 2^bit_depth, and then downcasting the pixel to the destination
depth. For obvious reasons, the first step is skipped if the source
is already floating point, and the two latter are no-ops if the
destination's floating point.

The implementation in this commit is structured in a two-level
abstract pattern:

- KisDitherOp is the interface that all operators
expose to dither a single pixel. They are implemented as instances of
KisDitherOpImpl<srcCSTraits, dstCSTraits, DitherType>.

- KisDitherOpImpl is the template class that does the hard work
described above.

Instances of the dither operator are inserted on each colorspace
through a templated, inlined factory function,
addStandardDitherOps<srcCSTraits>(KoColorSpace *). Given the
source bit depth and all known possible destination depths,
this template calls another templated function, addDitherOpsByDepth<
srcCSTraits, dstCSTraits, DitherType>(KoColorSpace *). Each call
to this function creates a KisDitherOpImpl instance corresponding
to each triplet of bit depths and dither implementation, and inserts it
in the given colorspace.

Since this operator needs both source and destination
colorspaces' traits, I check at compilation time that all known traits
have been considered at op creation.

The vectorization properties have been tested with Xcode 11 on macOS,
dumping kritalcmsengine.so with objdump, and checking disassembly
manually.

There are two ways to use this dither operator, once a copy has been
obtained:

- Use dither(const quint8* src, quint8* dst, int x, int y) to dither
a single pixel.

- Use dither(const quint8* src, int srcRowStride, quint8* dst,
int dstRowStride, int x, int y, int columns, int rows) to dither a
whole tile at once. This is the pattern that's used in
KisGradientPainter.

Additionally to Pixman's implementation, and the optimizations already
provided by KoColorSpaceTraits, an optimized version has been
made for the case of pass-through dither and no downcasting, where a
memcpy call suffices.

Finally, this implementation has been made available as a checkbox in:

- the Gradient tool

- Layer / Layer Styles / Gradient overlay

- Fill Layers / Gradient generator

Support has also been included for Photoshop's dithered gradients,
when importing and exporting PSDs. As with the rest of Krita,
they map to blue-noise dithering.

Thanks to Wolthera van Hövell, Dmitry Kazakov, and Mathias Wein for
their review.

BUG: 343864
CCMAIL: kimageshop at kde.org

M  +10   -7    libs/global/kis_global.h
M  +5    -6    libs/image/kis_asl_layer_style_serializer.cpp
M  +73   -20   libs/image/kis_gradient_painter.cc
M  +6    -2    libs/image/kis_gradient_painter.h
M  +3    -1    libs/image/layerstyles/kis_ls_utils.cpp
A  +203  -0    libs/pigment/KisDitherMaths.h     [License: MIT]
A  +52   -0    libs/pigment/KisDitherOp.h     [License: GPL(v2.0+)]
A  +194  -0    libs/pigment/KisDitherOpImpl.h     [License: GPL(v2.0+)]
M  +40   -3    libs/pigment/KoColorSpace.cpp
M  +11   -3    libs/pigment/KoColorSpace.h
M  +2    -2    libs/pigment/KoColorSpaceAbstract.h
M  +5    -1    libs/pigment/KoColorSpace_p.h
M  +3    -1    libs/pigment/colorspaces/KoLabColorSpace.cpp
M  +4    -1    libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp
M  +3    -2    libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp
A  +26   -0    libs/pigment/dithering/KisCmykDitherOpFactory.h     [License: GPL(v2.0+)]
A  +26   -0    libs/pigment/dithering/KisGrayDitherOpFactory.h     [License: GPL(v2.0+)]
A  +26   -0    libs/pigment/dithering/KisLabDitherOpFactory.h     [License: GPL(v2.0+)]
A  +27   -0    libs/pigment/dithering/KisRgbDitherOpFactory.h     [License: GPL(v2.0+)]
A  +26   -0    libs/pigment/dithering/KisXyzDitherOpFactory.h     [License: GPL(v2.0+)]
A  +26   -0    libs/pigment/dithering/KisYCbCrDitherOpFactory.h     [License: GPL(v2.0+)]
M  +40   -67   libs/pigment/resources/KoSegmentGradient.cpp
M  +17   -32   libs/pigment/resources/KoStopGradient.cpp
M  +11   -0    libs/psd/psd.h
M  +3    -0    libs/ui/dialogs/kis_dlg_layer_style.cpp
M  +34   -15   libs/ui/layerstyles/WdgGradientOverlay.ui
M  +3    -1    plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp
M  +16   -13   plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.cpp
M  +1    -3    plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.h
M  +16   -13   plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.cpp
M  +1    -3    plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.h
M  +3    -0    plugins/color/lcms2engine/colorspaces/gray_f16/GrayF16ColorSpace.cpp
M  +3    -0    plugins/color/lcms2engine/colorspaces/gray_f32/GrayF32ColorSpace.cpp
M  +9    -6    plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.cpp
M  +1    -3    plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.h
M  +9    -6    plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.cpp
M  +1    -3    plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.h
M  +3    -1    plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
M  +5    -3    plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
M  +7    -4    plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/xyz_f16/XyzF16ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/xyz_f32/XyzF32ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/xyz_u16/XyzU16ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/xyz_u8/XyzU8ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp
M  +4    -1    plugins/color/lcms2engine/colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp
M  +3    -1    plugins/generators/gradient/KisGradientGenerator.cpp
M  +4    -0    plugins/generators/gradient/KisGradientGeneratorConfigWidget.cpp
M  +14   -0    plugins/generators/gradient/KisGradientGeneratorConfigWidget.ui
M  +12   -1    plugins/generators/gradient/KisGradientGeneratorConfiguration.cpp
M  +8    -0    plugins/generators/gradient/KisGradientGeneratorConfiguration.h
M  +17   -2    plugins/tools/basictools/kis_tool_gradient.cc
M  +4    -0    plugins/tools/basictools/kis_tool_gradient.h

https://invent.kde.org/graphics/krita/commit/4cfda40a29f9ee7548ab5d2f7320f2fca8674f65

diff --git a/libs/global/kis_global.h b/libs/global/kis_global.h
index 475c18aea1..932646e42d 100644
--- a/libs/global/kis_global.h
+++ b/libs/global/kis_global.h
@@ -1,13 +1,14 @@
 /*
  *  SPDX-FileCopyrightText: 2000 Matthias Elter <elter at kde.org>
  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak at codepimps.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
 #ifndef KISGLOBAL_H_
 #define KISGLOBAL_H_
 
-#include <limits.h>
+#include <limits>
 
 #include <KoConfig.h>
 #include "kis_assert.h"
@@ -15,14 +16,16 @@
 #include <QPoint>
 #include <QPointF>
 
-const quint8 quint8_MAX = UCHAR_MAX;
-const quint16 quint16_MAX = 65535;
+const quint8 quint8_MAX = std::numeric_limits<quint8>::max();
+const quint16 quint16_MAX = std::numeric_limits<quint16>::max();
 
-const qint32 qint32_MAX = (2147483647);
-const qint32 qint32_MIN = (-2147483647 - 1);
+const qint16 qint16_MIN = std::numeric_limits<qint16>::min();
+const qint16 qint16_MAX = std::numeric_limits<qint16>::max();
+const qint32 qint32_MAX = std::numeric_limits<qint32>::max();
+const qint32 qint32_MIN = std::numeric_limits<qint32>::min();
 
-const quint8 MAX_SELECTED = UCHAR_MAX;
-const quint8 MIN_SELECTED = 0;
+const quint8 MAX_SELECTED = std::numeric_limits<quint8>::max();
+const quint8 MIN_SELECTED = std::numeric_limits<quint8>::min();
 const quint8 SELECTION_THRESHOLD = 1;
 
 enum OutlineStyle {
diff --git a/libs/image/kis_asl_layer_style_serializer.cpp b/libs/image/kis_asl_layer_style_serializer.cpp
index ca69ef1ec6..2bc8d758f5 100644
--- a/libs/image/kis_asl_layer_style_serializer.cpp
+++ b/libs/image/kis_asl_layer_style_serializer.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73 at gmail.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -605,8 +606,7 @@ QDomDocument KisAslLayerStyleSerializer::formXmlDocument() const
 
             w.writeOffsetPoint("Ofst", gradientOverlay->gradientOffset());
 
-            // FIXME: Krita doesn't support dithering
-            w.writeBoolean("Dthr", true/*gradientOverlay->dither()*/);
+            w.writeBoolean("Dthr", gradientOverlay->dither());
 
             w.leaveDescriptor();
         }
@@ -667,8 +667,7 @@ QDomDocument KisAslLayerStyleSerializer::formXmlDocument() const
                 w.writeBoolean("Algn", stroke->alignWithLayer());
                 w.writeOffsetPoint("Ofst", stroke->gradientOffset());
 
-                // FIXME: Krita doesn't support dithering
-                w.writeBoolean("Dthr", true/*stroke->dither()*/);
+                w.writeBoolean("Dthr", stroke->dither());
 
             } else if (stroke->fillType() == psd_fill_pattern) {
                 w.writePatternRef("Ptrn", stroke->pattern(), fetchPatternUuidSafe(stroke->pattern(), patternToUuidMap));
@@ -1056,7 +1055,7 @@ void KisAslLayerStyleSerializer::connectCatcherToStyle(KisPSDLayerStyle *style,
     CONN_UNITF("/GrFl/Scl ", "#Prc", setScale, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
     CONN_UNITF("/GrFl/Angl", "#Ang", setAngle, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
     CONN_BOOL("/GrFl/enab", setEffectEnabled, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
-    // CONN_BOOL("/GrFl/Dthr", setDitherNotImplemented, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
+    CONN_BOOL("/GrFl/Dthr", setDither, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
     CONN_BOOL("/GrFl/Rvrs", setReverse, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
     CONN_BOOL("/GrFl/Algn", setAlignWithLayer, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
     CONN_POINT("/GrFl/Ofst", setGradientOffset, gradientOverlay, psd_layer_effects_gradient_overlay, prefix);
@@ -1111,7 +1110,7 @@ void KisAslLayerStyleSerializer::connectCatcherToStyle(KisPSDLayerStyle *style,
     CONN_BOOL("/FrFX/Rvrs", setReverse, stroke, psd_layer_effects_stroke, prefix);
     CONN_BOOL("/FrFX/Algn", setAlignWithLayer, stroke, psd_layer_effects_stroke, prefix);
     CONN_POINT("/FrFX/Ofst", setGradientOffset, stroke, psd_layer_effects_stroke, prefix);
-    // CONN_BOOL("/FrFX/Dthr", setDitherNotImplemented, stroke, psd_layer_effects_stroke, prefix);
+    CONN_BOOL("/FrFX/Dthr", setDither, stroke, psd_layer_effects_stroke, prefix);
 
     // Pattern type
 
diff --git a/libs/image/kis_gradient_painter.cc b/libs/image/kis_gradient_painter.cc
index 6ca41ec9e2..69c2551c33 100644
--- a/libs/image/kis_gradient_painter.cc
+++ b/libs/image/kis_gradient_painter.cc
@@ -1,12 +1,14 @@
 /*
  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian at pagenet.plus.com>
  *  SPDX-FileCopyrightText: 2019 Miguel Lopez <reptillia39 at live.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
 
 #include "kis_gradient_painter.h"
 
+#include <algorithm>
 #include <cfloat>
 
 #include <KoColorSpace.h>
@@ -14,6 +16,8 @@
 #include <KoUpdater.h>
 #include <KoEphemeralResource.h>
 
+#include <KoColorModelStandardIds.h>
+#include <KoColorSpaceRegistry.h>
 #include "kis_global.h"
 #include "kis_paint_device.h"
 #include <resources/KoPattern.h>
@@ -27,6 +31,7 @@
 #include "kis_cached_gradient_shape_strategy.h"
 #include "krita_utils.h"
 #include "KoMixColorsOp.h"
+#include <KisDitherOp.h>
 #include <KoCachedGradient.h>
 
 namespace
@@ -675,10 +680,10 @@ const quint8 *RepeatForwardsPaintPolicy::colorAt(qreal x, qreal y) const
         }
 
         qint16 colorWeights[2];
-        colorWeights[0] = static_cast<quint8>((1.0 - s) * 255 + 0.5);
-        colorWeights[1] = 255 - colorWeights[0];
+        colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
+        colorWeights[1] = qint16_MAX - colorWeights[0];
 
-        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data());
+        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
         
         return m_resultColor.data();
     }
@@ -779,12 +784,12 @@ const quint8 *ConicalGradientPaintPolicy::colorAt(qreal x, qreal y) const
         } else {
             s = (t - antiAliasThresholdNormalizedRev) / antiAliasThresholdNormalizedDbl;
         }
-        
+
         qint16 colorWeights[2];
-        colorWeights[0] = static_cast<quint8>((1.0 - s) * 255 + 0.5);
-        colorWeights[1] = 255 - colorWeights[0];
+        colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
+        colorWeights[1] = qint16_MAX - colorWeights[0];
 
-        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data());
+        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
 
         return m_resultColor.data();
     }
@@ -921,12 +926,12 @@ const quint8 *SpyralGradientRepeatNonePaintPolicy::colorAt(qreal x, qreal y) con
         } else {
             m_extremeColors[1] = (m_cachedGradient->cachedAt(distanceInPixels / m_distanceInPixels));
         }
-        
+
         qint16 colorWeights[2];
-        colorWeights[0] = static_cast<quint8>((1.0 - s) * 255 + 0.5);
-        colorWeights[1] = 255 - colorWeights[0];
+        colorWeights[0] = std::lround((1.0 - s) * qint16_MAX);
+        colorWeights[1] = qint16_MAX - colorWeights[0];
 
-        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data());
+        m_colorSpace->mixColorsOp()->mixColors(m_extremeColors, colorWeights, 2, m_resultColor.data(), qint16_MAX);
 
         return m_resultColor.data();
     }
@@ -1090,14 +1095,16 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                        qint32 startx,
                                        qint32 starty,
                                        qint32 width,
-                                       qint32 height)
+                                       qint32 height,
+                                       bool useDithering)
 {
     return paintGradient(gradientVectorStart,
                          gradientVectorEnd,
                          repeat,
                          antiAliasThreshold,
                          reverseGradient,
-                         QRect(startx, starty, width, height));
+                         QRect(startx, starty, width, height),
+                         useDithering);
 }
 
 bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
@@ -1105,7 +1112,8 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                        enumGradientRepeat repeat,
                                        double antiAliasThreshold,
                                        bool reverseGradient,
-                                       const QRect &applyRect)
+                                       const QRect &applyRect,
+                                       bool useDithering)
 {
     // The following combinations of options have aliasing artifacts
     // where the first color meets the last color of the gradient.
@@ -1123,6 +1131,7 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                  repeat,
                                  antiAliasThreshold,
                                  reverseGradient,
+                                 useDithering,
                                  applyRect,
                                  paintPolicy);
 
@@ -1133,6 +1142,7 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                  repeat,
                                  antiAliasThreshold,
                                  reverseGradient,
+                                 useDithering,
                                  applyRect,
                                  paintPolicy);
 
@@ -1144,6 +1154,7 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                  repeat,
                                  antiAliasThreshold,
                                  reverseGradient,
+                                 useDithering,
                                  applyRect,
                                  paintPolicy);
         }
@@ -1156,6 +1167,7 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                          repeat,
                          antiAliasThreshold,
                          reverseGradient,
+                         useDithering,
                          applyRect,
                          paintPolicy);
 }
@@ -1166,6 +1178,7 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                                        enumGradientRepeat repeat,
                                        double antiAliasThreshold,
                                        bool reverseGradient,
+                                       bool useDithering,
                                        const QRect &applyRect,
                                        T & paintPolicy)
 {
@@ -1262,16 +1275,31 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
 
     KisPaintDeviceSP dev = device()->createCompositionSourceDevice();
 
-    const KoColorSpace * colorSpace = dev->colorSpace();
-    const qint32 pixelSize = colorSpace->pixelSize();
+    KoID depthId;
+    const KoColorSpace *destCs = dev->colorSpace();
+
+    if (destCs->colorDepthId() == Integer8BitsColorDepthID) {
+        depthId = Integer16BitsColorDepthID;
+    } else {
+        depthId = destCs->colorDepthId();
+    }
+
+    const KoColorSpace *mixCs = KoColorSpaceRegistry::instance()->colorSpace(destCs->colorModelId().id(), depthId.id(), destCs->profile());
+    const quint32 mixPixelSize = mixCs->pixelSize();
+
+    KisPaintDeviceSP tmp(new KisPaintDevice(mixCs));
+    tmp->setDefaultBounds(dev->defaultBounds());
+    tmp->clear();
+
+    const KisDitherOp* op = mixCs->ditherOp(destCs->colorDepthId().id(), useDithering ? DITHER_BEST : DITHER_NONE);
 
     Q_FOREACH (const Private::ProcessRegion &r, m_d->processRegions) {
         QRect processRect = r.processRect;
         QSharedPointer<KisGradientShapeStrategy> shapeStrategy = r.precalculatedShapeStrategy;
 
-        KoCachedGradient cachedGradient(gradient(), qMax(processRect.width(), processRect.height()), colorSpace);
+        KoCachedGradient cachedGradient(gradient(), qMax(processRect.width(), processRect.height()), mixCs);
 
-        KisSequentialIteratorProgress it(dev, processRect, progressUpdater());
+        KisSequentialIteratorProgress it(tmp, processRect, progressUpdater());
 
         paintPolicy.setup(gradientVectorStart,
                           gradientVectorEnd,
@@ -1282,11 +1310,36 @@ bool KisGradientPainter::paintGradient(const QPointF& gradientVectorStart,
                           &cachedGradient);
 
         while (it.nextPixel()) {
-            memcpy(it.rawData(), paintPolicy.colorAt(it.x(), it.y()), pixelSize);
+            const quint8 *const pixel {paintPolicy.colorAt(it.x(), it.y())};
+            memcpy(it.rawData(), pixel, mixPixelSize);
         }
 
-        bitBlt(processRect.topLeft(), dev, processRect);
+        KisRandomAccessorSP dstIt = dev->createRandomAccessorNG();
+        KisRandomConstAccessorSP srcIt = tmp->createRandomConstAccessorNG();
+
+        int rows = 1;
+        int columns = 1;
+
+        for (int y = processRect.y(); y <= processRect.bottom(); y += rows) {
+            rows = qMin(srcIt->numContiguousRows(y), qMin(dstIt->numContiguousRows(y), processRect.bottom() - y + 1));
+
+            for (int x = processRect.x(); x <= processRect.right(); x += columns) {
+                columns = qMin(srcIt->numContiguousColumns(x), qMin(dstIt->numContiguousColumns(x), processRect.right() - x + 1));
+
+                srcIt->moveTo(x, y);
+                dstIt->moveTo(x, y);
+
+                const qint32 srcRowStride = srcIt->rowStride(x, y);
+                const qint32 dstRowStride = dstIt->rowStride(x, y);
+                const quint8 *srcPtr = srcIt->rawDataConst();
+                quint8 *dstPtr = dstIt->rawData();
+
+                op->dither(srcPtr, srcRowStride, dstPtr, dstRowStride, x, y, columns, rows);
+            }
+        }
     }
 
+    bitBlt(requestedRect.topLeft(), dev, requestedRect);
+
     return true;
 }
diff --git a/libs/image/kis_gradient_painter.h b/libs/image/kis_gradient_painter.h
index 66b0ba2fcf..79142ba70e 100644
--- a/libs/image/kis_gradient_painter.h
+++ b/libs/image/kis_gradient_painter.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian at pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -63,7 +64,8 @@ public:
                        qint32 startx,
                        qint32 starty,
                        qint32 width,
-                       qint32 height);
+                       qint32 height,
+                       bool useDithering = false);
 
     // convenience overload
     bool paintGradient(const QPointF& gradientVectorStart,
@@ -71,7 +73,8 @@ public:
                        enumGradientRepeat repeat,
                        double antiAliasThreshold,
                        bool reverseGradient,
-                       const QRect &applyRect);
+                       const QRect &applyRect,
+                       bool useDithering = false);
 
     template <class T> 
     bool paintGradient(const QPointF& gradientVectorStart,
@@ -79,6 +82,7 @@ public:
                        enumGradientRepeat repeat,
                        double antiAliasThreshold,
                        bool reverseGradient,
+                       bool useDithering,
                        const QRect &applyRect,
                        T & paintPolicy);
 
diff --git a/libs/image/layerstyles/kis_ls_utils.cpp b/libs/image/layerstyles/kis_ls_utils.cpp
index 79135d8a7a..1d56ebd937 100644
--- a/libs/image/layerstyles/kis_ls_utils.cpp
+++ b/libs/image/layerstyles/kis_ls_utils.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2015 Dmitry Kazakov <dimula73 at gmail.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -522,7 +523,8 @@ namespace KisLsUtils
             gc.paintGradient(gradStart, gradEnd,
                              repeat, 0.0,
                              config->reverse(),
-                             applyRect);
+                             applyRect,
+                             config->dither());
         }
     }
 
diff --git a/libs/pigment/KisDitherMaths.h b/libs/pigment/KisDitherMaths.h
new file mode 100644
index 0000000000..e3c4a872d2
--- /dev/null
+++ b/libs/pigment/KisDitherMaths.h
@@ -0,0 +1,203 @@
+/*
+ * SPDX-FileCopyrightText: 1987, 1988, 1989, 1998  The Open Group
+ * SPDX-FileCopyrightText: 1987, 1988, 1989 Digital Equipment Corporation
+ * SPDX-FileCopyrightText: 1999, 2004, 2008 Keith Packard
+ * SPDX-FileCopyrightText: 2000 SuSE, Inc.
+ * SPDX-FileCopyrightText: 2000 Keith Packard, member of The XFree86 Project, Inc.
+ * SPDX-FileCopyrightText: 2004, 2005, 2007, 2008, 2009, 2010 Red Hat, Inc.
+ * SPDX-FileCopyrightText: 2004 Nicholas Miell
+ * SPDX-FileCopyrightText: 2005 Lars Knoll & Zack Rusin, Trolltech
+ * SPDX-FileCopyrightText: 2005 Trolltech AS
+ * SPDX-FileCopyrightText: 2007 Luca Barbato
+ * SPDX-FileCopyrightText: 2008 Aaron Plattner, NVIDIA Corporation
+ * SPDX-FileCopyrightText: 2008 Rodrigo Kumpera
+ * SPDX-FileCopyrightText: 2008 André Tupinambá
+ * SPDX-FileCopyrightText: 2008 Mozilla Corporation
+ * SPDX-FileCopyrightText: 2008 Frederic Plourde
+ * SPDX-FileCopyrightText: 2009, Oracle and/or its affiliates. All rights reserved.
+ * SPDX-FileCopyrightText: 2009, 2010 Nokia Corporation
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * This version is part of Krita. Based on Pixman's implementation in commits
+ * cb2ec4268fbde0df3b588ce5cbe2e43e0465452 and
+ * ddcc41b999562efdd9f88daa51ffbf39782748b5.
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <array>
+#include <cstdint>
+
+namespace KisDitherMaths
+{
+constexpr static const std::array<uint16_t, 4096> mask = {{
+    3039, 1368, 3169, 103,  2211, 1248, 2981, 668,  2633, 37,   3963, 2903, 384,  2564, 3115, 1973, 3348, 830,  2505, 1293, 3054, 1060, 1505, 3268, 400,  1341, 593,  3802, 3384, 429,  4082, 1411, 2503, 3863, 126,  1292, 1887, 2855, 205,
+    2094, 2977, 1899, 3924, 356,  3088, 2500, 3942, 1409, 2293, 1734, 3732, 1291, 3227, 277,  2054, 786,  2871, 411,  2425, 1678, 3986, 455,  2879, 2288, 388,  1972, 3851, 778,  2768, 3697, 944,  2123, 1501, 3533, 937,  1713, 1381, 3888,
+    156,  1242, 516,  2888, 1607, 3676, 632,  2397, 3804, 2673, 1898, 3534, 2593, 1777, 1170, 2299, 3013, 1838, 523,  3053, 1647, 3601, 3197, 959,  1520, 3633, 893,  2437, 3367, 2187, 1258, 137,  1965, 401,  3546, 643,  3087, 2498, 733,
+    2786, 3371, 4053, 1266, 1977, 3663, 183,  2570, 2107, 1183, 3708, 907,  2473, 1151, 3363, 1527, 1902, 232,  3903, 3060, 496,  2486, 3206, 2165, 861,  2387, 3653, 2101, 3972, 132,  2162, 3437, 1827, 215,  895,  3114, 271,  969,  2932,
+    197,  1598, 878,  3696, 1140, 2120, 904,  2431, 302,  3846, 2675, 481,  3187, 66,   1440, 650,  3833, 2826, 3435, 901,  2936, 2111, 250,  1875, 3609, 1174, 1747, 162,  2346, 3420, 913,  3172, 1383, 752,  3298, 1735, 3540, 2938, 249,
+    2324, 526,  3099, 2561, 1324, 2347, 1861, 1200, 3702, 257,  3442, 1514, 2999, 992,  1766, 2735, 1163, 478,  2943, 1279, 3635, 2177, 1464, 3672, 2386, 3871, 3340, 2690, 64,   3489, 2811, 3999, 633,  1948, 1243, 2269, 1807, 1143, 2750,
+    3729, 1790, 2363, 1053, 1537, 2636, 4065, 1076, 1476, 3869, 450,  2200, 2676, 658,  2979, 1548, 544,  1913, 2838, 3911, 116,  2698, 517,  1295, 3997, 1739, 3665, 1083, 3509, 599,  3400, 118,  2956, 720,  2689, 1907, 567,  2523, 284,
+    3397, 711,  3219, 2450, 3985, 1665, 2549, 562,  3011, 1855, 729,  1355, 528,  1908, 2456, 1384, 337,  1540, 2654, 3138, 3513, 703,  4080, 3314, 2047, 855,  3037, 209,  3317, 577,  1828, 17,   2336, 3193, 2748, 962,  3441, 1450, 3246,
+    1075, 3878, 2615, 3497, 1033, 2310, 1442, 2183, 1654, 3254, 2061, 738,  2832, 148,  2030, 1670, 909,  3850, 2109, 1533, 4046, 1085, 3098, 3897, 1378, 2248, 3829, 1495, 1966, 23,   797,  3427, 1124, 4057, 95,   2787, 2190, 3074, 3950,
+    742,  3194, 1999, 3386, 1113, 16,   1657, 2804, 201,  1543, 383,  2559, 1325, 3604, 2068, 2493, 3771, 1284, 3460, 710,  1716, 2447, 80,   3811, 2032, 347,  2227, 15,   1689, 397,  3084, 662,  3798, 973,  43,   2608, 3143, 1459, 2423,
+    4066, 2770, 3191, 1283, 2630, 314,  3235, 2289, 72,   1822, 2840, 924,  350,  2653, 1057, 3715, 2235, 2775, 346,  2083, 1553, 3292, 1081, 274,  1686, 1188, 2327, 3743, 578,  2234, 3916, 2519, 1011, 3056, 2207, 3438, 3890, 537,  1617,
+    837,  3094, 373,  2795, 1980, 276,  3951, 1353, 3015, 844,  1724, 3651, 2923, 1316, 4092, 2504, 3627, 1936, 2854, 2461, 3929, 1193, 421,  3746, 820,  1180, 286,  2261, 532,  3625, 1812, 802,  1327, 3527, 670,  3730, 2025, 3124, 3565,
+    529,  2960, 1769, 1390, 3196, 2494, 3756, 796,  3618, 2602, 3463, 2847, 166,  953,  1745, 2900, 438,  2070, 1418, 3741, 639,  1205, 1891, 2882, 2282, 4012, 1182, 1696, 3630, 951,  2904, 2170, 3530, 375,  2320, 2742, 1132, 701,  3216,
+    2023, 847,  1230, 310,  3431, 770,  1961, 3531, 1702, 2181, 3370, 1877, 3072, 1571, 3389, 1071, 2415, 3782, 2803, 1610, 2454, 1211, 182,  1655, 2322, 1282, 3372, 287,  3935, 704,  1232, 415,  1910, 2286, 1399, 556,  1964, 4068, 2444,
+    3605, 1272, 3345, 816,  3526, 256,  2402, 2777, 955,  345,  3289, 111,  2727, 635,  2396, 1488, 3331, 600,  1032, 1575, 4026, 515,  3507, 2433, 1605, 460,  3364, 2783, 1810, 1397, 2334, 223,  2945, 688,  2533, 99,   2705, 624,  3944,
+    2073, 46,   2978, 508,  2132, 269,  3173, 3453, 2631, 4076, 694,  1892, 2586, 972,  2178, 3470, 1695, 2849, 3141, 77,   3884, 994,  3029, 1536, 673,  3083, 124,  2583, 1722, 2821, 1944, 4027, 1661, 3176, 3728, 1337, 1813, 3503, 2035,
+    3930, 157,  2537, 1865, 3096, 2646, 1941, 3252, 1449, 135,  2836, 3758, 2139, 84,   3678, 3106, 3862, 1545, 3307, 1320, 3955, 1031, 3664, 1306, 2460, 776,  1487, 3294, 1187, 3990, 1903, 1021, 549,  1484, 943,  3027, 97,   3853, 1499,
+    2880, 198,  2575, 3995, 1089, 1587, 2475, 3282, 339,  2657, 1158, 2105, 1493, 3943, 580,  3232, 1287, 846,  48,   2480, 2112, 771,  2534, 459,  3134, 850,  1298, 3790, 325,  3652, 1249, 193,  940,  2202, 3895, 1829, 911,  1366, 2577,
+    1069, 534,  2104, 1009, 2667, 392,  1983, 2917, 1645, 324,  3439, 2869, 3705, 1767, 2592, 756,  2916, 3683, 2276, 2850, 2053, 3594, 2403, 3181, 634,  3699, 1933, 906,  519,  2150, 3673, 764,  1770, 2220, 3795, 3336, 502,  3547, 2339,
+    1110, 301,  2210, 3354, 3643, 569,  1518, 2940, 3973, 1138, 1613, 2773, 2127, 2983, 1671, 769,  2161, 3800, 2730, 3127, 1179, 533,  3259, 2284, 4014, 1651, 2820, 3566, 653,  1839, 3455, 2399, 789,  3149, 2244, 1863, 1099, 474,  2307,
+    158,  3541, 1312, 1711, 0,    3902, 360,  1629, 1091, 395,  1781, 1191, 2374, 3353, 1419, 3225, 206,  2931, 3553, 1046, 54,   1646, 2470, 910,  1860, 3137, 3770, 2635, 1562, 2809, 1215, 3788, 222,  2199, 3335, 67,   3606, 524,  1001,
+    3309, 2410, 3473, 591,  1619, 291,  2502, 3629, 2891, 335,  741,  3378, 168,  2384, 3129, 4051, 22,   1444, 3613, 543,  3893, 186,  2665, 4062, 933,  3058, 2142, 449,  2711, 3224, 849,  1330, 3349, 2195, 2670, 3484, 2993, 32,   3774,
+    2722, 1859, 2548, 1268, 583,  2027, 3165, 2807, 4029, 227,  2897, 1434, 721,  1816, 195,  905,  2066, 3258, 1754, 970,  2674, 1880, 2338, 3915, 1485, 2660, 14,   1313, 2914, 2046, 4074, 791,  1917, 1301, 1725, 2687, 2019, 1443, 418,
+    1186, 1664, 2859, 1049, 2056, 2741, 1226, 1589, 3186, 2042, 1377, 3449, 1574, 3941, 1063, 1930, 2501, 3751, 2930, 671,  4031, 888,  2081, 1544, 684,  1117, 351,  4052, 1698, 2393, 3881, 1439, 785,  1277, 2013, 3488, 441,  2459, 3980,
+    3061, 3481, 2543, 419,  3020, 609,  3515, 1350, 799,  2878, 348,  2034, 3966, 1824, 950,  3281, 1394, 2239, 3452, 55,   3922, 3119, 892,  3785, 3023, 2140, 782,  2492, 3817, 241,  3355, 2424, 856,  3639, 612,  2556, 245,  2858, 705,
+    2316, 3562, 495,  1748, 128,  1912, 1454, 280,  2552, 3905, 3130, 2274, 3472, 834,  3055, 240,  2692, 471,  2272, 3301, 2632, 1080, 3693, 2136, 1029, 1364, 590,  1611, 4067, 1190, 2360, 3827, 261,  3180, 1768, 3471, 1103, 3003, 520,
+    3674, 151,  2571, 555,  3033, 982,  2353, 504,  1259, 2555, 149,  3889, 3380, 493,  3178, 1681, 663,  1924, 2990, 49,   1792, 3861, 1192, 1987, 3273, 297,  1457, 3043, 1177, 2292, 3249, 2829, 3682, 1154, 1758, 428,  2872, 1993, 1500,
+    3703, 1129, 3421, 1840, 3754, 163,  659,  1733, 3182, 38,   2875, 1957, 3614, 2237, 78,   1873, 2801, 1513, 2121, 1074, 2516, 667,  3710, 1429, 2430, 2088, 2830, 1072, 3557, 1531, 2733, 1955, 3286, 3590, 1826, 2778, 1068, 1932, 1452,
+    2279, 1185, 3564, 3952, 1391, 2726, 3313, 2331, 870,  3709, 1674, 2772, 4085, 808,  2596, 3848, 927,  538,  2335, 3334, 773,  3597, 1347, 109,  2663, 608,  2108, 2994, 936,  1524, 2922, 3968, 2422, 1467, 845,  3870, 321,  2704, 1073,
+    3308, 3680, 823,  430,  3375, 4030, 112,  2171, 2695, 267,  3374, 731,  1627, 3919, 1871, 352,  3839, 1370, 234,  794,  1532, 3245, 647,  3575, 74,   3045, 2766, 285,  2174, 498,  1059, 1551, 385,  3125, 2598, 143,  1128, 2095, 3395,
+    318,  1590, 3524, 1345, 1969, 242,  2759, 2092, 947,  3926, 3244, 2356, 1658, 6,    3593, 2554, 1172, 1995, 371,  2755, 3417, 2294, 1570, 3164, 748,  2517, 1401, 3111, 2420, 1662, 2910, 1276, 3276, 854,  1804, 4000, 1253, 2987, 229,
+    2344, 3184, 649,  2196, 2921, 4095, 2389, 1289, 2193, 2579, 4023, 757,  1858, 986,  3199, 2514, 3475, 4021, 2154, 651,  1432, 3468, 2404, 574,  1799, 3105, 2145, 86,   2614, 3218, 1565, 4088, 2481, 3079, 1815, 323,  1212, 3837, 759,
+    2159, 435,  3223, 784,  3659, 1114, 1888, 550,  1221, 3786, 1803, 499,  2117, 185,  3763, 942,  589,  2001, 3838, 1483, 3154, 2256, 468,  2544, 3403, 898,  1208, 2610, 3622, 967,  1929, 378,  3781, 220,  1656, 1115, 3347, 2428, 3822,
+    1577, 712,  1959, 110,  2765, 1762, 3854, 979,  2928, 3714, 1371, 746,  3969, 2884, 975,  3779, 641,  1142, 159,  1460, 702,  3485, 2866, 2495, 3330, 1305, 3937, 1635, 2229, 2962, 146,  4055, 3091, 2417, 100,  3508, 2933, 4006, 1167,
+    1920, 2760, 3552, 2545, 433,  2845, 142,  1056, 1886, 3616, 1435, 2099, 3803, 1749, 27,   1446, 3350, 2843, 884,  3310, 2948, 2103, 447,  1351, 187,  2895, 3655, 1256, 3036, 932,  3325, 2257, 451,  1915, 40,   2780, 2438, 1112, 1814,
+    423,  2290, 1905, 2898, 3419, 2306, 3760, 1938, 486,  1019, 1791, 3010, 2628, 203,  3408, 1269, 2507, 1606, 862,  2779, 2078, 952,  1529, 2638, 708,  3332, 1413, 2,    1726, 1156, 3500, 2392, 3791, 3076, 812,  107,  2861, 501,  3050,
+    3487, 2455, 594,  1731, 2685, 1498, 680,  3908, 2621, 3529, 1786, 2236, 342,  2569, 1526, 3722, 230,  1290, 3203, 3947, 1609, 3516, 467,  3267, 3685, 1461, 3140, 3569, 367,  1759, 928,  2754, 1332, 2219, 4034, 260,  655,  1984, 978,
+    3814, 617,  2086, 3525, 279,  3841, 1373, 3361, 319,  2251, 3066, 407,  2382, 3918, 3133, 2168, 762,  1523, 507,  2641, 1677, 4025, 2413, 1584, 793,  2049, 1109, 3962, 2218, 1194, 3692, 266,  1687, 981,  3103, 740,  3983, 1005, 3434,
+    570,  2383, 1942, 2718, 676,  2462, 1007, 2089, 1308, 2222, 233,  2568, 829,  1241, 2669, 3987, 514,  3303, 69,   3142, 1603, 3560, 2295, 3288, 1497, 2696, 1764, 2865, 1058, 3271, 1914, 477,  2529, 3927, 1736, 1273, 3752, 2029, 1012,
+    565,  2798, 4078, 1949, 3305, 1175, 2179, 380,  3366, 1195, 3849, 2637, 416,  2959, 125,  3396, 2467, 2036, 3234, 2340, 68,   2819, 1436, 2011, 3139, 1704, 4073, 860,  3582, 1468, 2969, 211,  3157, 4056, 866,  2935, 2000, 3923, 31,
+    2157, 1477, 2429, 1147, 3792, 2557, 774,  2802, 1153, 3747, 464,  3192, 42,   3904, 539,  1474, 2283, 803,  2876, 1061, 75,   3477, 747,  2893, 1538, 3626, 251,  1322, 2506, 189,  2791, 3667, 939,  2991, 1971, 175,  3195, 1416, 3648,
+    1857, 3052, 454,  851,  3789, 1271, 1906, 3694, 2484, 406,  2757, 26,   1189, 2909, 296,  2215, 3784, 1864, 637,  2715, 1673, 3445, 581,  1572, 3059, 3469, 761,  2984, 1737, 2058, 440,  1414, 1921, 121,  2527, 894,  2223, 1302, 2377,
+    3077, 2666, 3759, 3198, 1811, 3661, 2166, 2731, 1883, 359,  3285, 2458, 1805, 3459, 926,  3834, 675,  1893, 1496, 2612, 657,  3523, 1763, 2354, 564,  961,  1367, 3977, 1588, 2714, 322,  3446, 1088, 625,  3887, 1354, 3535, 2090, 3316,
+    1760, 1127, 483,  3491, 1421, 2301, 94,   1202, 3740, 2311, 1014, 1878, 3836, 180,  3412, 991,  2868, 3953, 3450, 3081, 1632, 4071, 1882, 3543, 726,  1719, 179,  1171, 364,  1420, 622,  3090, 1490, 946,  4007, 2212, 1102, 619,  2739,
+    2189, 1669, 2937, 3426, 39,   3940, 2191, 1264, 887,  4091, 2792, 2135, 4,    2883, 2281, 631,  3044, 1641, 2232, 3243, 1773, 2319, 827,  2591, 629,  3938, 2426, 3222, 2629, 1044, 3879, 3293, 1952, 2749, 275,  2590, 472,  1372, 2496,
+    660,  3669, 2264, 208,  915,  2167, 561,  2828, 307,  3265, 1104, 3964, 2155, 3425, 1951, 4077, 2391, 283,  3387, 2581, 115,  1415, 3069, 3896, 141,  3158, 1214, 442,  2405, 1349, 3085, 425,  2528, 3002, 312,  1602, 3588, 1137, 3323,
+    1963, 1002, 3578, 2521, 127,  925,  2970, 273,  3737, 1573, 167,  2863, 1509, 800,  147,  2059, 2942, 409,  921,  3151, 1451, 3909, 3333, 2844, 2096, 1512, 3136, 1210, 1798, 2709, 1331, 3586, 1034, 1521, 2441, 2926, 488,  2585, 775,
+    3031, 2693, 879,  3602, 1173, 2028, 3654, 2781, 841,  1975, 1507, 3646, 768,  3991, 2012, 996,  3544, 1666, 3810, 1990, 3360, 753,  2597, 3736, 304,  1473, 3828, 485,  1334, 4008, 2072, 3495, 1136, 2806, 2004, 3236, 1010, 2130, 3819,
+    1750, 3567, 644,  2515, 1794, 3636, 698,  2137, 1162, 832,  3761, 326,  2613, 513,  3302, 3820, 357,  3163, 2259, 3733, 101,  1922, 1386, 3587, 1640, 28,   1286, 2141, 1761, 2918, 693,  1639, 457,  3250, 2434, 365,  2599, 1729, 3284,
+    2643, 306,  2793, 689,  1090, 104,  1309, 2305, 1831, 2776, 859,  2446, 2915, 1778, 3337, 2677, 614,  1508, 2409, 469,  4033, 1321, 3563, 402,  3131, 2720, 1093, 1569, 4042, 1229, 2277, 216,  3046, 1817, 57,   3006, 1684, 4059, 2016,
+    795,  2440, 1652, 1960, 610,  2763, 920,  3864, 3110, 1026, 2326, 3762, 3233, 521,  3856, 173,  2457, 3939, 2138, 1262, 3572, 989,  3021, 2238, 119,  1445, 3832, 1809, 2297, 3467, 2700, 3684, 3102, 394,  4036, 2050, 3256, 89,   2198,
+    1079, 248,  1845, 3805, 3104, 880,  1779, 2688, 717,  2373, 1375, 262,  2249, 3071, 13,   2813, 3429, 1600, 3984, 2416, 3603, 1299, 2298, 998,  3492, 1393, 2951, 10,   4009, 1247, 3462, 1679, 2204, 414,  2736, 316,  1894, 2816, 1050,
+    3373, 1462, 3107, 817,  3464, 21,   1835, 4070, 568,  1178, 3718, 875,  3168, 466,  2974, 1458, 2084, 616,  1564, 1018, 1693, 546,  1244, 3899, 716,  3160, 3608, 2877, 1220, 334,  3443, 2270, 44,   3000, 1843, 3928, 3405, 766,  3686,
+    2040, 587,  993,  2647, 387,  930,  2753, 630,  3274, 150,  2808, 453,  3638, 1092, 2352, 3030, 239,  2562, 700,  3240, 1257, 4016, 730,  1515, 2203, 2551, 417,  1866, 1123, 2348, 2902, 1550, 2678, 2075, 3238, 1630, 2531, 2115, 1255,
+    4054, 840,  290,  3874, 2477, 3399, 2250, 3577, 2817, 1626, 2576, 1356, 2315, 792,  2087, 2618, 1612, 3855, 1263, 3637, 1036, 494,  1535, 2553, 1198, 1715, 3867, 3170, 1359, 1954, 3483, 1539, 2069, 3886, 1772, 2487, 1534, 2045, 3242,
+    806,  1578, 2018, 3948, 1423, 3596, 2076, 2466, 3424, 139,  3688, 871,  4049, 2852, 3342, 547,  3719, 327,  852,  3505, 207,  2794, 542,  3600, 45,   2411, 3324, 1788, 3012, 1235, 61,   2655, 917,  253,  1986, 3738, 313,  1706, 4072,
+    120,  3229, 957,  597,  2024, 3262, 2453, 2857, 2002, 3190, 210,  2784, 2206, 300,  2400, 3766, 553,  3152, 218,  1150, 2988, 883,  3753, 627,  2664, 3831, 437,  3385, 1008, 2957, 60,   1636, 891,  2899, 1776, 3062, 1315, 2026, 194,
+    1643, 2079, 1296, 3201, 2465, 1379, 1927, 3898, 1125, 1847, 2846, 1552, 1028, 2725, 2169, 787,  3202, 1441, 3982, 3032, 1052, 3251, 605,  2639, 3073, 1431, 3642, 2329, 2949, 341,  1634, 833,  129,  4020, 916,  3571, 669,  1506, 3411,
+    821,  2856, 1207, 2337, 2683, 3448, 340,  2214, 3128, 235,  1738, 1288, 2833, 2419, 606,  1884, 2668, 552,  3765, 1176, 399,  2302, 596,  3591, 2634, 767,  3845, 2767, 995,  3967, 491,  3057, 814,  2300, 3422, 691,  3797, 254,  3645,
+    509,  3478, 1836, 2119, 475,  2445, 1525, 2175, 3539, 914,  1926, 473,  1157, 1800, 3971, 2701, 3739, 2129, 3486, 1333, 1784, 2366, 2982, 1070, 4089, 1802, 73,   1642, 3958, 835,  1837, 1480, 4043, 1217, 2469, 3416, 2113, 88,   3668,
+    1240, 3255, 3920, 2355, 3167, 2003, 2645, 3936, 3228, 1592, 1144, 3474, 2394, 79,   1820, 2241, 1594, 3656, 2584, 153,  1448, 3034, 2005, 2511, 1692, 1335, 3913, 217,  2822, 3391, 745,  3813, 192,  1274, 2941, 3847, 2489, 3440, 744,
+    161,  1422, 1086, 572,  3004, 2617, 338,  3807, 2031, 236,  2472, 3065, 2098, 3358, 362,  2163, 3574, 497,  2788, 1970, 948,  3885, 685,  3100, 1712, 2228, 292,  1408, 1016, 164,  3537, 1417, 941,  34,   2172, 3001, 358,  1491, 3147,
+    699,  3356, 258,  1149, 2946, 1787, 3931, 382,  1146, 3291, 818,  2890, 2379, 1096, 3679, 1328, 1901, 3162, 2747, 1730, 2253, 5,    1556, 2818, 2093, 3166, 2522, 3410, 2287, 1701, 956,  3237, 620,  1596, 3300, 1307, 511,  3701, 1020,
+    2939, 1362, 2532, 3208, 749,  3641, 160,  1522, 2624, 1095, 4086, 826,  2841, 3583, 2173, 1727, 723,  2925, 1911, 2482, 3726, 863,  1962, 4028, 1111, 2835, 3773, 2449, 2022, 582,  3278, 923,  2619, 2152, 4039, 92,   1934, 3145, 677,
+    2530, 53,   2303, 1003, 458,  3989, 739,  3321, 1064, 369,  3556, 877,  1900, 426,  3876, 1,    3617, 2106, 1197, 2805, 3634, 857,  2706, 1504, 2418, 682,  3868, 20,   1139, 1688, 2333, 3311, 2907, 1945, 265,  2385, 3433, 1601, 636,
+    2620, 3095, 4044, 386,  3382, 1184, 527,  2814, 3414, 2342, 465,  1889, 1343, 874,  3479, 1502, 2233, 3689, 1385, 559,  2745, 1463, 3465, 376,  1718, 3217, 4045, 1580, 3612, 2525, 1228, 3018, 1958, 3725, 2358, 1361, 3996, 1581, 3063,
+    1224, 2737, 1475, 2442, 3946, 191,  1796, 2128, 3975, 134,  1916, 3318, 1597, 2071, 3749, 2672, 403,  1278, 602,  3745, 3220, 1374, 445,  2064, 3830, 243,  1252, 2390, 1563, 2724, 3875, 1818, 1346, 165,  1650, 3264, 2680, 117,  2998,
+    4081, 343,  2799, 9,    3122, 1743, 3724, 1040, 2231, 3842, 1209, 900,  398,  2851, 697,  1797, 3482, 293,  2679, 1649, 566,  2954, 91,   2697, 714,  2060, 3211, 781,  480,  3040, 1038, 2611, 666,  2989, 3458, 1201, 2796, 548,  2975,
+    839,  3121, 1850, 4001, 2208, 1631, 790,  2558, 2972, 1148, 3213, 1849, 3624, 971,  2102, 108,  772,  3101, 2589, 3777, 1042, 656,  3907, 2097, 1615, 2540, 805,  1935, 1231, 3494, 2451, 268,  2995, 750,  2682, 2020, 3024, 1392, 2124,
+    3279, 106,  2217, 1387, 822,  3214, 3825, 2160, 1000, 2395, 3691, 228,  4038, 1872, 3413, 1608, 2225, 3536, 303,  1653, 886,  2541, 224,  4037, 2252, 1428, 172,  3504, 958,  2848, 113,  3628, 1834, 3979, 19,   2317, 779,  2797, 518,
+    3174, 3549, 1482, 2266, 444,  2014, 3555, 2439, 1213, 3113, 535,  1135, 3204, 3858, 2309, 931,  623,  2009, 3359, 1566, 140,  3550, 1808, 3872, 2488, 1152, 3764, 2892, 3960, 2412, 353,  1223, 1825, 3444, 3116, 1717, 1082, 2313, 1280,
+    2661, 82,   3852, 1389, 3200, 2330, 3812, 2038, 3581, 1728, 1039, 3339, 2427, 586,  2580, 1238, 3328, 2280, 1047, 595,  2662, 1363, 3338, 1620, 3934, 2497, 1881, 1054, 3954, 3215, 864,  2887, 1801, 320,  3519, 2378, 3704, 1753, 424,
+    2958, 1660, 4005, 2601, 1116, 3912, 2381, 573,  2740, 200,  828,  1667, 432,  1931, 1035, 1616, 3598, 2640, 728,  264,  1437, 557,  3501, 2966, 372,  3734, 974,  1978, 758,  2719, 1145, 452,  1433, 725,  2681, 408,  3843, 1918, 1547,
+    3906, 1996, 503,  1456, 3019, 3493, 1700, 3742, 355,  2134, 176,  1311, 615,  2867, 315,  1680, 1314, 8,    3297, 1494, 783,  1950, 83,   2656, 1382, 3561, 138,  2834, 1404, 330,  1904, 3156, 1027, 1357, 3381, 3041, 3666, 2729, 734,
+    3415, 177,  3051, 2021, 4079, 2823, 3775, 2186, 2616, 869,  1668, 3148, 2367, 3315, 393,  4075, 1870, 2920, 3343, 2362, 3188, 1303, 2782, 825,  3171, 259,  2905, 3717, 2538, 184,  2074, 838,  2860, 2407, 1024, 3496, 3008, 3706, 1985,
+    2349, 3623, 2582, 4058, 2184, 2694, 3873, 2964, 990,  3346, 690,  2033, 1066, 2201, 3490, 2971, 718,  3700, 2188, 4061, 391,  1989, 2325, 1430, 3150, 2125, 2526, 592,  1403, 976,  2351, 1165, 1851, 114,  3921, 2063, 613,  1358, 2785,
+    1623, 2254, 25,   3542, 1045, 246,  1852, 3554, 87,   2243, 3615, 1169, 727,  1705, 968,  3957, 3185, 1251, 500,  4063, 1751, 2622, 842,  1519, 90,   3393, 819,  490,  1874, 999,  571,  1275, 2271, 1586, 4040, 2448, 3126, 3731, 436,
+    885,  1708, 2421, 24,   1599, 889,  2563, 1199, 645,  70,   4013, 1237, 3723, 1694, 3499, 3,    3266, 484,  2997, 3390, 1233, 2842, 3687, 152,  3480, 1084, 3698, 881,  2490, 1542, 3992, 2209, 692,  1690, 3022, 1470, 2625, 2114, 3512,
+    2359, 381,  2684, 1897, 3368, 1395, 3080, 289,  2065, 3981, 2758, 1141, 3097, 1472, 2870, 3352, 3707, 225,  3159, 505,  1895, 214,  1222, 1774, 2686, 3978, 3275, 1196, 3518, 2825, 3270, 1720, 3796, 3466, 2650, 1841, 298,  899,  2862,
+    2091, 2671, 1744, 3735, 801,  1560, 349,  2262, 903,  1833, 2524, 512,  3117, 1793, 2827, 476,  3038, 1216, 2550, 3826, 980,  431,  4048, 35,   2992, 1265, 1595, 765,  3675, 76,   2247, 696,  3456, 1254, 2452, 664,  1757, 2133, 3750,
+    145,  2332, 1554, 1981, 3580, 2712, 868,  3640, 2919, 638,  2275, 1427, 309,  2595, 2006, 492,  2226, 178,  2911, 836,  1528, 3028, 2240, 3327, 404,  3970, 707,  1294, 2464, 2131, 4032, 2600, 3319, 1406, 2913, 3974, 2156, 1425, 221,
+    3877, 2017, 811,  3662, 272,  3287, 1988, 2408, 3357, 1746, 598,  3239, 3823, 2182, 2934, 1078, 2604, 3840, 1697, 2906, 413,  3210, 3880, 331,  2644, 1260, 848,  3042, 2535, 1077, 1438, 3261, 2365, 1561, 3799, 85,   3082, 1876, 674,
+    3932, 1101, 3644, 1344, 1943, 2401, 390,  3835, 1048, 2572, 1541, 1133, 3075, 3584, 308,  2889, 1065, 1869, 601,  3783, 282,  1181, 736,  3312, 2368, 1126, 3383, 1675, 2734, 1426, 628,  2873, 1317, 843,  2717, 2048, 1004, 2536, 333,
+    1782, 3295, 1517, 219,  2153, 815,  3502, 1579, 2268, 987,  3409, 1780, 4018, 354,  665,  3914, 47,   1956, 456,  1006, 2010, 3406, 1130, 3621, 2894, 1549, 3092, 2485, 640,  3993, 3179, 1270, 3436, 585,  1925, 3757, 2304, 136,  1976,
+    1486, 646,  3520, 50,   3155, 1637, 2435, 3522, 1937, 2756, 3748, 661,  2224, 58,   3230, 2357, 1830, 3892, 170,  3607, 1447, 3949, 190,  3392, 1336, 584,  4010, 918,  3016, 3670, 1155, 2406, 52,   1304, 3009, 607,  2085, 2699, 3205,
+    1848, 2291, 3402, 2764, 3865, 3048, 2508, 735,  2710, 443,  2341, 897,  263,  1785, 2769, 983,  56,   2197, 1685, 2703, 202,  2944, 810,  3377, 2626, 3787, 3047, 2055, 1236, 2752, 2122, 945,  3093, 96,   1624, 439,  3014, 1388, 4015,
+    977,  448,  3506, 1098, 2242, 3026, 506,  2361, 2952, 1862, 3619, 2790, 1992, 2483, 525,  1868, 2652, 4093, 1998, 3595, 2478, 3816, 122,  1412, 929,  3716, 1166, 1648, 813,  1300, 199,  1489, 3998, 1771, 1310, 3808, 2052, 3423, 434,
+    3712, 1625, 3558, 2955, 853,  4019, 1348, 3511, 1732, 1246, 487,  934,  1672, 2510, 3965, 788,  3711, 396,  1369, 4090, 1055, 2603, 1879, 3528, 2518, 2067, 3005, 1516, 2588, 751,  1740, 3418, 1131, 1576, 686,  2296, 1118, 18,   3263,
+    1365, 3401, 294,  737,  3177, 410,  867,  1633, 2963, 3579, 2375, 252,  2881, 479,  2471, 3576, 2180, 3306, 332,  2255, 3035, 41,   2648, 1396, 2929, 2230, 1219, 2512, 446,  2008, 3189, 2388, 626,  2164, 2831, 4047, 2376, 174,  3272,
+    368,  1469, 3226, 2578, 1991, 2874, 2263, 3681, 876,  188,  1239, 683,  3776, 226,  3183, 4083, 2148, 63,   2649, 3859, 299,  3086, 3933, 1585, 2185, 3767, 988,  1707, 2908, 1407, 1844, 2771, 2245, 1161, 560,  1755, 3376, 2051, 4064,
+    3135, 1832, 652,  2853, 1051, 3649, 760,  3290, 1105, 3945, 872,  154,  3207, 713,  3780, 1453, 281,  1087, 3695, 30,   3299, 1919, 1400, 3551, 1119, 1890, 2314, 618,  1703, 3428, 724,  295,  3146, 1557, 3341, 2896, 1683, 2723, 1974,
+    1017, 541,  1380, 3720, 804,  3280, 2082, 997,  2567, 777,  2961, 213,  2707, 2328, 3632, 1025, 3891, 3304, 255,  4003, 3108, 2587, 1323, 743,  1479, 105,  1013, 3901, 1618, 2044, 2627, 1465, 1846, 576,  1994, 2560, 3521, 1742, 2118,
+    2800, 3404, 1783, 2609, 2968, 1582, 1022, 412,  2713, 687,  2976, 3857, 2761, 3620, 62,   1108, 3844, 1340, 2100, 540,  2345, 3925, 405,  3457, 1319, 2468, 3362, 2815, 1867, 2372, 1281, 1714, 3690, 482,  3498, 1842, 1285, 3994, 558,
+    2039, 81,   2499, 678,  1481, 1923, 964,  12,   3824, 2980, 2205, 2762, 3432, 2398, 181,  3247, 462,  4094, 2350, 3589, 3089, 1555, 1094, 4041, 247,  1267, 908,  3959, 2041, 732,  3860, 2343, 3132, 3769, 2144, 1621, 237,  912,  1329,
+    3025, 2146, 2642, 1775, 3721, 2746, 1121, 1953, 902,  2285, 130,  3671, 1659, 278,  3153, 522,  2721, 123,  2996, 1466, 2380, 377,  3231, 873,  1510, 3476, 3123, 1250, 2147, 3650, 2839, 3451, 2323, 1122, 3545, 379,  1765, 1218, 603,
+    3768, 1360, 938,  2885, 133,  1245, 363,  2364, 554,  2743, 3344, 2474, 530,  3112, 169,  1297, 3430, 536,  1741, 98,   1043, 2574, 3253, 2246, 1854, 4022, 510,  3283, 204,  858,  3398, 36,   3118, 1478, 3794, 2986, 706,  2176, 922,
+    3559, 1097, 3976, 3322, 2149, 1160, 2810, 3883, 2007, 2513, 2953, 328,  1721, 3793, 422,  2566, 807,  329,  1638, 1967, 648,  2520, 3727, 3109, 2116, 2927, 2491, 1939, 3365, 1709, 2728, 3815, 2037, 3120, 831,  1405, 1896, 3592, 1622,
+    2369, 2864, 2151, 1107, 2542, 3532, 1410, 3917, 427,  3568, 709,  2509, 1503, 1037, 2973, 2436, 1604, 4035, 2594, 563,  1819, 2659, 1234, 4004, 2565, 1511, 2273, 1823, 336,  882,  3772, 575,  1628, 171,  3570, 1120, 2260, 2716, 935,
+    3064, 1806, 1342, 3144, 3900, 2744, 3296, 985,  1546, 238,  896,  1663, 305,  3660, 695,  2213, 960,  3407, 144,  1795, 3894, 2267, 51,   2708, 1023, 3818, 366,  1821, 4087, 2985, 755,  2057, 2912, 949,  1583, 2774, 231,  3447, 2258,
+    3866, 1982, 672,  1225, 2077, 3320, 1062, 370,  3241, 1968, 7,    3068, 681,  3631, 2573, 1567, 3175, 2321, 1067, 3070, 722,  1856, 3744, 642,  1471, 4084, 131,  3514, 2443, 531,  1227, 155,  2265, 4024, 2658, 3326, 3910, 1168, 3078,
+    1530, 3956, 489,  1424, 3647, 1203, 420,  2924, 3755, 719,  3248, 1376, 3067, 890,  196,  1559, 3269, 270,  2432, 1885, 3212, 1164, 3778, 1752, 579,  1338, 344,  3585, 3017, 288,  3658, 2371, 3882, 1691, 611,  2789, 3809, 1339, 389,
+    2950, 2015, 59,   3548, 2751, 2158, 4011, 1352, 29,   3388, 2370, 2812, 1946, 954,  2110, 1558, 2947, 3573, 1909, 1326, 679,  1853, 2312, 551,  2702, 33,   2414, 3209, 2824, 2547, 2143, 3379, 966,  1492, 1979, 2479, 463,  2194, 3657,
+    2738, 2318, 1261, 3713, 604,  4002, 11,   2192, 2967, 919,  2607, 3369, 2837, 1676, 2539, 984,  1568, 93,   2901, 1318, 3538, 1041, 2216, 1756, 3454, 1030, 4050, 1402, 798,  1723, 311,  3277, 2546, 2886, 2043, 461,  1206, 3677, 361,
+    3260, 3988, 809,  2605, 470,  3007, 3517, 102,  3221, 1398, 2062, 3611, 1134, 1928, 865,  4060, 621,  1710, 2606, 3510, 317,  4017, 1682, 3329, 1159, 1940, 654,  3461, 1789, 1015, 2691, 1455, 3599, 374,  1947, 4069, 71,   2126, 763,
+    3961, 2278, 3161, 1997, 824,  2623, 2080, 244,  3257, 780,  2732, 2308, 545,  3351, 2476, 3806, 1204, 588,  1591, 963,  3610, 1699, 754,  3049, 2651, 1106, 65,   2221, 1644, 3821, 1100, 2463, 1614, 3801, 965,  2965, 715,  3394, 1593,
+    212,
+}};
+
+inline float dither_factor_blue_noise_64(int x, int y)
+{
+    float m = mask.at(((y & 0x3f) << 6) | (x & 0x3f));
+    return m * (1.f / 4096.f) + (1.f / 8192.f);
+}
+
+inline float dither_factor_bayer_8(int x, int y)
+{
+    y ^= x;
+
+    /* Compute reverse(interleave(xor(x mod n, y mod n), x mod n))
+     * Here n = 8 and `mod n` is the bottom 3 bits.
+     */
+    uint32_t m = ((y & 0x1) << 5) | ((x & 0x1) << 4) | ((y & 0x2) << 2) | ((x & 0x2) << 1) | ((y & 0x4) >> 1) | ((x & 0x4) >> 2);
+
+    /* m is in range [0, 63].  We scale it to [0, 63.0f/64.0f], then
+     * shift it to to [1.0f/128.0f, 127.0f/128.0f] so that 0 < d < 1.
+     * This ensures exact values are not changed by dithering.
+     */
+    return static_cast<float>(m) * (1.f / 64.f) + (1.f / 128.f);
+}
+
+inline float apply_dither(float f, float d, float s)
+{
+    /* float_to_unorm splits the [0, 1] segment in (1 << n_bits)
+     * subsections of equal length; however unorm_to_float does not
+     * map to the center of those sections.  In fact, pixel value u is
+     * mapped to:
+     *
+     *       u              u              u               1
+     * -------------- = ---------- + -------------- * ----------
+     *  2^n_bits - 1     2^n_bits     2^n_bits - 1     2^n_bits
+     *
+     * Hence if f = u / (2^n_bits - 1) is exactly representable on a
+     * n_bits palette, all the numbers between
+     *
+     *     u
+     * ----------  =  f - f * 2^n_bits = f + (0 - f) * 2^n_bits
+     *  2^n_bits
+     *
+     *  and
+     *
+     *    u + 1
+     * ---------- = f - (f - 1) * 2^n_bits = f + (1 - f) * 2^n_bits
+     *  2^n_bits
+     *
+     * are also mapped back to u.
+     *
+     * Hence the following calculation ensures that we add as much
+     * noise as possible without perturbing values which are exactly
+     * representable in the target colorspace.  Note that this corresponds
+     * to mixing the original color with noise with a ratio of `1 /
+     * 2^n_bits`.
+     */
+    return f + (d - f) * s;
+}
+} // namespace KisDitherMaths
diff --git a/libs/pigment/KisDitherOp.h b/libs/pigment/KisDitherOp.h
new file mode 100644
index 0000000000..48e173f00f
--- /dev/null
+++ b/libs/pigment/KisDitherOp.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include "kritapigment_export.h"
+
+#include <array>
+#include <cmath>
+
+#include <QScopedPointer>
+#include <QtGlobal>
+
+class KoColorSpace;
+class KoID;
+
+enum DitherType {
+    DITHER_NONE = 0,
+    DITHER_FAST = 1,
+    DITHER_BEST = 2,
+
+    DITHER_BAYER,
+    DITHER_BLUE_NOISE,
+};
+
+class KRITAPIGMENT_EXPORT KisDitherOp
+{
+public:
+    virtual ~KisDitherOp() = default;
+    virtual void dither(const quint8 *src, quint8 *dst, int x, int y) const = 0;
+    virtual void dither(const quint8 *srcRowStart, int srcRowStride, quint8 *dstRowStart, int dstRowStride, int x, int y, int columns, int rows) const = 0;
+
+    /**
+     * @return the identifier of this op's source depth
+     */
+    virtual KoID sourceDepthId() const = 0;
+
+    /**
+     * @return the identifier of this op's destination depth
+     */
+    virtual KoID destinationDepthId() const = 0;
+
+    /**
+     * @return the identifier of this op's type
+     */
+    virtual DitherType type() const = 0;
+};
diff --git a/libs/pigment/KisDitherOpImpl.h b/libs/pigment/KisDitherOpImpl.h
new file mode 100644
index 0000000000..053e7b6871
--- /dev/null
+++ b/libs/pigment/KisDitherOpImpl.h
@@ -0,0 +1,194 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <type_traits>
+
+#include "DebugPigment.h"
+#include "KoConfig.h"
+
+#ifdef HAVE_OPENEXR
+#include "half.h"
+#endif
+
+#include <KoColorModelStandardIds.h>
+#include <KoColorSpace.h>
+#include <KoColorSpaceMaths.h>
+#include <KoColorSpaceTraits.h>
+
+#include "KisDitherOp.h"
+#include "KisDitherMaths.h"
+
+template<typename srcCSTraits, typename dstCSTraits, DitherType dType> class KisDitherOpImpl : public KisDitherOp
+{
+    using srcChannelsType = typename srcCSTraits::channels_type;
+    using dstChannelsType = typename dstCSTraits::channels_type;
+
+public:
+    KisDitherOpImpl(const KoID &srcId, const KoID &dstId)
+        : m_srcDepthId(srcId)
+        , m_dstDepthId(dstId)
+    {
+    }
+
+    void dither(const quint8 *src, quint8 *dst, int x, int y) const override
+    {
+        ditherImpl(src, dst, x, y);
+    }
+
+    void dither(const quint8 *srcRowStart, int srcRowStride, quint8 *dstRowStart, int dstRowStride, int x, int y, int columns, int rows) const override
+    {
+        ditherImpl(srcRowStart, srcRowStride, dstRowStart, dstRowStride, x, y, columns, rows);
+    }
+
+    KoID sourceDepthId() const override
+    {
+        return m_srcDepthId;
+    }
+
+    KoID destinationDepthId() const override
+    {
+        return m_dstDepthId;
+    }
+
+    DitherType type() const override
+    {
+        return dType;
+    }
+
+private:
+    const KoID m_srcDepthId, m_dstDepthId;
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_NONE && std::is_same<srcCSTraits, dstCSTraits>::value, void>::type * = nullptr> inline void ditherImpl(const quint8 *src, quint8 *dst, int, int) const
+    {
+        memcpy(dst, src, srcCSTraits::pixelSize);
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_NONE && !std::is_same<srcCSTraits, dstCSTraits>::value, void>::type * = nullptr> inline void ditherImpl(const quint8 *src, quint8 *dst, int, int) const
+    {
+        const srcChannelsType *nativeSrc = srcCSTraits::nativeArray(src);
+        dstChannelsType *nativeDst = dstCSTraits::nativeArray(dst);
+
+        for (uint channelIndex = 0; channelIndex < srcCSTraits::channels_nb; ++channelIndex) {
+            nativeDst[channelIndex] = KoColorSpaceMaths<srcChannelsType, dstChannelsType>::scaleToA(nativeSrc[channelIndex]);
+        }
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t != DITHER_NONE, void>::type * = nullptr>
+    inline void ditherImpl(const quint8 *src, quint8 *dst, int x, int y) const
+    {
+        const srcChannelsType *nativeSrc = srcCSTraits::nativeArray(src);
+        dstChannelsType *nativeDst = dstCSTraits::nativeArray(dst);
+
+        float f = factor(x, y);
+        float s = scale();
+
+        for (uint channelIndex = 0; channelIndex < srcCSTraits::channels_nb; ++channelIndex) {
+            float c = KoColorSpaceMaths<srcChannelsType, float>::scaleToA(nativeSrc[channelIndex]);
+            c = KisDitherMaths::apply_dither(c, f, s);
+            nativeDst[channelIndex] = KoColorSpaceMaths<float, dstChannelsType>::scaleToA(c);
+        }
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_NONE && std::is_same<srcCSTraits, dstCSTraits>::value, void>::type * = nullptr>
+    inline void ditherImpl(const quint8 *srcRowStart, int srcRowStride, quint8 *dstRowStart, int dstRowStride, int, int, int columns, int rows) const
+    {
+        const quint8 *nativeSrc = srcRowStart;
+        quint8 *nativeDst = dstRowStart;
+
+        for (int y = 0; y < rows; ++y) {
+            memcpy(nativeDst, nativeSrc, columns);
+
+            nativeSrc += srcRowStride;
+            nativeDst += dstRowStride;
+        }
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_NONE && !std::is_same<srcCSTraits, dstCSTraits>::value, void>::type * = nullptr>
+    inline void ditherImpl(const quint8 *srcRowStart, int srcRowStride, quint8 *dstRowStart, int dstRowStride, int, int, int columns, int rows) const
+    {
+        const quint8 *nativeSrc = srcRowStart;
+        quint8 *nativeDst = dstRowStart;
+
+        for (int y = 0; y < rows; ++y) {
+            const srcChannelsType *srcPtr = srcCSTraits::nativeArray(nativeSrc);
+            dstChannelsType *dstPtr = dstCSTraits::nativeArray(nativeDst);
+
+            for (int x = 0; x < columns; ++x) {
+                for (uint channelIndex = 0; channelIndex < srcCSTraits::channels_nb; ++channelIndex) {
+                    dstPtr[channelIndex] = KoColorSpaceMaths<srcChannelsType, dstChannelsType>::scaleToA(srcPtr[channelIndex]);
+                }
+
+                srcPtr += srcCSTraits::channels_nb;
+                dstPtr += dstCSTraits::channels_nb;
+            }
+
+            nativeSrc += srcRowStride;
+            nativeDst += dstRowStride;
+        }
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t != DITHER_NONE, void>::type * = nullptr>
+    inline void ditherImpl(const quint8 *srcRowStart, int srcRowStride, quint8 *dstRowStart, int dstRowStride, int x, int y, int columns, int rows) const
+    {
+        const quint8 *nativeSrc = srcRowStart;
+        quint8 *nativeDst = dstRowStart;
+
+        float s = scale();
+
+        for (int a = 0; a < rows; ++a) {
+            const srcChannelsType *srcPtr = srcCSTraits::nativeArray(nativeSrc);
+            dstChannelsType *dstPtr = dstCSTraits::nativeArray(nativeDst);
+
+            for (int b = 0; b < columns; ++b) {
+                float f = factor(x + b, y + a);
+
+                for (uint channelIndex = 0; channelIndex < srcCSTraits::channels_nb; ++channelIndex) {
+                    float c = KoColorSpaceMaths<srcChannelsType, float>::scaleToA(srcPtr[channelIndex]);
+                    c = KisDitherMaths::apply_dither(c, f, s);
+                    dstPtr[channelIndex] = KoColorSpaceMaths<float, dstChannelsType>::scaleToA(c);
+                }
+
+                srcPtr += srcCSTraits::channels_nb;
+                dstPtr += dstCSTraits::channels_nb;
+            }
+
+            nativeSrc += srcRowStride;
+            nativeDst += dstRowStride;
+        }
+    }
+
+    template<typename U = typename dstCSTraits::channels_type, typename std::enable_if<!std::numeric_limits<U>::is_integer, void>::type * = nullptr> constexpr float scale() const
+    {
+        return 0.f; // no dithering for floating point
+    }
+
+    template<typename U = typename dstCSTraits::channels_type, typename std::enable_if<std::numeric_limits<U>::is_integer, void>::type * = nullptr> constexpr float scale() const
+    {
+        return 1.f / static_cast<float>(1 << dstCSTraits::depth);
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_BAYER, void>::type * = nullptr> inline float factor(int x, int y) const
+    {
+        return KisDitherMaths::dither_factor_bayer_8(x, y);
+    }
+
+    template<DitherType t = dType, typename std::enable_if<t == DITHER_BLUE_NOISE, void>::type * = nullptr> inline float factor(int x, int y) const
+    {
+        return KisDitherMaths::dither_factor_blue_noise_64(x, y);
+    }
+};
+
+template<typename srcCSTraits, class dstCSTraits> inline void addDitherOpsByDepth(KoColorSpace *cs, const KoID &dstDepth)
+{
+    const KoID &srcDepth {cs->colorDepthId()};
+    cs->addDitherOp(new KisDitherOpImpl<srcCSTraits, dstCSTraits, DITHER_NONE>(srcDepth, dstDepth));
+    cs->addDitherOp(new KisDitherOpImpl<srcCSTraits, dstCSTraits, DITHER_BAYER>(srcDepth, dstDepth));
+    cs->addDitherOp(new KisDitherOpImpl<srcCSTraits, dstCSTraits, DITHER_BLUE_NOISE>(srcDepth, dstDepth));
+}
diff --git a/libs/pigment/KoColorSpace.cpp b/libs/pigment/KoColorSpace.cpp
index 4b4dd701d6..17a13a232d 100644
--- a/libs/pigment/KoColorSpace.cpp
+++ b/libs/pigment/KoColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud at valdyas.org>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "KoColorSpace.h"
 #include "KoColorSpace_p.h"
@@ -26,6 +27,9 @@
 #include "KoColorSpaceEngine.h"
 #include <KoColorSpaceTraits.h>
 #include <KoColorSpacePreserveLightnessUtils.h>
+#include "KisDitherOp.h"
+
+#include <cmath>
 
 #include <QThreadStorage>
 #include <QByteArray>
@@ -33,14 +37,13 @@
 #include <QPolygonF>
 #include <QPointF>
 
-#include <math.h>
 
 KoColorSpace::KoColorSpace()
     : d(new Private())
 {
 }
 
-KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp)
+KoColorSpace::KoColorSpace(const QString &id, const QString &name, KoMixColorsOp *mixColorsOp, KoConvolutionOp *convolutionOp)
     : d(new Private())
 {
     d->id = id;
@@ -65,6 +68,9 @@ KoColorSpace::~KoColorSpace()
     Q_ASSERT(d->deletability != OwnedByRegistryDoNotDelete);
 
     qDeleteAll(d->compositeOps);
+    for (const auto& map: d->ditherOps) {
+        qDeleteAll(map);
+    }
     Q_FOREACH (KoChannelInfo * channel, d->channels) {
         delete channel;
     }
@@ -317,6 +323,37 @@ KoMixColorsOp* KoColorSpace::mixColorsOp() const
     return d->mixColorsOp;
 }
 
+const KisDitherOp *KoColorSpace::ditherOp(const QString &depth, DitherType type) const
+{
+    const auto it = d->ditherOps.constFind(depth);
+    if (it != d->ditherOps.constEnd()) {
+        switch (type) {
+        case DITHER_FAST:
+        case DITHER_BAYER:
+            return it->constFind(DITHER_BAYER).value();
+        case DITHER_BEST:
+        case DITHER_BLUE_NOISE:
+            return it->constFind(DITHER_BLUE_NOISE).value();
+        case DITHER_NONE:
+        default:
+            return it->constFind(DITHER_NONE).value();
+        }
+    } else {
+        warnPigment << "Asking for dither op from " << colorDepthId() << "to an unsupported depth" << depth << "!";
+        return nullptr;
+    }
+}
+
+void KoColorSpace::addDitherOp(KisDitherOp *op)
+{
+    if (op->sourceDepthId() == colorDepthId()) {
+        if (!d->ditherOps.contains(op->destinationDepthId().id())) {
+            d->ditherOps.insert(op->destinationDepthId().id(), {{op->type(), op}});
+        } else {
+            d->ditherOps[op->destinationDepthId().id()].insert(op->type(), op);
+        }
+    }
+}
 
 KoConvolutionOp* KoColorSpace::convolutionOp() const
 {
diff --git a/libs/pigment/KoColorSpace.h b/libs/pigment/KoColorSpace.h
index 4b39e0c720..d7339a82f6 100644
--- a/libs/pigment/KoColorSpace.h
+++ b/libs/pigment/KoColorSpace.h
@@ -1,13 +1,14 @@
 /*
  *  SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2006-2007 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
  */
 #ifndef KOCOLORSPACE_H
 #define KOCOLORSPACE_H
 
-#include <limits.h>
+#include <climits>
 
 #include <QImage>
 #include <QHash>
@@ -19,6 +20,7 @@
 #include "KoColorConversionTransformation.h"
 #include "KoColorProofingConversionTransformation.h"
 #include "KoCompositeOp.h"
+#include "KisDitherOp.h"
 #include <KoID.h>
 #include "kritapigment_export.h"
 
@@ -83,7 +85,7 @@ protected:
 public:
 
     /// Should be called by real color spaces
-    KoColorSpace(const QString &id, const QString &name, KoMixColorsOp* mixColorsOp, KoConvolutionOp* convolutionOp);
+    KoColorSpace(const QString &id, const QString &name, KoMixColorsOp *mixColorsOp, KoConvolutionOp *convolutionOp);
 
     virtual bool operator==(const KoColorSpace& rhs) const;
 protected:
@@ -255,7 +257,6 @@ public:
      */
     virtual bool hasHighDynamicRange() const = 0;
 
-
 //========== Display profiles =============================================//
 
     /**
@@ -349,6 +350,13 @@ public:
                                                                   KoColorConversionTransformation::Intent renderingIntent,
                                                                   KoColorConversionTransformation::ConversionFlags conversionFlags) const;
 
+    /**
+     * Retrieve the elevate-to-normalized floating point dithering op.
+     */
+    virtual const KisDitherOp *ditherOp(const QString &depth, DitherType type) const;
+
+    virtual void addDitherOp(KisDitherOp *op);
+
     /**
      * Convert a byte array of srcLen pixels *src to the specified color space
      * and put the converted bytes into the prepared byte array *dst.
diff --git a/libs/pigment/KoColorSpaceAbstract.h b/libs/pigment/KoColorSpaceAbstract.h
index b86d6d4c0c..b237329995 100644
--- a/libs/pigment/KoColorSpaceAbstract.h
+++ b/libs/pigment/KoColorSpaceAbstract.h
@@ -1,9 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
  *  SPDX-FileCopyrightText: 2007 Emanuele Tamponi <emanuele at valinor.it>
- *
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #ifndef KOCOLORSPACEABSTRACT_H
 #define KOCOLORSPACEABSTRACT_H
diff --git a/libs/pigment/KoColorSpace_p.h b/libs/pigment/KoColorSpace_p.h
index 1fb2a39d21..cf5ed6c2fa 100644
--- a/libs/pigment/KoColorSpace_p.h
+++ b/libs/pigment/KoColorSpace_p.h
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2005 Boudewijn Rempt <boud at valdyas.org>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me> *
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #ifndef _KOCOLORSPACE_P_H_
 #define _KOCOLORSPACE_P_H_
@@ -10,6 +11,7 @@
 #include "KoColorSpace.h"
 #include "KoColorSpaceEngine.h"
 #include "KoColorConversionTransformation.h"
+#include <QPair>
 #include <QThreadStorage>
 #include <QPolygonF>
 
@@ -22,6 +24,8 @@ struct Q_DECL_HIDDEN KoColorSpace::Private {
     QList<KoChannelInfo *> channels;
     KoMixColorsOp* mixColorsOp;
     KoConvolutionOp* convolutionOp;
+    QHash<QString, QMap<DitherType, KisDitherOp*>> ditherOps;
+
     QThreadStorage< QVector<quint8>* > conversionCache;
 
     mutable KoColorConversionTransformation* transfoToRGBA16;
diff --git a/libs/pigment/colorspaces/KoLabColorSpace.cpp b/libs/pigment/colorspaces/KoLabColorSpace.cpp
index d77268cdfd..ccf1358874 100644
--- a/libs/pigment/colorspaces/KoLabColorSpace.cpp
+++ b/libs/pigment/colorspaces/KoLabColorSpace.cpp
@@ -1,6 +1,7 @@
 /*
  *  SPDX-FileCopyrightText: 2004-2009 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
@@ -21,6 +22,7 @@
 #include "KoColorConversions.h"
 
 #include "../compositeops/KoCompositeOps.h"
+#include "dithering/KisLabDitherOpFactory.h"
 
 KoLabColorSpace::KoLabColorSpace() :
         KoSimpleColorSpace<KoLabU16Traits>(colorSpaceId(),
@@ -35,7 +37,7 @@ KoLabColorSpace::KoLabColorSpace() :
 
     // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT
     addStandardCompositeOps<KoLabU16Traits>(this);
-
+    addStandardDitherOps<KoLabU16Traits>(this);
 }
 
 KoLabColorSpace::~KoLabColorSpace()
diff --git a/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp b/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp
index 2228dfe0e9..2d18b46459 100644
--- a/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp
+++ b/libs/pigment/colorspaces/KoRgbU16ColorSpace.cpp
@@ -1,6 +1,7 @@
 /*
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
@@ -20,6 +21,7 @@
 #include "KoIntegerMaths.h"
 
 #include "KoColorConversions.h"
+#include "dithering/KisRgbDitherOpFactory.h"
 #include <KoColorSpacePreserveLightnessUtils.h>
 
 KoRgbU16ColorSpace::KoRgbU16ColorSpace() :
@@ -28,6 +30,7 @@ KoRgbU16ColorSpace::KoRgbU16ColorSpace() :
                                            RGBAColorModelID,
                                            Integer16BitsColorDepthID)
 {
+    addStandardDitherOps<KoBgrU16Traits>(this);
 }
 
 KoRgbU16ColorSpace::~KoRgbU16ColorSpace()
@@ -94,4 +97,4 @@ void KoRgbU16ColorSpace::fillGrayBrushWithColorAndLightnessOverlay(quint8 *dst,
 void KoRgbU16ColorSpace::fillGrayBrushWithColorAndLightnessWithStrength(quint8* dst, const QRgb* brush, quint8* brushColor, qreal strength, qint32 nPixels) const
 {
     fillGrayBrushWithColorPreserveLightnessRGB<KoBgrU16Traits>(dst, brush, brushColor, strength, nPixels);
-}
\ No newline at end of file
+}
diff --git a/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp b/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp
index 5f35118769..c0e0376348 100644
--- a/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp
+++ b/libs/pigment/colorspaces/KoRgbU8ColorSpace.cpp
@@ -1,6 +1,7 @@
 /*
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
@@ -18,11 +19,11 @@
 #include "KoID.h"
 #include "KoIntegerMaths.h"
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisRgbDitherOpFactory.h"
 
 #include "KoColorConversions.h"
 #include <KoColorSpacePreserveLightnessUtils.h>
 
-
 KoRgbU8ColorSpace::KoRgbU8ColorSpace() :
 
         KoSimpleColorSpace<KoBgrU8Traits>(colorSpaceId(),
@@ -38,7 +39,7 @@ KoRgbU8ColorSpace::KoRgbU8ColorSpace() :
 
     // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT
     addStandardCompositeOps<KoBgrU8Traits>(this);
-
+    addStandardDitherOps<KoBgrU8Traits>(this);
 }
 
 KoRgbU8ColorSpace::~KoRgbU8ColorSpace()
diff --git a/libs/pigment/dithering/KisCmykDitherOpFactory.h b/libs/pigment/dithering/KisCmykDitherOpFactory.h
new file mode 100644
index 0000000000..e564d27260
--- /dev/null
+++ b/libs/pigment/dithering/KisCmykDitherOpFactory.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoCmykU8Traits>::value || std::is_same<srcCSTraits, KoCmykU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoCmykF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoCmykF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoCmykU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoCmykU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoCmykF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoCmykF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/dithering/KisGrayDitherOpFactory.h b/libs/pigment/dithering/KisGrayDitherOpFactory.h
new file mode 100644
index 0000000000..419b504c7e
--- /dev/null
+++ b/libs/pigment/dithering/KisGrayDitherOpFactory.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoGrayU8Traits>::value || std::is_same<srcCSTraits, KoGrayU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoGrayF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoGrayF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoGrayU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoGrayU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoGrayF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoGrayF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/dithering/KisLabDitherOpFactory.h b/libs/pigment/dithering/KisLabDitherOpFactory.h
new file mode 100644
index 0000000000..a35eb8f2a0
--- /dev/null
+++ b/libs/pigment/dithering/KisLabDitherOpFactory.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoLabU8Traits>::value || std::is_same<srcCSTraits, KoLabU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoLabF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoLabF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoLabU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoLabU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoLabF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoLabF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/dithering/KisRgbDitherOpFactory.h b/libs/pigment/dithering/KisRgbDitherOpFactory.h
new file mode 100644
index 0000000000..c9b11d6391
--- /dev/null
+++ b/libs/pigment/dithering/KisRgbDitherOpFactory.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoBgrU8Traits>::value || std::is_same<srcCSTraits, KoBgrU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoRgbF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoRgbF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoBgrU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoBgrU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoRgbF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoRgbF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/dithering/KisXyzDitherOpFactory.h b/libs/pigment/dithering/KisXyzDitherOpFactory.h
new file mode 100644
index 0000000000..a2d0a484b1
--- /dev/null
+++ b/libs/pigment/dithering/KisXyzDitherOpFactory.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoXyzU8Traits>::value || std::is_same<srcCSTraits, KoXyzU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoXyzF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoXyzF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoXyzU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoXyzU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoXyzF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoXyzF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/dithering/KisYCbCrDitherOpFactory.h b/libs/pigment/dithering/KisYCbCrDitherOpFactory.h
new file mode 100644
index 0000000000..79569cde97
--- /dev/null
+++ b/libs/pigment/dithering/KisYCbCrDitherOpFactory.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Krita
+ *
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "KisDitherOpImpl.h"
+
+template<class srcCSTraits> inline void addStandardDitherOps(KoColorSpace *cs)
+{
+    static_assert(std::is_same<srcCSTraits, KoYCbCrU8Traits>::value || std::is_same<srcCSTraits, KoYCbCrU16Traits>::value ||
+#ifdef HAVE_OPENEXR
+                      std::is_same<srcCSTraits, KoYCbCrF16Traits>::value ||
+#endif
+                      std::is_same<srcCSTraits, KoYCbCrF32Traits>::value,
+                  "Missing colorspace, add a transform case!");
+
+    addDitherOpsByDepth<srcCSTraits, KoYCbCrU8Traits>(cs, Integer8BitsColorDepthID);
+    addDitherOpsByDepth<srcCSTraits, KoYCbCrU16Traits>(cs, Integer16BitsColorDepthID);
+#ifdef HAVE_OPENEXR
+    addDitherOpsByDepth<srcCSTraits, KoYCbCrF16Traits>(cs, Float16BitsColorDepthID);
+#endif
+    addDitherOpsByDepth<srcCSTraits, KoYCbCrF32Traits>(cs, Float32BitsColorDepthID);
+}
diff --git a/libs/pigment/resources/KoSegmentGradient.cpp b/libs/pigment/resources/KoSegmentGradient.cpp
index 028eba8c05..cf9eaea76c 100644
--- a/libs/pigment/resources/KoSegmentGradient.cpp
+++ b/libs/pigment/resources/KoSegmentGradient.cpp
@@ -4,12 +4,14 @@
     SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud at valdyas.org>
     SPDX-FileCopyrightText: 2004 Adrian Page <adrian at pagenet.plus.com>
     SPDX-FileCopyrightText: 2004, 2007 Sven Langkamp <sven.langkamp at gmail.com>
+    SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
 
     SPDX-License-Identifier: LGPL-2.1-or-later
  */
 
 #include <resources/KoSegmentGradient.h>
 
+#include <array>
 #include <cfloat>
 #include <cmath>
 
@@ -21,18 +23,18 @@
 #include <QDomElement>
 #include <QBuffer>
 
+#include <DebugPigment.h>
+#include <KoCanvasResourcesIds.h>
+#include <KoCanvasResourcesInterface.h>
+#include <KoColorModelStandardIds.h>
 #include <kis_dom_utils.h>
+#include <kis_global.h>
+#include <klocalizedstring.h>
 
-#include "KoColorSpaceRegistry.h"
+#include "KoColor.h"
 #include "KoColorSpace.h"
+#include "KoColorSpaceRegistry.h"
 #include "KoMixColorsOp.h"
-#include <KoColorModelStandardIds.h>
-
-#include <DebugPigment.h>
-#include <klocalizedstring.h>
-
-#include <KoCanvasResourcesIds.h>
-#include <KoCanvasResourcesInterface.h>
 
 KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInterpolationStrategy::m_instance = 0;
 KoGradientSegment::HSVCWColorInterpolationStrategy *KoGradientSegment::HSVCWColorInterpolationStrategy::m_instance = 0;
@@ -112,7 +114,7 @@ bool KoSegmentGradient::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP r
 
     dbgPigment << "Number of segments = " << numSegments;
 
-    const KoColorSpace* rgbColorSpace = KoColorSpaceRegistry::instance()->rgb8();
+    const KoColorSpace *rgbColorSpace = KoColorSpaceRegistry::instance()->rgb16(KoColorSpaceRegistry::instance()->p709SRGBProfile());
 
     for (int i = 0; i < numSegments; i++) {
 
@@ -145,20 +147,20 @@ bool KoSegmentGradient::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP r
         else {
             startType = endType = COLOR_ENDPOINT;
         }
-        quint8 data[4];
-        data[2] = static_cast<quint8>(leftRed * 255 + 0.5);
-        data[1] = static_cast<quint8>(leftGreen * 255 + 0.5);
-        data[0] = static_cast<quint8>(leftBlue * 255 + 0.5);
-        data[3] = static_cast<quint8>(leftAlpha * OPACITY_OPAQUE_U8 + 0.5);
+        std::array<quint16, 4> data;
+        data[2] = static_cast<quint16>(leftRed * quint16_MAX + 0.5);
+        data[1] = static_cast<quint16>(leftGreen * quint16_MAX + 0.5);
+        data[0] = static_cast<quint16>(leftBlue * quint16_MAX + 0.5);
+        data[3] = static_cast<quint16>(leftAlpha * quint16_MAX + 0.5);
 
-        KoColor leftColor(data, rgbColorSpace);
+        KoColor leftColor(reinterpret_cast<quint8 *>(data.data()), rgbColorSpace);
 
-        data[2] = static_cast<quint8>(rightRed * 255 + 0.5);
-        data[1] = static_cast<quint8>(rightGreen * 255 + 0.5);
-        data[0] = static_cast<quint8>(rightBlue * 255 + 0.5);
-        data[3] = static_cast<quint8>(rightAlpha * OPACITY_OPAQUE_U8 + 0.5);
+        data[2] = static_cast<quint16>(rightRed * quint16_MAX + 0.5);
+        data[1] = static_cast<quint16>(rightGreen * quint16_MAX + 0.5);
+        data[0] = static_cast<quint16>(rightBlue * quint16_MAX + 0.5);
+        data[3] = static_cast<quint16>(rightAlpha * quint16_MAX + 0.5);
 
-        KoColor rightColor(data, rgbColorSpace);
+        KoColor rightColor(reinterpret_cast<quint8 *>(data.data()), rgbColorSpace);
         KoGradientSegmentEndpoint left(leftOffset, leftColor, startType);
         KoGradientSegmentEndpoint right(rightOffset, rightColor, endType);
 
@@ -609,7 +611,7 @@ bool KoGradientSegment::isValid() const
 }
 
 KoGradientSegment::RGBColorInterpolationStrategy::RGBColorInterpolationStrategy()
-    : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
+    : m_colorSpace(KoColorSpaceRegistry::instance()->rgb16(KoColorSpaceRegistry::instance()->p709SRGBProfile()))
 {
 }
 
@@ -625,51 +627,22 @@ KoGradientSegment::RGBColorInterpolationStrategy *KoGradientSegment::RGBColorInt
 
 void KoGradientSegment::RGBColorInterpolationStrategy::colorAt(KoColor& dst, qreal t, const KoColor& _start, const KoColor& _end) const
 {
+    const KoColorSpace *mixSpace = dst.colorSpace();
 
-    KoColor buffer(m_colorSpace);
-    KoColor start(m_colorSpace);
-    KoColor end(m_colorSpace);
-
-    KoColor startDummy, endDummy;
-    //hack to get a color space with the bitdepth of the gradients(8bit), but with the colour profile of the image//
-    const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile());
-    //convert to the right colorspace for the start and end if we have our mixSpace.
-    if (mixSpace){
-        startDummy = KoColor(_start, mixSpace);
-        endDummy = KoColor(_end, mixSpace);
-    } else {
-        startDummy = _start;
-        endDummy = _end;
-    }
-
-    start.fromKoColor(_start);
-    end.fromKoColor(_end);
+    KoColor startDummy(_start, mixSpace);
+    KoColor endDummy(_end, mixSpace);
 
-    const quint8 *colors[2];
-    colors[0] = startDummy.data();
-    colors[1] = endDummy.data();
+    const std::array<quint8*, 2> colors = {{startDummy.data(), endDummy.data()}};
 
-    qint16 colorWeights[2];
-    colorWeights[0] = static_cast<quint8>((1.0 - t) * 255 + 0.5);
-    colorWeights[1] = 255 - colorWeights[0];
+    std::array<qint16, 2> colorWeights{};
+    colorWeights[0] = std::lround((1.0 - t) * qint16_MAX);
+    colorWeights[1] = qint16_MAX - colorWeights[0];
 
-    //check if our mixspace exists, it doesn't at startup.
-    if (mixSpace){
-        if (*buffer.colorSpace() != *mixSpace) {
-            buffer = KoColor(mixSpace);
-        }
-        mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
-    }
-    else {
-        buffer = KoColor(m_colorSpace);
-        m_colorSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
-    }
-
-    dst.fromKoColor(buffer);
+    mixSpace->mixColorsOp()->mixColors(colors.data(), colorWeights.data(), 2, dst.data(), qint16_MAX);
 }
 
 KoGradientSegment::HSVCWColorInterpolationStrategy::HSVCWColorInterpolationStrategy()
-    : m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
+    : m_colorSpace(KoColorSpaceRegistry::instance()->rgb16(KoColorSpaceRegistry::instance()->p709SRGBProfile()))
 {
 }
 
@@ -704,17 +677,17 @@ void KoGradientSegment::HSVCWColorInterpolationStrategy::colorAt(KoColor& dst, q
             h -= 360;
         }
     }
-    // XXX: added an explicit cast. Is this correct?
-    quint8 opacity = static_cast<quint8>(sc.alpha() + t * (ec.alpha() - sc.alpha()));
+
+    qreal opacity{sc.alphaF() + t * (ec.alphaF() - sc.alphaF())};
 
     QColor result;
     result.setHsv(h, s, v);
-    result.setAlpha(opacity);
+    result.setAlphaF(opacity);
     dst.fromQColor(result);
 }
 
-KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy() :
-    m_colorSpace(KoColorSpaceRegistry::instance()->rgb8())
+KoGradientSegment::HSVCCWColorInterpolationStrategy::HSVCCWColorInterpolationStrategy()
+    : m_colorSpace(KoColorSpaceRegistry::instance()->rgb16(KoColorSpaceRegistry::instance()->p709SRGBProfile()))
 {
 }
 
@@ -750,12 +723,12 @@ void KoGradientSegment::HSVCCWColorInterpolationStrategy::colorAt(KoColor& dst,
             h -= 360;
         }
     }
-    // XXX: Added an explicit static cast
-    quint8 opacity = static_cast<quint8>(sc.alpha() + t * (se.alpha() - sc.alpha()));
+
+    qreal opacity = sc.alphaF() + t * (se.alphaF() - sc.alphaF());
 
     QColor result;
     result.setHsv(h, s, v);
-    result.setAlpha(opacity);
+    result.setAlphaF(opacity);
     dst.fromQColor(result);
 }
 
diff --git a/libs/pigment/resources/KoStopGradient.cpp b/libs/pigment/resources/KoStopGradient.cpp
index b30c60aa9a..ef314f38c2 100644
--- a/libs/pigment/resources/KoStopGradient.cpp
+++ b/libs/pigment/resources/KoStopGradient.cpp
@@ -2,13 +2,16 @@
     SPDX-FileCopyrightText: 2005 Tim Beaulen <tbscope at gmail.org>
     SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham at gmx.net>
     SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp at gmail.com>
+    SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
 
     SPDX-License-Identifier: LGPL-2.1-or-later
  */
 
 #include <resources/KoStopGradient.h>
 
+#include <array>
 #include <cfloat>
+#include <cmath>
 
 #include <QColor>
 #include <QFile>
@@ -26,7 +29,6 @@
 
 #include "kis_dom_utils.h"
 
-#include <math.h>
 #include <KoColorModelStandardIds.h>
 #include <KoXmlNS.h>
 
@@ -154,48 +156,31 @@ bool KoStopGradient::stopsAt(KoGradientStop& leftStop, KoGradientStop& rightStop
 
 void KoStopGradient::colorAt(KoColor& dst, qreal t) const
 {
-    KoColor buffer;
-
     KoGradientStop leftStop, rightStop;
     if (!stopsAt(leftStop, rightStop, t)) return;
 
-    const KoColorSpace* mixSpace = KoColorSpaceRegistry::instance()->rgb8(dst.colorSpace()->profile());
+    const KoColorSpace *mixSpace = dst.colorSpace();
 
-    KoColor startDummy, endDummy;
-    if (mixSpace) {
-        startDummy = KoColor(leftStop.color, mixSpace);
-        endDummy = KoColor(rightStop.color, mixSpace);
-    } else {
-        startDummy = leftStop.color;
-        endDummy = rightStop.color;
-    }
-    const quint8* colors[2];
-    colors[0] = startDummy.data();
-    colors[1] = endDummy.data();
+    KoColor buffer(mixSpace);
+    KoColor startDummy(leftStop.color, mixSpace);
+    KoColor endDummy(rightStop.color, mixSpace);
+
+    const std::array<quint8 *, 2> colors = {{startDummy.data(), endDummy.data()}};
 
-    qreal localT;
+    qreal localT = NAN;
     qreal stopDistance = rightStop.position - leftStop.position;
     if (stopDistance < DBL_EPSILON) {
         localT = 0.5;
     } else {
         localT = (t - leftStop.position) / stopDistance;
     }
-    qint16 colorWeights[2];
-    colorWeights[0] = static_cast<quint8>((1.0 - localT) * 255 + 0.5);
-    colorWeights[1] = 255 - colorWeights[0];
-
-    //check if our mixspace exists, it doesn't at startup.
-    if (mixSpace) {
-        if (*buffer.colorSpace() != *mixSpace) {
-            buffer = KoColor(mixSpace);
-        }
-        mixSpace->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
-    } else {
-        buffer = KoColor(colorSpace());
-        colorSpace()->mixColorsOp()->mixColors(colors, colorWeights, 2, buffer.data());
-    }
+    std::array<qint16, 2> colorWeights {};
+    colorWeights[0] = std::lround((1.0 - localT) * qint16_MAX);
+    colorWeights[1] = qint16_MAX - colorWeights[0];
+
+    mixSpace->mixColorsOp()->mixColors(colors.data(), colorWeights.data(), 2, buffer.data(), qint16_MAX);
 
-    dst.fromKoColor(buffer);
+    dst = buffer;
 }
 
 QSharedPointer<KoStopGradient> KoStopGradient::fromQGradient(const QGradient *gradient)
@@ -537,7 +522,7 @@ void KoStopGradient::parseSvgGradient(const QDomElement& element, QHash<QString,
             if (!colorstop.attribute("stop-opacity").isEmpty())
                 opacity = colorstop.attribute("stop-opacity").toDouble();
 
-            color.setOpacity(static_cast<quint8>(opacity * OPACITY_OPAQUE_U8 + 0.5));
+            color.setOpacity(static_cast<quint8>(std::lround(opacity * OPACITY_OPAQUE_U8)));
             QString stopTypeStr = colorstop.attribute("krita:stop-type", "color-stop");
             KoGradientStopType stopType = KoGradientStop::typeFromString(stopTypeStr);
             if (stopType != COLORSTOP) {
diff --git a/libs/psd/psd.h b/libs/psd/psd.h
index fe6d6ce90a..9004a9cd37 100644
--- a/libs/psd/psd.h
+++ b/libs/psd/psd.h
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2010 Boudewijn Rempt <boud at valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -884,6 +885,7 @@ struct psd_layer_effects_overlay_base : public psd_layer_effects_shadow_base
     psd_layer_effects_overlay_base()
         : m_scale(100),
           m_alignWithLayer(true),
+          m_dither(false),
           m_reverse(false),
           m_style(psd_gradient_style_linear),
           m_gradientXOffset(0),
@@ -910,6 +912,10 @@ struct psd_layer_effects_overlay_base : public psd_layer_effects_shadow_base
         return m_alignWithLayer;
     }
 
+    bool dither() const {
+        return m_dither;
+    }
+
     bool reverse() const {
         return m_reverse;
     }
@@ -949,6 +955,10 @@ public:
         m_alignWithLayer = value;
     }
 
+    void setDither(bool value) {
+        m_dither = value;
+    }
+
     void setReverse(bool value) {
         m_reverse = value;
     }
@@ -991,6 +1001,7 @@ private:
     bool m_alignWithLayer;
 
     // Gradient
+    bool m_dither;
     bool m_reverse;
     psd_gradient_style m_style;
     int m_gradientXOffset; // 0..100%
diff --git a/libs/ui/dialogs/kis_dlg_layer_style.cpp b/libs/ui/dialogs/kis_dlg_layer_style.cpp
index e41ccf105c..569f03a31f 100644
--- a/libs/ui/dialogs/kis_dlg_layer_style.cpp
+++ b/libs/ui/dialogs/kis_dlg_layer_style.cpp
@@ -1107,6 +1107,7 @@ GradientOverlay::GradientOverlay(KisCanvasResourceProvider *resourceProvider, QW
     connect(ui.cmbStyle, SIGNAL(currentIndexChanged(int)), SIGNAL(configChanged()));
     connect(ui.chkAlignWithLayer, SIGNAL(toggled(bool)), SIGNAL(configChanged()));
     connect(ui.intScale, SIGNAL(valueChanged(int)), SIGNAL(configChanged()));
+    connect(ui.chkDither, SIGNAL(toggled(bool)), SIGNAL(configChanged()));
 }
 
 void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overlay *config)
@@ -1125,6 +1126,7 @@ void GradientOverlay::setGradientOverlay(const psd_layer_effects_gradient_overla
     ui.chkAlignWithLayer->setCheckable(config->alignWithLayer());
     ui.angleSelector->setValue(config->angle());
     ui.intScale->setValue(config->scale());
+    ui.chkDither->setChecked(config->dither());
 }
 
 void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *config) const
@@ -1137,6 +1139,7 @@ void GradientOverlay::fetchGradientOverlay(psd_layer_effects_gradient_overlay *c
     config->setAlignWithLayer(ui.chkAlignWithLayer->isChecked());
     config->setAngle(ui.angleSelector->value());
     config->setScale(ui.intScale->value());
+    config->setDither(ui.chkDither->isChecked());
 }
 
 
diff --git a/libs/ui/layerstyles/WdgGradientOverlay.ui b/libs/ui/layerstyles/WdgGradientOverlay.ui
index ab62ec5d04..ac5c11ee4c 100644
--- a/libs/ui/layerstyles/WdgGradientOverlay.ui
+++ b/libs/ui/layerstyles/WdgGradientOverlay.ui
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <ui version="4.0">
  <author>
     SPDX-FileCopyrightText: none
@@ -15,7 +15,7 @@
    </rect>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item column="0" row="0">
+   <item row="0" column="0">
     <widget class="QGroupBox" name="groupBox">
      <property name="title">
       <string>Gradient Overlay</string>
@@ -27,7 +27,7 @@
          <string>Gradient</string>
         </property>
         <layout class="QFormLayout" name="formLayout">
-         <item column="0" row="0">
+         <item row="0" column="0">
           <widget class="QLabel" name="label_13">
            <property name="text">
             <string>Ble&nd Mode:</string>
@@ -37,7 +37,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="0">
+         <item row="0" column="1">
           <widget class="KisLayerStyleCompositeOpComboBox" name="cmbCompositeOp">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -50,7 +50,7 @@
            </property>
           </widget>
          </item>
-         <item column="0" row="1">
+         <item row="1" column="0">
           <widget class="QLabel" name="label_14">
            <property name="text">
             <string>Opac&ity:</string>
@@ -60,7 +60,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="1">
+         <item row="1" column="1">
           <widget class="KisSliderSpinBox" name="intOpacity" native="true">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
@@ -82,7 +82,7 @@
            </property>
           </widget>
          </item>
-         <item column="0" row="2">
+         <item row="2" column="0">
           <widget class="QLabel" name="label_7">
            <property name="text">
             <string>&Gradient:</string>
@@ -92,7 +92,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="2">
+         <item row="2" column="1">
           <layout class="QHBoxLayout" name="horizontalLayout_3">
            <item>
             <widget class="KisCmbGradient" name="cmbGradient">
@@ -113,7 +113,7 @@
            </item>
           </layout>
          </item>
-         <item column="0" row="3">
+         <item row="3" column="0">
           <widget class="QLabel" name="label_6">
            <property name="text">
             <string>St&yle:</string>
@@ -123,7 +123,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="3">
+         <item row="3" column="1">
           <layout class="QHBoxLayout" name="horizontalLayout_4">
            <item>
             <widget class="QComboBox" name="cmbStyle">
@@ -169,7 +169,7 @@
            </item>
           </layout>
          </item>
-         <item column="0" row="4">
+         <item row="4" column="0">
           <widget class="QLabel" name="label_9">
            <property name="text">
             <string>&Angle:</string>
@@ -179,7 +179,7 @@
            </property>
           </widget>
          </item>
-         <item column="0" row="6">
+         <item row="6" column="0">
           <widget class="QLabel" name="label_8">
            <property name="text">
             <string>S&cale:</string>
@@ -189,7 +189,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="6">
+         <item row="6" column="1">
           <widget class="KisSliderSpinBox" name="intScale" native="true">
            <property name="sizePolicy">
             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@@ -202,7 +202,7 @@
            </property>
           </widget>
          </item>
-         <item column="1" row="4">
+         <item row="4" column="1">
           <layout class="QHBoxLayout" name="horizontalLayout">
            <item>
             <widget class="KisLayerStyleAngleSelector" name="angleSelector" native="true">
@@ -229,6 +229,23 @@
            </item>
           </layout>
          </item>
+         <item row="7" column="0">
+          <widget class="QLabel" name="lblDither">
+           <property name="text">
+            <string>&Dither:</string>
+           </property>
+           <property name="buddy">
+            <cstring>chkDither</cstring>
+           </property>
+          </widget>
+         </item>
+         <item row="7" column="1">
+          <widget class="QCheckBox" name="chkDither">
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
         </layout>
         <zorder>label_6</zorder>
         <zorder>intScale</zorder>
@@ -239,6 +256,8 @@
         <zorder>intOpacity</zorder>
         <zorder>label_13</zorder>
         <zorder>label_14</zorder>
+        <zorder>lblDither</zorder>
+        <zorder>chkDither</zorder>
        </widget>
       </item>
       <item>
@@ -285,4 +304,4 @@
  </customwidgets>
  <resources/>
  <connections/>
-</ui>
\ No newline at end of file
+</ui>
diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp
index 5e38958190..787fe1e59c 100644
--- a/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/cmyk_f32/CmykF32ColorSpace.cpp
@@ -1,6 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
- *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy at amyspark.me>
+ *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
 */
@@ -12,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisCmykDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -38,6 +39,7 @@ CmykF32ColorSpace::CmykF32ColorSpace(const QString &name, KoColorProfile *p)
     dbgPlugins << "K: " << uiRanges[3].minVal << uiRanges[3].maxVal;
 
     addStandardCompositeOps<KoCmykF32Traits>(this);
+    addStandardDitherOps<KoCmykF32Traits>(this);
 }
 
 bool CmykF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.cpp
index a40ffecef8..80ed2c0136 100644
--- a/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "CmykU16ColorSpace.h"
 
@@ -11,11 +12,12 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisCmykDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
 CmykU16ColorSpace::CmykU16ColorSpace(const QString &name, KoColorProfile *p)
-    : LcmsColorSpace<CmykU16Traits>(colorSpaceId(), name,  TYPE_CMYKA_16, cmsSigCmykData, p)
+    : LcmsColorSpace<KoCmykU16Traits>(colorSpaceId(), name, TYPE_CMYKA_16, cmsSigCmykData, p)
 {
     addChannel(new KoChannelInfo(i18n("Cyan"), 0 * sizeof(quint16), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), Qt::cyan));
     addChannel(new KoChannelInfo(i18n("Magenta"), 1 * sizeof(quint16), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT16, sizeof(quint16), Qt::magenta));
@@ -25,7 +27,8 @@ CmykU16ColorSpace::CmykU16ColorSpace(const QString &name, KoColorProfile *p)
 
     init();
 
-    addStandardCompositeOps<CmykU16Traits>(this);
+    addStandardCompositeOps<KoCmykU16Traits>(this);
+    addStandardDitherOps<KoCmykF32Traits>(this);
 }
 
 bool CmykU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const
@@ -44,23 +47,23 @@ KoColorSpace *CmykU16ColorSpace::clone() const
 
 void CmykU16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const
 {
-    const CmykU16Traits::Pixel *p = reinterpret_cast<const CmykU16Traits::Pixel *>(pixel);
+    const KoCmykU16Traits::Pixel *p = reinterpret_cast<const KoCmykU16Traits::Pixel *>(pixel);
     QDomElement labElt = doc.createElement("CMYK");
-    labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths< CmykU16Traits::channels_type, qreal>::scaleToA(p->cyan)));
-    labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths< CmykU16Traits::channels_type, qreal>::scaleToA(p->magenta)));
-    labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths< CmykU16Traits::channels_type, qreal>::scaleToA(p->yellow)));
-    labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths< CmykU16Traits::channels_type, qreal>::scaleToA(p->black)));
+    labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU16Traits::channels_type, qreal>::scaleToA(p->cyan)));
+    labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU16Traits::channels_type, qreal>::scaleToA(p->magenta)));
+    labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU16Traits::channels_type, qreal>::scaleToA(p->yellow)));
+    labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU16Traits::channels_type, qreal>::scaleToA(p->black)));
     labElt.setAttribute("space", profile()->name());
     colorElt.appendChild(labElt);
 }
 
 void CmykU16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const
 {
-    CmykU16Traits::Pixel *p = reinterpret_cast<CmykU16Traits::Pixel *>(pixel);
-    p->cyan = KoColorSpaceMaths< qreal, CmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c")));
-    p->magenta = KoColorSpaceMaths< qreal, CmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m")));
-    p->yellow = KoColorSpaceMaths< qreal, CmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y")));
-    p->black = KoColorSpaceMaths< qreal, CmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k")));
+    KoCmykU16Traits::Pixel *p = reinterpret_cast<KoCmykU16Traits::Pixel *>(pixel);
+    p->cyan = KoColorSpaceMaths< qreal, KoCmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c")));
+    p->magenta = KoColorSpaceMaths< qreal, KoCmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m")));
+    p->yellow = KoColorSpaceMaths< qreal, KoCmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y")));
+    p->black = KoColorSpaceMaths< qreal, KoCmykU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k")));
     p->alpha = KoColorSpaceMathsTraits<quint16>::max;
 }
 
diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.h
index 167b0911fa..9b3722966b 100644
--- a/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/cmyk_u16/CmykU16ColorSpace.h
@@ -12,11 +12,9 @@
 
 #include "KoColorModelStandardIds.h"
 
-typedef KoCmykTraits<quint16> CmykU16Traits;
-
 #define TYPE_CMYKA_16           (COLORSPACE_SH(PT_CMYK)|EXTRA_SH(1)|CHANNELS_SH(4)|BYTES_SH(2))
 
-class CmykU16ColorSpace : public LcmsColorSpace<CmykU16Traits>
+class CmykU16ColorSpace : public LcmsColorSpace<KoCmykU16Traits>
 {
 public:
     CmykU16ColorSpace(const QString &name, KoColorProfile *p);
diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.cpp
index 6639971689..f28a1b0d7a 100644
--- a/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006-2007 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "CmykU8ColorSpace.h"
 
@@ -12,11 +13,12 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisCmykDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
 CmykU8ColorSpace::CmykU8ColorSpace(const QString &name, KoColorProfile *p)
-    : LcmsColorSpace<CmykU8Traits>(colorSpaceId(), name,  TYPE_CMYKA_8, cmsSigCmykData, p)
+    : LcmsColorSpace<KoCmykU8Traits>(colorSpaceId(), name, TYPE_CMYKA_8, cmsSigCmykData, p)
 {
     addChannel(new KoChannelInfo(i18n("Cyan"), 0 * sizeof(quint8), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8, sizeof(quint8), Qt::cyan));
     addChannel(new KoChannelInfo(i18n("Magenta"), 1 * sizeof(quint8), 1, KoChannelInfo::COLOR, KoChannelInfo::UINT8, sizeof(quint8), Qt::magenta));
@@ -26,7 +28,8 @@ CmykU8ColorSpace::CmykU8ColorSpace(const QString &name, KoColorProfile *p)
 
     init();
 
-    addStandardCompositeOps<CmykU8Traits>(this);
+    addStandardCompositeOps<KoCmykU8Traits>(this);
+    addStandardDitherOps<KoCmykU8Traits>(this);
 }
 
 bool CmykU8ColorSpace::willDegrade(ColorSpaceIndependence independence) const
@@ -45,23 +48,23 @@ KoColorSpace *CmykU8ColorSpace::clone() const
 
 void CmykU8ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const
 {
-    const CmykU8Traits::Pixel *p = reinterpret_cast<const CmykU8Traits::Pixel *>(pixel);
+    const KoCmykU8Traits::Pixel *p = reinterpret_cast<const KoCmykU8Traits::Pixel *>(pixel);
     QDomElement labElt = doc.createElement("CMYK");
-    labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths< CmykU8Traits::channels_type, qreal>::scaleToA(p->cyan)));
-    labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths< CmykU8Traits::channels_type, qreal>::scaleToA(p->magenta)));
-    labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths< CmykU8Traits::channels_type, qreal>::scaleToA(p->yellow)));
-    labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths< CmykU8Traits::channels_type, qreal>::scaleToA(p->black)));
+    labElt.setAttribute("c", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU8Traits::channels_type, qreal>::scaleToA(p->cyan)));
+    labElt.setAttribute("m", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU8Traits::channels_type, qreal>::scaleToA(p->magenta)));
+    labElt.setAttribute("y", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU8Traits::channels_type, qreal>::scaleToA(p->yellow)));
+    labElt.setAttribute("k", KisDomUtils::toString(KoColorSpaceMaths<KoCmykU8Traits::channels_type, qreal>::scaleToA(p->black)));
     labElt.setAttribute("space", profile()->name());
     colorElt.appendChild(labElt);
 }
 
 void CmykU8ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const
 {
-    CmykU8Traits::Pixel *p = reinterpret_cast<CmykU8Traits::Pixel *>(pixel);
-    p->cyan = KoColorSpaceMaths< qreal, CmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c")));
-    p->magenta = KoColorSpaceMaths< qreal, CmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m")));
-    p->yellow = KoColorSpaceMaths< qreal, CmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y")));
-    p->black = KoColorSpaceMaths< qreal, CmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k")));
+    KoCmykU8Traits::Pixel *p = reinterpret_cast<KoCmykU8Traits::Pixel *>(pixel);
+    p->cyan = KoColorSpaceMaths< qreal, KoCmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("c")));
+    p->magenta = KoColorSpaceMaths< qreal, KoCmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("m")));
+    p->yellow = KoColorSpaceMaths< qreal, KoCmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("y")));
+    p->black = KoColorSpaceMaths< qreal, KoCmykU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("k")));
     p->alpha = KoColorSpaceMathsTraits<quint8>::max;
 }
 
diff --git a/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.h b/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.h
index 25039007f3..9cc65f900a 100644
--- a/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/cmyk_u8/CmykU8ColorSpace.h
@@ -11,9 +11,7 @@
 #include <KoCmykColorSpaceTraits.h>
 #include "KoColorModelStandardIds.h"
 
-typedef KoCmykTraits<quint8> CmykU8Traits;
-
-class CmykU8ColorSpace : public LcmsColorSpace<CmykU8Traits>
+class CmykU8ColorSpace : public LcmsColorSpace<KoCmykU8Traits>
 {
 public:
     CmykU8ColorSpace(const QString &name, KoColorProfile *p);
diff --git a/plugins/color/lcms2engine/colorspaces/gray_f16/GrayF16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/gray_f16/GrayF16ColorSpace.cpp
index ce8943e216..9da4bc19c6 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_f16/GrayF16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/gray_f16/GrayF16ColorSpace.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -14,6 +15,7 @@
 #include <KoColorSpaceRegistry.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisGrayDitherOpFactory.h"
 #include <kis_dom_utils.h>
 
 GrayF16ColorSpace::GrayF16ColorSpace(const QString &name, KoColorProfile *p)
@@ -28,6 +30,7 @@ GrayF16ColorSpace::GrayF16ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoGrayF16Traits>(this);
+    addStandardDitherOps<KoGrayF16Traits>(this);
 }
 
 KoColorSpace *GrayF16ColorSpace::clone() const
diff --git a/plugins/color/lcms2engine/colorspaces/gray_f32/GrayF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/gray_f32/GrayF32ColorSpace.cpp
index ac8a0cd1ed..05c48878da 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_f32/GrayF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/gray_f32/GrayF32ColorSpace.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -15,6 +16,7 @@
 #include <KoColorSpaceRegistry.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisGrayDitherOpFactory.h"
 #include <kis_dom_utils.h>
 
 GrayF32ColorSpace::GrayF32ColorSpace(const QString &name, KoColorProfile *p)
@@ -31,6 +33,7 @@ GrayF32ColorSpace::GrayF32ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoGrayF32Traits>(this);
+    addStandardDitherOps<KoGrayF32Traits>(this);
 }
 
 KoColorSpace *GrayF32ColorSpace::clone() const
diff --git a/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.cpp
index 87de5273d0..d27900727e 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -15,17 +16,19 @@
 #include <KoColorSpaceRegistry.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisGrayDitherOpFactory.h"
 #include <kis_dom_utils.h>
 
 GrayAU16ColorSpace::GrayAU16ColorSpace(const QString &name, KoColorProfile *p)
-    : LcmsColorSpace<GrayAU16Traits>(colorSpaceId(), name,  TYPE_GRAYA_16, cmsSigGrayData, p)
+    : LcmsColorSpace<KoGrayU16Traits>(colorSpaceId(), name,  TYPE_GRAYA_16, cmsSigGrayData, p)
 {
     addChannel(new KoChannelInfo(i18n("Gray"), 0 * sizeof(quint16), 0, KoChannelInfo::COLOR, KoChannelInfo::UINT16));
     addChannel(new KoChannelInfo(i18n("Alpha"), 1 * sizeof(quint16), 1, KoChannelInfo::ALPHA, KoChannelInfo::UINT16));
 
     init();
 
-    addStandardCompositeOps<GrayAU16Traits>(this);
+    addStandardCompositeOps<KoGrayU16Traits>(this);
+    addStandardDitherOps<KoGrayU16Traits>(this);
 }
 
 KoColorSpace *GrayAU16ColorSpace::clone() const
@@ -35,17 +38,17 @@ KoColorSpace *GrayAU16ColorSpace::clone() const
 
 void GrayAU16ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const
 {
-    const GrayAU16Traits::channels_type *p = reinterpret_cast<const GrayAU16Traits::channels_type *>(pixel);
+    const KoGrayU16Traits::channels_type *p = reinterpret_cast<const KoGrayU16Traits::channels_type *>(pixel);
     QDomElement labElt = doc.createElement("Gray");
-    labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< GrayAU16Traits::channels_type, qreal>::scaleToA(p[0])));
+    labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoGrayU16Traits::channels_type, qreal>::scaleToA(p[0])));
     labElt.setAttribute("space", profile()->name());
     colorElt.appendChild(labElt);
 }
 
 void GrayAU16ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const
 {
-    GrayAU16Traits::channels_type *p = reinterpret_cast<GrayAU16Traits::channels_type *>(pixel);
-    p[0] = KoColorSpaceMaths< qreal, GrayAU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g")));
+    KoGrayU16Traits::channels_type *p = reinterpret_cast<KoGrayU16Traits::channels_type *>(pixel);
+    p[0] = KoColorSpaceMaths< qreal, KoGrayU16Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g")));
     p[1] = KoColorSpaceMathsTraits<quint16>::max;
 }
 
diff --git a/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.h b/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.h
index fa14735e0d..5f10ab29c3 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/gray_u16/GrayU16ColorSpace.h
@@ -11,9 +11,7 @@
 #include <KoColorSpaceTraits.h>
 #include "KoColorModelStandardIds.h"
 
-typedef KoColorSpaceTrait<quint16, 2, 1> GrayAU16Traits;
-
-class GrayAU16ColorSpace : public LcmsColorSpace<GrayAU16Traits>
+class GrayAU16ColorSpace : public LcmsColorSpace<KoGrayU16Traits>
 {
 public:
     GrayAU16ColorSpace(const QString &name, KoColorProfile *p);
diff --git a/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.cpp
index 9498b5d383..a066d4be20 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.cpp
@@ -1,5 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -14,17 +15,19 @@
 #include <KoColorSpaceRegistry.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisGrayDitherOpFactory.h"
 #include <kis_dom_utils.h>
 
 GrayAU8ColorSpace::GrayAU8ColorSpace(const QString &name, KoColorProfile *p)
-    : LcmsColorSpace<GrayAU8Traits>(colorSpaceId(), name,  TYPE_GRAYA_8, cmsSigGrayData, p)
+    : LcmsColorSpace<KoGrayU8Traits>(colorSpaceId(), name,  TYPE_GRAYA_8, cmsSigGrayData, p)
 {
     addChannel(new KoChannelInfo(i18n("Gray"), 0, 0, KoChannelInfo::COLOR, KoChannelInfo::UINT8));
     addChannel(new KoChannelInfo(i18n("Alpha"), 1, 1, KoChannelInfo::ALPHA, KoChannelInfo::UINT8));
 
     init();
 
-    addStandardCompositeOps<GrayAU8Traits>(this);
+    addStandardCompositeOps<KoGrayU8Traits>(this);
+    addStandardDitherOps<KoGrayU8Traits>(this);
 }
 
 KoColorSpace *GrayAU8ColorSpace::clone() const
@@ -34,17 +37,17 @@ KoColorSpace *GrayAU8ColorSpace::clone() const
 
 void GrayAU8ColorSpace::colorToXML(const quint8 *pixel, QDomDocument &doc, QDomElement &colorElt) const
 {
-    const GrayAU8Traits::channels_type *p = reinterpret_cast<const GrayAU8Traits::channels_type *>(pixel);
+    const KoGrayU8Traits::channels_type *p = reinterpret_cast<const KoGrayU8Traits::channels_type *>(pixel);
     QDomElement labElt = doc.createElement("Gray");
-    labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< GrayAU8Traits::channels_type, qreal>::scaleToA(p[0])));
+    labElt.setAttribute("g", KisDomUtils::toString(KoColorSpaceMaths< KoGrayU8Traits::channels_type, qreal>::scaleToA(p[0])));
     labElt.setAttribute("space", profile()->name());
     colorElt.appendChild(labElt);
 }
 
 void GrayAU8ColorSpace::colorFromXML(quint8 *pixel, const QDomElement &elt) const
 {
-    GrayAU8Traits::channels_type *p = reinterpret_cast<GrayAU8Traits::channels_type *>(pixel);
-    p[0] = KoColorSpaceMaths< qreal, GrayAU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g")));
+    KoGrayU8Traits::channels_type *p = reinterpret_cast<KoGrayU8Traits::channels_type *>(pixel);
+    p[0] = KoColorSpaceMaths< qreal, KoGrayU8Traits::channels_type >::scaleToA(KisDomUtils::toDouble(elt.attribute("g")));
     p[1] = KoColorSpaceMathsTraits<quint8>::max;
 }
 
diff --git a/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.h b/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.h
index a48f862db9..1f46845e9b 100644
--- a/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.h
+++ b/plugins/color/lcms2engine/colorspaces/gray_u8/GrayU8ColorSpace.h
@@ -11,9 +11,7 @@
 #include <KoColorSpaceTraits.h>
 #include "KoColorModelStandardIds.h"
 
-typedef KoColorSpaceTrait<quint8, 2, 1> GrayAU8Traits;
-
-class GrayAU8ColorSpace : public LcmsColorSpace<GrayAU8Traits>
+class GrayAU8ColorSpace : public LcmsColorSpace<KoGrayU8Traits>
 {
 public:
 
diff --git a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp
index 24b6c992d5..d41e369764 100644
--- a/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/lab_f32/LabF32ColorSpace.cpp
@@ -1,6 +1,6 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
- *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy at amyspark.me>
+ *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
 */
@@ -12,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "../compositeops/KoCompositeOps.h"
+#include "dithering/KisLabDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -31,6 +32,7 @@ LabF32ColorSpace::LabF32ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoLabF32Traits>(this);
+    addStandardDitherOps<KoLabF32Traits>(this);
 
     dbgPlugins << "La*b* (float) channel bounds for: " << icc_p->name();
     dbgPlugins << "L: " << uiRanges[0].minVal << uiRanges[0].maxVal;
diff --git a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp
index 7c2519b4ab..2122409eb0 100644
--- a/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/lab_u16/LabColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "LabColorSpace.h"
 
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "../compositeops/KoCompositeOps.h"
+#include "dithering/KisLabDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -25,6 +27,7 @@ LabU16ColorSpace::LabU16ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoLabU16Traits>(this);
+    addStandardDitherOps<KoLabU16Traits>(this);
 }
 
 bool LabU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp
index 6f148516f4..d0042cd28f 100644
--- a/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/lab_u8/LabU8ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "LabU8ColorSpace.h"
 
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "../compositeops/KoCompositeOps.h"
+#include "dithering/KisLabDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -23,6 +25,7 @@ LabU8ColorSpace::LabU8ColorSpace(const QString &name, KoColorProfile *p) :
     addChannel(new KoChannelInfo(i18n("Alpha"),     3 * sizeof(quint8), 3, KoChannelInfo::ALPHA, KoChannelInfo::UINT8, sizeof(quint8)));
     init();
     addStandardCompositeOps<KoLabU8Traits>(this);
+    addStandardDitherOps<KoLabU8Traits>(this);
 }
 
 bool LabU8ColorSpace::willDegrade(ColorSpaceIndependence /*independence*/) const
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
index cfa85b1796..06a8bb0e7f 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f16/RgbF16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "RgbF16ColorSpace.h"
 
@@ -15,6 +16,7 @@
 #include "compositeops/RgbCompositeOpIn.h"
 #include "compositeops/RgbCompositeOpOut.h"
 #include "compositeops/RgbCompositeOpBumpmap.h"
+#include "dithering/KisRgbDitherOpFactory.h"
 #include <kis_dom_utils.h>
 #include <KoColorSpacePreserveLightnessUtils.h>
 
@@ -29,6 +31,7 @@ RgbF16ColorSpace::RgbF16ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoRgbF16Traits>(this);
+    addStandardDitherOps<KoRgbF16Traits>(this);
 
     addCompositeOp(new RgbCompositeOpIn<KoRgbF16Traits>(this));
     addCompositeOp(new RgbCompositeOpOut<KoRgbF16Traits>(this));
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
index 3d39b4c51e..17c76a962d 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_f32/RgbF32ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "RgbF32ColorSpace.h"
 
@@ -11,12 +12,12 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
-
 #include "compositeops/RgbCompositeOps.h"
-#include <kis_dom_utils.h>
+#include "dithering/KisRgbDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <KoColorSpaceMaths.h>
 #include <KoColorSpacePreserveLightnessUtils.h>
+#include <kis_dom_utils.h>
 
 RgbF32ColorSpace::RgbF32ColorSpace(const QString &name, KoColorProfile *p) :
     LcmsColorSpace<KoRgbF32Traits>(colorSpaceId(), name, TYPE_RGBA_FLT, cmsSigRgbData, p)
@@ -34,6 +35,7 @@ RgbF32ColorSpace::RgbF32ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoRgbF32Traits>(this);
+    addStandardDitherOps<KoRgbF32Traits>(this);
 
     addCompositeOp(new RgbCompositeOpIn<KoRgbF32Traits>(this));
     addCompositeOp(new RgbCompositeOpOut<KoRgbF32Traits>(this));
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
index 4d18df5f86..881edfd8a5 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u16/RgbU16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger at cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "RgbU16ColorSpace.h"
 
@@ -12,6 +13,7 @@
 
 #include "compositeops/KoCompositeOps.h"
 #include "compositeops/RgbCompositeOps.h"
+#include "dithering/KisRgbDitherOpFactory.h"
 #include "kis_dom_utils.h"
 #include <KoColorConversions.h>
 #include <KoColorSpacePreserveLightnessUtils.h>
@@ -26,6 +28,7 @@ RgbU16ColorSpace::RgbU16ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoBgrU16Traits>(this);
+    addStandardDitherOps<KoBgrU16Traits>(this);
 
     addCompositeOp(new RgbCompositeOpIn<KoBgrU16Traits>(this));
     addCompositeOp(new RgbCompositeOpOut<KoBgrU16Traits>(this));
diff --git a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
index 42521bd92b..da3a0ae29e 100644
--- a/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/rgb_u8/RgbU8ColorSpace.cpp
@@ -1,6 +1,7 @@
 /*
  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak at codepimps.org>
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud at valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: LGPL-2.1-or-later
  */
@@ -12,13 +13,14 @@
 
 #include <klocalizedstring.h>
 
-#include <KoIntegerMaths.h>
-#include <KoColorSpaceRegistry.h>
-#include <KoColorConversions.h>
 #include "compositeops/KoCompositeOps.h"
 #include "compositeops/RgbCompositeOps.h"
-#include <kis_dom_utils.h>
+#include "dithering/KisRgbDitherOpFactory.h"
+#include <KoColorConversions.h>
 #include <KoColorSpacePreserveLightnessUtils.h>
+#include <KoColorSpaceRegistry.h>
+#include <KoIntegerMaths.h>
+#include <kis_dom_utils.h>
 
 #define downscale(quantum)  (quantum) //((unsigned char) ((quantum)/257UL))
 #define upscale(value)  (value) // ((quint8) (257UL*(value)))
@@ -34,6 +36,7 @@ RgbU8ColorSpace::RgbU8ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoBgrU8Traits>(this);
+    addStandardDitherOps<KoBgrU8Traits>(this);
 
     addCompositeOp(new RgbCompositeOpIn<KoBgrU8Traits>(this));
     addCompositeOp(new RgbCompositeOpOut<KoBgrU8Traits>(this));
diff --git a/plugins/color/lcms2engine/colorspaces/xyz_f16/XyzF16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/xyz_f16/XyzF16ColorSpace.cpp
index 870985da93..d3cd9101ed 100644
--- a/plugins/color/lcms2engine/colorspaces/xyz_f16/XyzF16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/xyz_f16/XyzF16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "XyzF16ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisXyzDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -25,6 +27,7 @@ XyzF16ColorSpace::XyzF16ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoXyzF16Traits>(this);
+    addStandardDitherOps<KoXyzF16Traits>(this);
 }
 
 bool XyzF16ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/xyz_f32/XyzF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/xyz_f32/XyzF32ColorSpace.cpp
index ae1d9eefea..44d8f7e5fc 100644
--- a/plugins/color/lcms2engine/colorspaces/xyz_f32/XyzF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/xyz_f32/XyzF32ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "XyzF32ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisXyzDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -29,6 +31,7 @@ XyzF32ColorSpace::XyzF32ColorSpace(const QString &name, KoColorProfile *p) :
     init();
 
     addStandardCompositeOps<KoXyzF32Traits>(this);
+    addStandardDitherOps<KoXyzF32Traits>(this);
 }
 
 bool XyzF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/xyz_u16/XyzU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/xyz_u16/XyzU16ColorSpace.cpp
index b69855c783..c83de77b99 100644
--- a/plugins/color/lcms2engine/colorspaces/xyz_u16/XyzU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/xyz_u16/XyzU16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "XyzU16ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisXyzDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -25,6 +27,7 @@ XyzU16ColorSpace::XyzU16ColorSpace(const QString &name, KoColorProfile *p) :
 
     // ADD, ALPHA_DARKEN, BURN, DIVIDE, DODGE, ERASE, MULTIPLY, OVER, OVERLAY, SCREEN, SUBTRACT
     addStandardCompositeOps<KoXyzU16Traits>(this);
+    addStandardDitherOps<KoXyzU16Traits>(this);
 }
 
 bool XyzU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/xyz_u8/XyzU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/xyz_u8/XyzU8ColorSpace.cpp
index 5154939c0e..e2b5246cc4 100644
--- a/plugins/color/lcms2engine/colorspaces/xyz_u8/XyzU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/xyz_u8/XyzU8ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "XyzU8ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisXyzDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -25,6 +27,7 @@ XyzU8ColorSpace::XyzU8ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoXyzU8Traits>(this);
+    addStandardDitherOps<KoXyzU8Traits>(this);
 }
 
 bool XyzU8ColorSpace::willDegrade(ColorSpaceIndependence /*independence*/) const
diff --git a/plugins/color/lcms2engine/colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp
index 7d77b1553e..1c5b8c4be8 100644
--- a/plugins/color/lcms2engine/colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/ycbcr_f32/YCbCrF32ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "YCbCrF32ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisYCbCrDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -30,6 +32,7 @@ YCbCrF32ColorSpace::YCbCrF32ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoYCbCrF32Traits>(this);
+    addStandardDitherOps<KoYCbCrF32Traits>(this);
 }
 
 bool YCbCrF32ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp
index 2cb64eb3b5..5995a08b31 100644
--- a/plugins/color/lcms2engine/colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/ycbcr_u16/YCbCrU16ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "YCbCrU16ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisYCbCrDitherOpFactory.h"
 #include <KoColorConversions.h>
 #include <kis_dom_utils.h>
 
@@ -25,6 +27,7 @@ YCbCrU16ColorSpace::YCbCrU16ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoYCbCrU16Traits>(this);
+    addStandardDitherOps<KoYCbCrU16Traits>(this);
 }
 
 bool YCbCrU16ColorSpace::willDegrade(ColorSpaceIndependence independence) const
diff --git a/plugins/color/lcms2engine/colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp b/plugins/color/lcms2engine/colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp
index 376c4d9caa..c71e141a71 100644
--- a/plugins/color/lcms2engine/colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp
+++ b/plugins/color/lcms2engine/colorspaces/ycbcr_u8/YCbCrU8ColorSpace.cpp
@@ -1,8 +1,9 @@
 /*
  *  SPDX-FileCopyrightText: 2007 Cyrille Berger (cberger at cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: LGPL-2.1-or-later
-*/
+ */
 
 #include "YCbCrU8ColorSpace.h"
 #include <QDomElement>
@@ -11,6 +12,7 @@
 #include <klocalizedstring.h>
 
 #include "compositeops/KoCompositeOps.h"
+#include "dithering/KisYCbCrDitherOpFactory.h"
 #include <KoColorConversions.h>
 
 #include <kis_dom_utils.h>
@@ -26,6 +28,7 @@ YCbCrU8ColorSpace::YCbCrU8ColorSpace(const QString &name, KoColorProfile *p)
     init();
 
     addStandardCompositeOps<KoYCbCrU8Traits>(this);
+    addStandardDitherOps<KoYCbCrU8Traits>(this);
 }
 
 bool YCbCrU8ColorSpace::willDegrade(ColorSpaceIndependence /*independence*/) const
diff --git a/plugins/generators/gradient/KisGradientGenerator.cpp b/plugins/generators/gradient/KisGradientGenerator.cpp
index 77e98ed0b5..8a4c96e391 100644
--- a/plugins/generators/gradient/KisGradientGenerator.cpp
+++ b/plugins/generators/gradient/KisGradientGenerator.cpp
@@ -2,6 +2,7 @@
  * KDE. Krita Project.
  *
  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba at gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -48,7 +49,8 @@ void KisGradientGenerator::generate(KisProcessingInformation dst,
         generatorConfiguration->repeat(),
         generatorConfiguration->antiAliasThreshold(),
         generatorConfiguration->reverse(),
-        QRect(dst.topLeft(), size)
+        QRect(dst.topLeft(), size),
+        generatorConfiguration->dither()
     );
 }
 
diff --git a/plugins/generators/gradient/KisGradientGeneratorConfigWidget.cpp b/plugins/generators/gradient/KisGradientGeneratorConfigWidget.cpp
index b30a899d23..e66cbccda3 100644
--- a/plugins/generators/gradient/KisGradientGeneratorConfigWidget.cpp
+++ b/plugins/generators/gradient/KisGradientGeneratorConfigWidget.cpp
@@ -2,6 +2,7 @@
  * KDE. Krita Project.
  *
  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba at gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -70,6 +71,7 @@ KisGradientGeneratorConfigWidget::KisGradientGeneratorConfigWidget(QWidget* pare
     connect(m_ui.comboBoxShape, SIGNAL(currentIndexChanged(int)), this, SIGNAL(sigConfigurationUpdated()));
     connect(m_ui.comboBoxRepeat, SIGNAL(currentIndexChanged(int)), this, SIGNAL(sigConfigurationUpdated()));
     connect(m_ui.sliderAntiAliasThreshold, SIGNAL(valueChanged(qreal)), this, SIGNAL(sigConfigurationUpdated()));
+    connect(m_ui.checkBoxDither, SIGNAL(toggled(bool)), this, SIGNAL(sigConfigurationUpdated()));
     connect(m_ui.checkBoxReverse, SIGNAL(toggled(bool)), this, SIGNAL(sigConfigurationUpdated()));
 
     connect(m_ui.spinBoxStartPositionX, SIGNAL(valueChanged(double)), this, SIGNAL(sigConfigurationUpdated()));
@@ -118,6 +120,7 @@ void KisGradientGeneratorConfigWidget::setConfiguration(const KisPropertiesConfi
         m_ui.comboBoxShape->setCurrentIndex(generatorConfig->shape());
         m_ui.comboBoxRepeat->setCurrentIndex(generatorConfig->repeat());
         m_ui.sliderAntiAliasThreshold->setValue(generatorConfig->antiAliasThreshold());
+        m_ui.checkBoxDither->setChecked(generatorConfig->dither());
         m_ui.checkBoxReverse->setChecked(generatorConfig->reverse());
 
         m_ui.spinBoxStartPositionX->setValue(generatorConfig->startPositionX());
@@ -152,6 +155,7 @@ KisPropertiesConfigurationSP KisGradientGeneratorConfigWidget::configuration() c
     config->setShape(static_cast<KisGradientPainter::enumGradientShape>(m_ui.comboBoxShape->currentIndex()));
     config->setRepeat(static_cast<KisGradientPainter::enumGradientRepeat>(m_ui.comboBoxRepeat->currentIndex()));
     config->setAntiAliasThreshold(m_ui.sliderAntiAliasThreshold->value());
+    config->setDither(m_ui.checkBoxDither->isChecked());
     config->setReverse(m_ui.checkBoxReverse->isChecked());
 
     config->setStartPositionX(m_ui.spinBoxStartPositionX->value());
diff --git a/plugins/generators/gradient/KisGradientGeneratorConfigWidget.ui b/plugins/generators/gradient/KisGradientGeneratorConfigWidget.ui
index 9e378e2610..f0ea682d21 100644
--- a/plugins/generators/gradient/KisGradientGeneratorConfigWidget.ui
+++ b/plugins/generators/gradient/KisGradientGeneratorConfigWidget.ui
@@ -123,6 +123,20 @@
            </property>
           </widget>
          </item>
+         <item row="4" column="0">
+          <widget class="QLabel" name="labelDither">
+           <property name="text">
+            <string>Dithering:</string>
+           </property>
+          </widget>
+         </item>
+         <item row="4" column="1">
+          <widget class="QCheckBox" name="checkBoxDither">
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
         </layout>
        </item>
        <item>
diff --git a/plugins/generators/gradient/KisGradientGeneratorConfiguration.cpp b/plugins/generators/gradient/KisGradientGeneratorConfiguration.cpp
index 3152f20e8f..4a98ccec27 100644
--- a/plugins/generators/gradient/KisGradientGeneratorConfiguration.cpp
+++ b/plugins/generators/gradient/KisGradientGeneratorConfiguration.cpp
@@ -2,6 +2,7 @@
  * KDE. Krita Project.
  *
  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba at gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -51,6 +52,11 @@ bool KisGradientGeneratorConfiguration::reverse() const
     return getBool("reverse", defaultReverse());
 }
 
+bool KisGradientGeneratorConfiguration::dither() const
+{
+    return getBool("dither", defaultDither());
+}
+
 qreal KisGradientGeneratorConfiguration::startPositionX() const
 {
     return getDouble("start_position_x", defaultStartPositionX());
@@ -184,6 +190,11 @@ void KisGradientGeneratorConfiguration::setAntiAliasThreshold(qreal newAntiAlias
     setProperty("antialias_threshold", newAntiAliasThreshold);
 }
 
+void KisGradientGeneratorConfiguration::setDither(bool newDither)
+{
+    setProperty("dither", newDither);
+}
+
 void KisGradientGeneratorConfiguration::setReverse(bool newReverse)
 {
     setProperty("reverse", newReverse);
@@ -303,4 +314,4 @@ void KisGradientGeneratorConfiguration::setDefaults()
     setEndPositionDistance(defaultEndPositionDistance());
     setEndPositionDistanceUnits(defaultEndPositionDistanceUnits());
     setGradient(defaultGradient());
-}
\ No newline at end of file
+}
diff --git a/plugins/generators/gradient/KisGradientGeneratorConfiguration.h b/plugins/generators/gradient/KisGradientGeneratorConfiguration.h
index ff533eddba..07944ae9cf 100644
--- a/plugins/generators/gradient/KisGradientGeneratorConfiguration.h
+++ b/plugins/generators/gradient/KisGradientGeneratorConfiguration.h
@@ -2,6 +2,7 @@
  * KDE. Krita Project.
  *
  * SPDX-FileCopyrightText: 2020 Deif Lou <ginoba at gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  * SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -82,6 +83,11 @@ public:
         return 0.0;
     }
 
+    static constexpr bool defaultDither()
+    {
+        return false;
+    }
+
     static constexpr bool defaultReverse()
     {
         return false;
@@ -173,6 +179,7 @@ public:
     KisGradientPainter::enumGradientShape shape() const;
     KisGradientPainter::enumGradientRepeat repeat() const;
     qreal antiAliasThreshold() const;
+    bool dither() const;
     bool reverse() const;
     qreal startPositionX() const;
     qreal startPositionY() const;
@@ -195,6 +202,7 @@ public:
     void setShape(KisGradientPainter::enumGradientShape newShape);
     void setRepeat(KisGradientPainter::enumGradientRepeat newRepeat);
     void setAntiAliasThreshold(qreal newAntiAliasThreshold);
+    void setDither(bool newDither);
     void setReverse(bool newReverse);
     void setStartPositionX(qreal newStartPositionX);
     void setStartPositionY(qreal newStartPositionY);
diff --git a/plugins/tools/basictools/kis_tool_gradient.cc b/plugins/tools/basictools/kis_tool_gradient.cc
index dc97024edc..be1ce3a66a 100644
--- a/plugins/tools/basictools/kis_tool_gradient.cc
+++ b/plugins/tools/basictools/kis_tool_gradient.cc
@@ -4,6 +4,7 @@
  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak at codepimps.org>
  *  SPDX-FileCopyrightText: 2003 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2004-2007 Adrian Page <adrian at pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -58,6 +59,7 @@ KisToolGradient::KisToolGradient(KoCanvasBase * canvas)
     m_startPos = QPointF(0, 0);
     m_endPos = QPointF(0, 0);
 
+    m_dither = false;
     m_reverse = false;
     m_shape = KisGradientPainter::GradientShapeLinear;
     m_repeat = KisGradientPainter::GradientRepeatNone;
@@ -156,6 +158,7 @@ void KisToolGradient::endPrimaryAction(KoPointerEvent *event)
         KisGradientPainter::enumGradientRepeat repeat = m_repeat;
         bool reverse = m_reverse;
         double antiAliasThreshold = m_antiAliasThreshold;
+        bool dither = m_dither;
 
         KUndo2MagicString actionName = kundo2_i18n("Gradient");
         KisProcessingApplicator applicator(image, resources->currentNode(),
@@ -166,7 +169,7 @@ void KisToolGradient::endPrimaryAction(KoPointerEvent *event)
         applicator.applyCommand(
             new KisCommandUtils::LambdaCommand(
                 [resources, startPos, endPos,
-                 shape, repeat, reverse, antiAliasThreshold] () mutable {
+                 shape, repeat, reverse, antiAliasThreshold, dither] () mutable {
 
                     KisNodeSP node = resources->currentNode();
                     KisPaintDeviceSP device = node->paintDevice();
@@ -183,7 +186,8 @@ void KisToolGradient::endPrimaryAction(KoPointerEvent *event)
                     painter.paintGradient(startPos, endPos,
                                           repeat, antiAliasThreshold,
                                           reverse, 0, 0,
-                                          bounds.width(), bounds.height());
+                                          bounds.width(), bounds.height(),
+                                          dither);
 
                     return painter.endAndTakeTransaction();
                 }));
@@ -266,11 +270,16 @@ QWidget* KisToolGradient::createOptionWidget()
     connect(m_ckReverse, SIGNAL(toggled(bool)), this, SLOT(slotSetReverse(bool)));
     addOptionWidgetOption(m_ckReverse);
 
+    m_ckDither = new QCheckBox(i18nc("the gradient will be dithered", "Dither"), widget);
+    m_ckDither->setObjectName("dither_check");
+    connect(m_ckDither, SIGNAL(toggled(bool)), this, SLOT(slotSetDither(bool)));
+    addOptionWidgetOption(m_ckDither);
 
     widget->setFixedHeight(widget->sizeHint().height());
 
 
     // load configuration settings into widget (updating UI will update internal variables from signals/slots)
+    m_ckDither->setChecked(m_configGroup.readEntry<bool>("dither", false));
     m_ckReverse->setChecked((bool)m_configGroup.readEntry("reverse", false));
     m_cmbShape->setCurrentIndex((int)m_configGroup.readEntry("shape", 0));
     m_cmbRepeat->setCurrentIndex((int)m_configGroup.readEntry("repeat", 0));
@@ -297,6 +306,12 @@ void KisToolGradient::slotSetReverse(bool state)
     m_configGroup.writeEntry("reverse", state);
 }
 
+void KisToolGradient::slotSetDither(bool state)
+{
+    m_dither = state;
+    m_configGroup.writeEntry("dither", state);
+}
+
 void KisToolGradient::slotSetAntiAliasThreshold(qreal value)
 {
     m_antiAliasThreshold = value;
diff --git a/plugins/tools/basictools/kis_tool_gradient.h b/plugins/tools/basictools/kis_tool_gradient.h
index 2bd765663f..81e5813a6c 100644
--- a/plugins/tools/basictools/kis_tool_gradient.h
+++ b/plugins/tools/basictools/kis_tool_gradient.h
@@ -5,6 +5,7 @@
  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak at codepimps.org>
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud at valdyas.org>
  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian at pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy at amyspark.me>
  *
  *  SPDX-License-Identifier: GPL-2.0-or-later
  */
@@ -56,6 +57,7 @@ private Q_SLOTS:
     void slotSetShape(int);
     void slotSetRepeat(int);
     void slotSetReverse(bool);
+    void slotSetDither(bool);
     void slotSetAntiAliasThreshold(qreal);
     void setOpacity(qreal opacity);
 protected Q_SLOTS:
@@ -80,11 +82,13 @@ private:
     KisGradientPainter::enumGradientShape m_shape;
     KisGradientPainter::enumGradientRepeat m_repeat;
 
+    bool m_dither;
     bool m_reverse;
     double m_antiAliasThreshold;
 
     QLabel *m_lbShape;
     QLabel *m_lbRepeat;
+    QCheckBox *m_ckDither;
     QCheckBox *m_ckReverse;
     KComboBox *m_cmbShape;
     KComboBox *m_cmbRepeat;


More information about the kimageshop mailing list