File indexing completed on 2024-06-09 04:23:36

0001 /*
0002  *  SPDX-FileCopyrightText: 2005 Adrian Page <adrian@pagenet.plus.com>
0003  *  SPDX-FileCopyrightText: 2008 Bart Coppens <kde@bartcoppens.be>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "KoRgbU8ColorSpaceTester.h"
0009 
0010 #include "KoColorModelStandardIds.h"
0011 
0012 #include <simpletest.h>
0013 #include <DebugPigment.h>
0014 #include <string.h>
0015 
0016 #include "KoColor.h"
0017 #include "KoColorSpace.h"
0018 #include "KoColorSpaceRegistry.h"
0019 #include "KoCompositeOp.h"
0020 #include "KoMixColorsOp.h"
0021 #include <KoCompositeOpRegistry.h>
0022 #include <kistest.h>
0023 
0024 #define NUM_CHANNELS 4
0025 
0026 #define RED_CHANNEL 0
0027 #define GREEN_CHANNEL 1
0028 #define BLUE_CHANNEL 2
0029 #define ALPHA_CHANNEL 3
0030 #include <KoColorProfile.h>
0031 
0032 void KoRgbU8ColorSpaceTester::testBasics()
0033 {
0034 }
0035 
0036 #define PIXEL_RED 0
0037 #define PIXEL_GREEN 1
0038 #define PIXEL_BLUE 2
0039 #define PIXEL_ALPHA 3
0040 
0041 void KoRgbU8ColorSpaceTester::testMixColors()
0042 {
0043     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
0044     KoMixColorsOp * mixOp = cs->mixColorsOp();
0045 
0046     // Test mixColors.
0047     quint8 pixel1[4];
0048     quint8 pixel2[4];
0049     quint8 outputPixel[4];
0050 
0051     pixel1[PIXEL_RED] = 255;
0052     pixel1[PIXEL_GREEN] = 255;
0053     pixel1[PIXEL_BLUE] = 255;
0054     pixel1[PIXEL_ALPHA] = 255;
0055 
0056     pixel2[PIXEL_RED] = 0;
0057     pixel2[PIXEL_GREEN] = 0;
0058     pixel2[PIXEL_BLUE] = 0;
0059     pixel2[PIXEL_ALPHA] = 0;
0060 
0061     const quint8 *pixelPtrs[2];
0062     qint16 weights[2];
0063 
0064     pixelPtrs[0] = pixel1;
0065     pixelPtrs[1] = pixel2;
0066 
0067     weights[0] = 255;
0068     weights[1] = 0;
0069 
0070     mixOp->mixColors(pixelPtrs, weights, 2, outputPixel);
0071 
0072     QVERIFY((int)outputPixel[PIXEL_RED] == 255);
0073     QVERIFY((int)outputPixel[PIXEL_GREEN] == 255);
0074     QVERIFY((int)outputPixel[PIXEL_BLUE] == 255);
0075     QVERIFY((int)outputPixel[PIXEL_ALPHA] == 255);
0076 
0077     weights[0] = 0;
0078     weights[1] = 255;
0079 
0080     mixOp->mixColors(pixelPtrs, weights, 2, outputPixel);
0081 
0082     QVERIFY((int)outputPixel[PIXEL_RED] == 0);
0083     QVERIFY((int)outputPixel[PIXEL_GREEN] == 0);
0084     QVERIFY((int)outputPixel[PIXEL_BLUE] == 0);
0085     QVERIFY((int)outputPixel[PIXEL_ALPHA] == 0);
0086 
0087     weights[0] = 128;
0088     weights[1] = 127;
0089 
0090     mixOp->mixColors(pixelPtrs, weights, 2, outputPixel);
0091 
0092     QVERIFY((int)outputPixel[PIXEL_RED] == 255);
0093     QVERIFY((int)outputPixel[PIXEL_GREEN] == 255);
0094     QVERIFY((int)outputPixel[PIXEL_BLUE] == 255);
0095     QVERIFY((int)outputPixel[PIXEL_ALPHA] == 128);
0096 
0097     pixel1[PIXEL_RED] = 200;
0098     pixel1[PIXEL_GREEN] = 100;
0099     pixel1[PIXEL_BLUE] = 50;
0100     pixel1[PIXEL_ALPHA] = 255;
0101 
0102     pixel2[PIXEL_RED] = 100;
0103     pixel2[PIXEL_GREEN] = 200;
0104     pixel2[PIXEL_BLUE] = 20;
0105     pixel2[PIXEL_ALPHA] = 255;
0106 
0107     mixOp->mixColors(pixelPtrs, weights, 2, outputPixel);
0108 
0109     QVERIFY((int)outputPixel[PIXEL_RED] == 150);
0110     QCOMPARE((int)outputPixel[PIXEL_GREEN], 150);
0111     QVERIFY((int)outputPixel[PIXEL_BLUE] == 35);
0112     QVERIFY((int)outputPixel[PIXEL_ALPHA] == 255);
0113 
0114     pixel1[PIXEL_RED] = 0;
0115     pixel1[PIXEL_GREEN] = 0;
0116     pixel1[PIXEL_BLUE] = 0;
0117     pixel1[PIXEL_ALPHA] = 0;
0118 
0119     pixel2[PIXEL_RED] = 255;
0120     pixel2[PIXEL_GREEN] = 255;
0121     pixel2[PIXEL_BLUE] = 255;
0122     pixel2[PIXEL_ALPHA] = 254;
0123 
0124     weights[0] = 89;
0125     weights[1] = 166;
0126 
0127     mixOp->mixColors(pixelPtrs, weights, 2, outputPixel);
0128 
0129     QVERIFY((int)outputPixel[PIXEL_RED] == 255);
0130     QVERIFY((int)outputPixel[PIXEL_GREEN] == 255);
0131     QVERIFY((int)outputPixel[PIXEL_BLUE] == 255);
0132     QVERIFY((int)outputPixel[PIXEL_ALPHA] == 165);
0133 }
0134 void KoRgbU8ColorSpaceTester::testMixColorsAverage()
0135 {
0136     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
0137     KoMixColorsOp * mixOp = cs->mixColorsOp();
0138 
0139     // Test mixColors.
0140     quint8 pixel1[4];
0141     quint8 pixel2[4];
0142     quint8 outputPixel[4];
0143 
0144     pixel1[PIXEL_RED] = 255;
0145     pixel1[PIXEL_GREEN] = 255;
0146     pixel1[PIXEL_BLUE] = 255;
0147     pixel1[PIXEL_ALPHA] = 255;
0148 
0149     pixel2[PIXEL_RED] = 0;
0150     pixel2[PIXEL_GREEN] = 0;
0151     pixel2[PIXEL_BLUE] = 0;
0152     pixel2[PIXEL_ALPHA] = 0;
0153 
0154     const quint8 *pixelPtrs[2];
0155 
0156     pixelPtrs[0] = pixel1;
0157     pixelPtrs[1] = pixel2;
0158 
0159     mixOp->mixColors(pixelPtrs, 2, outputPixel);
0160 
0161     QCOMPARE((int)outputPixel[PIXEL_RED], 255);
0162     QCOMPARE((int)outputPixel[PIXEL_GREEN], 255);
0163     QCOMPARE((int)outputPixel[PIXEL_BLUE], 255);
0164     QCOMPARE((int)outputPixel[PIXEL_ALPHA], 128);
0165 
0166     pixel2[PIXEL_ALPHA] = 255;
0167     mixOp->mixColors(pixelPtrs, 2, outputPixel);
0168 
0169     QCOMPARE((int)outputPixel[PIXEL_RED], 128);
0170     QCOMPARE((int)outputPixel[PIXEL_GREEN], 128);
0171     QCOMPARE((int)outputPixel[PIXEL_BLUE], 128);
0172     QCOMPARE((int)outputPixel[PIXEL_ALPHA], 255);
0173 }
0174 
0175 void KoRgbU8ColorSpaceTester::testCompositeOps()
0176 {
0177     // Just COMPOSITE_COPY for now
0178 
0179     QList<KoID> depthIDs = KoColorSpaceRegistry::instance()->colorDepthList(RGBAColorModelID.id(),
0180                            KoColorSpaceRegistry::AllColorSpaces);
0181 
0182     Q_FOREACH (const KoID& depthId, depthIDs) {
0183 
0184         if (depthId.id().contains("Float")) continue;
0185 
0186         qDebug() << depthId.id();
0187         const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(
0188                                      RGBAColorModelID.id(), depthId.id(), "");
0189         
0190         const KoCompositeOp* copyOp = cs->compositeOp(COMPOSITE_COPY);
0191         KoColor src(cs), dst(cs);
0192 
0193         QColor red(255, 0, 0);
0194         QColor blue(0, 0, 255);
0195         QColor transparentRed(255, 0, 0, 0);
0196 
0197         // Copying a color over another color should replace the original color
0198         src.fromQColor(red);
0199         dst.fromQColor(blue);
0200         
0201         qDebug() << src.toQColor() << dst.toQColor();
0202 
0203         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) != 0);
0204 
0205         copyOp->composite(dst.data(), cs->pixelSize(), src.data(), cs->pixelSize(),
0206                           0, 0, 1, 1, OPACITY_OPAQUE_U8);
0207 
0208         src.fromQColor(red);
0209         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) == 0);
0210 
0211         // Copying something transparent over something non-transparent should, of course, make the dst transparent
0212         src.fromQColor(transparentRed);
0213         dst.fromQColor(blue);
0214 
0215         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) != 0);
0216 
0217         copyOp->composite(dst.data(), cs->pixelSize(), src.data(), cs->pixelSize(),
0218                           0, 0, 1, 1, OPACITY_OPAQUE_U8);
0219 
0220         src.fromQColor(transparentRed);
0221         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) == 0);
0222 
0223         // Copying something solid over something transparent
0224         src.fromQColor(blue);
0225         dst.fromQColor(transparentRed);
0226 
0227         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) != 0);
0228 
0229         copyOp->composite(dst.data(), cs->pixelSize(), src.data(), cs->pixelSize(),
0230                           0, 0, 1, 1, OPACITY_OPAQUE_U8);
0231 
0232         src.fromQColor(blue);
0233         QVERIFY(memcmp(dst.data(), src.data(), cs->pixelSize()) == 0);
0234 
0235     }
0236 }
0237 
0238 void KoRgbU8ColorSpaceTester::testCompositeOpsWithChannelFlags()
0239 {
0240     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
0241     QList<KoCompositeOp*> ops = cs->compositeOps();
0242 
0243     Q_FOREACH (const KoCompositeOp *op, ops) {
0244         /**
0245          * ALPHA_DARKEN composite op doesn't take channel
0246          * flags into account, so just skip it
0247          */
0248         if (op->id() == COMPOSITE_ALPHA_DARKEN) continue;
0249         if (op->id() == COMPOSITE_DISSOLVE) continue;
0250 
0251         quint8 src[] = {128,128,128,129};
0252         quint8 goodDst[] = {10,10,10,11};
0253         quint8 badDst[] = {12,12,12,0};
0254 
0255         KoCompositeOp::ParameterInfo params;
0256         params.maskRowStart  = 0;
0257         params.dstRowStride  = 0;
0258         params.srcRowStride  = 0;
0259         params.maskRowStride = 0;
0260         params.rows          = 1;
0261         params.cols          = 1;
0262         params.opacity       = 1.0f;
0263         params.flow          = 1.0f;
0264 
0265         QBitArray channelFlags(4, true);
0266         channelFlags[2] = false;
0267         params.channelFlags  = channelFlags;
0268 
0269         params.srcRowStart   = src;
0270 
0271         params.dstRowStart   = goodDst;
0272         op->composite(params);
0273 
0274         params.dstRowStart   = badDst;
0275         op->composite(params);
0276 
0277         /**
0278          * The badDst has zero alpha, so the channels should be zeroed
0279          * before increasing alpha of the pixel
0280          */
0281         if (badDst[3] != 0 && badDst[2] != 0) {
0282             qDebug() << op->id()
0283                      << "easy case:" << goodDst[2]
0284                      << "difficult case:" << badDst[2];
0285 
0286             qDebug() << "The composite op has failed to erase the color "
0287                 "channel which was hidden by zero alpha.";
0288             qDebug() << "Expected Blue channel:" << 0;
0289             qDebug() << "Actual Blue channel:  " << badDst[2];
0290 
0291             QFAIL("Failed to erase color channel");
0292         }
0293     }
0294 }
0295 
0296 // for posix_memalign()
0297 #include <stdlib.h>
0298 #include <config-xsimd.h>
0299 
0300 #if defined Q_OS_WIN
0301 #define MEMALIGN_ALLOC(p, a, s) ((*(p)) = _aligned_malloc((s), (a)), *(p) ? 0 : errno)
0302 #define MEMALIGN_FREE(p) _aligned_free((p))
0303 #else
0304 #define MEMALIGN_ALLOC(p, a, s) posix_memalign((p), (a), (s))
0305 #define MEMALIGN_FREE(p) free((p))
0306 #endif
0307 
0308 void KoRgbU8ColorSpaceTester::testCompositeCopyDivisionByZero()
0309 {
0310     const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
0311     const KoCompositeOp *op = cs->compositeOp(COMPOSITE_COPY);
0312 
0313     const int pixelAlignment = sizeof(float) * 16; // 512-bit alignment should be enough for everyone! (c)
0314     const int numPixels = 256;
0315     void *src = 0;
0316     void *dst = 0;
0317 
0318     int result = 0;
0319 
0320     result = MEMALIGN_ALLOC(&src, pixelAlignment, cs->pixelSize() * numPixels);
0321     KIS_ASSERT(!result);
0322 
0323     result = MEMALIGN_ALLOC(&dst, pixelAlignment, cs->pixelSize() * numPixels);
0324     KIS_ASSERT(!result);
0325 
0326     // generate buffers in misaligned manner
0327     int numTestablePixels = numPixels - 1;
0328     quint8 * srcPtr = reinterpret_cast<quint8*>(src) + cs->pixelSize();
0329     quint8 * dstPtr = reinterpret_cast<quint8*>(dst) + cs->pixelSize();
0330 
0331 
0332     auto testBadPixel = [cs, op, srcPtr, dstPtr, numTestablePixels] (int badPixelPos,
0333             const quint8 *goodSrc,
0334             const quint8 *goodDst,
0335             const quint8 *badSrc,
0336             const quint8 *badDst,
0337             quint8 opacity,
0338             quint8 *expectedDst) {
0339 
0340         quint8 *badPixelDstPtr = dstPtr + badPixelPos * cs->pixelSize();
0341 
0342         for (int i = 0; i < numTestablePixels; i++) {
0343             if (i != badPixelPos) {
0344                 memcpy(srcPtr + cs->pixelSize() * i, goodSrc, cs->pixelSize());
0345                 memcpy(dstPtr + cs->pixelSize() * i, goodDst, cs->pixelSize());
0346             } else {
0347                 memcpy(srcPtr + cs->pixelSize() * i, badSrc, cs->pixelSize());
0348                 memcpy(dstPtr + cs->pixelSize() * i, badDst, cs->pixelSize());
0349             }
0350         }
0351 
0352         op->composite(dstPtr, numTestablePixels * cs->pixelSize(),
0353                       srcPtr, numTestablePixels * cs->pixelSize(),
0354                       0, 0,
0355                       1, numTestablePixels, opacity);
0356 
0357         if (memcmp(badPixelDstPtr, expectedDst, cs->pixelSize()) != 0) {
0358             qDebug() << "badPixelPos" << badPixelPos;
0359             qDebug() << "opacity" << opacity;
0360             qDebug() << "oriS" << badSrc[0] << badSrc[1] << badSrc[2] << badSrc[3];
0361             qDebug() << "oriD" << badDst[0] << badDst[1] << badDst[2] << badDst[3];
0362             qDebug() << "expD" << expectedDst[0] << expectedDst[1] << expectedDst[2] << expectedDst[3];
0363             qDebug() << "dst1" << badPixelDstPtr[0] << badPixelDstPtr[1] << badPixelDstPtr[2] << badPixelDstPtr[3];
0364 #if defined HAVE_XSIMD && !defined(XSIMD_NO_SUPPORTED_ARCHITECTURE)
0365             QFAIL("Failed to compose pixels");
0366 #else
0367             qWarning() << "Skipping failed test when xsimd library is not used";
0368 #endif
0369         }
0370     };
0371 
0372     /**
0373      * This test is supposed to catch irregularities between vector and scalar versions
0374      * of the composite op. In vector version we handle division be zero in a relaxed
0375      * way, so it some cases the content of the color channels may be different from
0376      * the one of the scalar versions of the algorithm. It is only allowed when the
0377      * pixel's alpha is null. In such case Krita considers pixel state as "undefined",
0378      * so any value is considered okay.
0379      */
0380 
0381     {
0382         quint8 goodSrc[] = {128,128,128,129};
0383         quint8 goodDst[] = {10,10,10,11};
0384         quint8 badSrc[] = {128,128,128,129};
0385         quint8 badDst[] = {12,12,12,0};
0386         quint8 *expectedDst = badSrc;
0387         testBadPixel(1, goodSrc, goodDst, badSrc, badDst, 255, expectedDst);
0388     }
0389 
0390     {
0391         quint8 goodSrc[] = {128,128,128,129};
0392         quint8 goodDst[] = {10,10,10,11};
0393         quint8 badSrc[] = {128,128,128,129};
0394         quint8 badDst[] = {12,12,12,0};
0395         quint8 *expectedDst = badSrc;
0396         testBadPixel(10, goodSrc, goodDst, badSrc, badDst, 255, expectedDst);
0397     }
0398 
0399 
0400     {
0401         quint8 goodSrc[] = {128,128,128,129};
0402         quint8 goodDst[] = {10,10,10,11};
0403         quint8 badSrc[] = {128,128,128,129};
0404         quint8 badDst[] = {12,12,12,0};
0405         quint8 expectedDst[] = {128,128,128,65};
0406         testBadPixel(1, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0407     }
0408 
0409     {
0410         quint8 goodSrc[] = {128,128,128,129};
0411         quint8 goodDst[] = {10,10,10,11};
0412         quint8 badSrc[] = {128,128,128,129};
0413         quint8 badDst[] = {12,12,12,0};
0414         quint8 expectedDst[] = {128,128,128,65};
0415         testBadPixel(10, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0416     }
0417 
0418     {
0419         quint8 goodSrc[] = {128,128,128,129};
0420         quint8 goodDst[] = {10,10,10,11};
0421         quint8 badSrc[] = {128,128,128,0};
0422         quint8 badDst[] = {12,12,12,0};
0423         quint8 expectedDst[] = {0,0,0,0}; // NOTE: the pixel has been changed, even though visually it hasn't (due to zero alpha)
0424         testBadPixel(1, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0425     }
0426 
0427     {
0428         quint8 goodSrc[] = {128,128,128,129};
0429         quint8 goodDst[] = {10,10,10,11};
0430         quint8 badSrc[] = {128,128,128,0};
0431         quint8 badDst[] = {12,12,12,0};
0432         quint8 expectedDst[] = {255,255,255,0}; // NOTE: the result is different from the scalar version, but we don't care, since alpha is still zero
0433         testBadPixel(10, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0434     }
0435 
0436     {
0437         quint8 goodSrc[] = {128,128,128,129};
0438         quint8 goodDst[] = {10,10,10,11};
0439         quint8 badSrc[] = {128,128,128,0};
0440         quint8 badDst[] = {12,12,12,127};
0441         quint8 expectedDst[] = {12,12,12,63};
0442         testBadPixel(1, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0443     }
0444 
0445     {
0446         quint8 goodSrc[] = {128,128,128,129};
0447         quint8 goodDst[] = {10,10,10,11};
0448         quint8 badSrc[] = {128,128,128,0};
0449         quint8 badDst[] = {12,12,12,127};
0450         quint8 expectedDst[] = {12,12,12,63};
0451         testBadPixel(10, goodSrc, goodDst, badSrc, badDst, 128, expectedDst);
0452     }
0453 }
0454 
0455 KISTEST_MAIN(KoRgbU8ColorSpaceTester)