[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-kimageshop
Subject:    =?utf-8?q?=5Bgraphics/krita=5D_/=3A_Feature=3A_HDR_gradients?=
From:       L. E. Segovia <null () kde ! org>
Date:       2021-02-10 18:57:41
Message-ID: 20210210185741.A2D821243333 () leptone ! kde ! org
[Download RAW message or body]

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@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@kde.org>
  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@pagenet.plus.com>
  *  SPDX-FileCopyrightText: 2019 Miguel Lopez <reptillia39@live.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@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, \
+    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@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@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@valdyas.org>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
  *  SPDX-FileCopyrightText: 2006-2007 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
  *  SPDX-FileCopyrightText: 2007 Emanuele Tamponi <emanuele@valinor.it>
- *
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
  *  SPDX-FileCopyrightText: 2006 Cyrille Berger <cberger@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@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@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@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@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@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@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@valdyas.org>
     SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
     SPDX-FileCopyrightText: 2004, 2007 Sven Langkamp <sven.langkamp@gmail.com>
+    SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.org>
     SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
     SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
+    SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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&amp;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&amp;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>&amp;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&amp;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>&amp;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&amp;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>&amp;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@cberger.net>
- *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
+ *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
- *  SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me>
+ *  SPDX-FileCopyrightText: 2020-2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@codepimps.org>
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@cberger.net)
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@gmail.com>
+ * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@codepimps.org>
  *  SPDX-FileCopyrightText: 2003 Boudewijn Rempt <boud@valdyas.org>
  *  SPDX-FileCopyrightText: 2004-2007 Adrian Page <adrian@pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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@codepimps.org>
  *  SPDX-FileCopyrightText: 2004 Boudewijn Rempt <boud@valdyas.org>
  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
+ *  SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@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;


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic