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)