@@ -1,9 +1,10 @@
# include "Document/BrushStroke.h"
# include "Document/Dither.h"
# include "Document/EditorSession.h"
# include "Document/PixelAdjust.h"
# include "Document/Scanlines.h"
# include "UI/ColorPaletteControls.h"
# include "UI/FilterSheet.h"
# include <QCheck Box>
# include <QCombo Box>
# include <QLabel>
# include <QLayout>
# include <QLineEdit>
@@ -11,8 +12,11 @@
# include <QSlider>
# include <QtTest>
# include <numeric>
extern " C " {
# include "DitherPixels.h"
}
// Swift 1.4.4 's Dith er › Scanlines (CRT), on every core .
// Swift 1.4.9 's Filt er › Scanlines, its ow n filter .
namespace {
QImage filled ( int width , int height , QColor colour )
{
@@ -21,16 +25,6 @@ QImage filled(int width, int height, QColor colour)
return image ;
}
DitherSettings scanlines ( double spacing , double glow = 0 )
{
DitherSettings settings ;
settings . style = DitherStyle : : scanlines ;
settings . lineSpacing = spacing ;
settings . glow = glow ;
return settings ;
}
// One channel of one column, top to bottom.
std : : vector < int > column ( const QImage & image , int x , int channel = 0 )
{
std : : vector < int > values ;
@@ -47,35 +41,65 @@ std::vector<int> row(const QImage &image, int y, int channel = 0)
return values ;
}
// Swift's test: one column of a flat gray's render .
// Swift's render: no glow unless asked .
QImage render ( const QImage & image , const std : : function < void ( ScanlinesSettings & ) > & adjust = { } )
{
ScanlinesSettings settings ;
if ( adjust )
adjust ( settings ) ;
return settings . apply ( image ) ;
}
std : : vector < int > gray ( int level , double spacing , double glow = 0 )
{
return column ( scanlines ( spacing , glow ) . apply ( filled ( 16 , 32 , QColor ( level , level , level ) ) ) , 5 ) ;
return column ( render ( filled ( 16 , 32 , QColor ( level , level , level ) ) , [ & ] ( ScanlinesSettings & s ) { s . lineSpacing = spacing , s . glow = glow ; } ) , 5 ) ;
}
// Worst difference from Swift's first glow, blurred whole .
int glowError ( const QImage & image , double glow )
// The oracle's picture: premultiplied, some clear, some soft .
QImage pattern ( int width , int height )
{
const QImage lines = scanlines ( 8 ) . apply ( image ) , result = scanlines ( 8 , glow ) . apply ( image ) ;
const QImage blurred = PixelAdjust : : gaussianBlur ( lines , 8 * 3 + 3 , true ) ;
const float amount = float ( glow / 100 * 2.5 ) ;
int worst = 0 ;
for ( int y = 0 ; y < image . height ( ) ; + + y ) {
for ( int x = 0 ; x < image . width ( ) ; + + x ) {
const int plain = lines . constScanLine ( y ) [ x * 4 ] , light = blurred . constScanLine ( y ) [ x * 4 ] ;
const int expected = int ( std : : lround ( std : : min ( 255.0f , float ( plain ) + float ( light ) * amount ) ) ) ;
worst = std : : max ( worst , std : : abs ( result . constScanLine ( y ) [ x * 4 ] - expected ) ) ;
QImage image = BrushRaster : : context ( width , height , false ) ;
for ( int y = 0 ; y < height ; + + y ) {
uchar * out = image . scanLine ( y ) ;
for ( int x = 0 ; x < width ; + + x ) {
const unsigned alpha = ( x * 13 + y * 7 ) % 4 = = 0 ? 0 : 128 + ( x * 29 + y * 17 ) % 128 ;
for ( int c = 0 ; c < 3 ; + + c )
out [ x * 4 + c ] = uchar ( ( ( x * 37 + y * 11 + c * 53 ) % 256 ) * alpha / 255 ) ;
out [ x * 4 + 3 ] = uchar ( alpha ) ;
}
}
return worst ;
return image ;
}
std : : vector < int > repeated ( const std : : vector < int > & band , int times )
quint64 fnv ( const QImage & image )
{
std : : vector < int > all ;
for ( int each = 0 ; each < times ; + + each )
all . insert ( all . end ( ) , band . begin ( ) , band . end ( ) ) ;
return all ;
quint64 hash = 1469598103934665603ULL ;
for ( int y = 0 ; y < image . height ( ) ; + + y )
for ( int x = 0 ; x < image . width ( ) * 4 ; + + x )
hash = ( hash ^ image . constScanLine ( y ) [ x ] ) * 1099511628211ULL ;
return hash ;
}
// Swift's params: unset fields zero, white lines unless set.
ScanlinesParams made ( const std : : function < void ( ScanlinesParams & ) > & set )
{
ScanlinesParams params { } ;
params . light [ 0 ] = params . light [ 1 ] = params . light [ 2 ] = 255 ;
set ( params ) ;
return params ;
}
void rgb ( uint8_t * to , uint8_t red , uint8_t green , uint8_t blue )
{
to [ 0 ] = red , to [ 1 ] = green , to [ 2 ] = blue ;
}
QImage kernel ( int width , int height , const ScanlinesParams & params )
{
QImage image = pattern ( width , height ) ;
if ( scanlines_apply ( image . bits ( ) , size_t ( width ) , size_t ( height ) , size_t ( image . bytesPerLine ( ) ) , & params ) = = 0 )
throw std : : runtime_error ( " no memory " ) ;
return image ;
}
struct Shown {
@@ -86,15 +110,11 @@ struct Shown {
session . createDocument ( 40 , 30 ) ;
const QImage image = filled ( 40 , 30 , QColor ( 200 , 120 , 40 ) ) ;
session . insert ( ImportedImage ( image , image , QStringLiteral ( " Colour " ) ) ) ;
session . beginFilter ( FilterKind : : dither ) ;
FilterSettings settings = session . filterEdit ( ) . value ( ) . settings ;
settings . dither . style = DitherStyle : : scanlines ;
session . updateFilter ( settings , true ) ;
session . beginFilter ( FilterKind : : scanlines ) ;
sheet = std : : make_unique < FilterSheet > ( session ) ;
sheet - > show ( ) ;
}
const DitherSettings & dither ( ) const { return session . filterEdit ( ) . value ( ) . settings . dither ; }
// The shown rows in the column's order, with their help.
const ScanlinesSettings & lines ( ) const { return session . filterEdit ( ) . value ( ) . settings . scanlines ; }
QStringList rows ( ) const
{
QStringList titles ;
@@ -103,8 +123,6 @@ struct Shown {
QWidget * each = column - > itemAt ( index ) - > widget ( ) ;
if ( ! each | | each - > isHidden ( ) )
continue ;
if ( auto * box = qobject_cast < QCheckBox * > ( each ) ; box & & box - > text ( ) ! = QLatin1String ( " Preview " ) )
titles < < box - > text ( ) ;
for ( QLabel * label : each - > findChildren < QLabel * > ( QString ( ) , Qt : : FindDirectChildrenOnly ) ) {
if ( label - > buddy ( ) )
titles < < label - > text ( ) + u ' | ' + each - > toolTip ( ) ;
@@ -119,21 +137,22 @@ class ScanlinesTests : public QObject {
Q_OBJECT
private slots :
void scanlinesAreLinesOfLightThatBloomWithBrightness ( ) ;
void linesShowOnSolidWhiteAndBlack ( ) ;
void glowLightsBetweenTheLines ( ) ;
void dotsBreakTheLinesIntoBeads ( ) ;
void dotsBeadTheLinesBelowTheirLevel ( ) ;
void wobbleMovesLinesSideways ( ) ;
void theStyleDrawsAtFullResolutionWithoutMarks ( ) ;
void normalizingClampsTheFourSettings ( ) ;
void eachLineIsTheAverageOfItsRows ( ) ;
void theScreenIsTheDarkColourOrBlack ( ) ;
void displaceLiftsLinesWhereThePictureIsBright ( ) ;
void aLiftedLineHidesTheLinesBehindIt ( ) ;
void thresholdDarkensWhatsBelowIt ( ) ;
void theKernelDrawsSwiftsOwnBytes ( ) ;
void theSettingsReachTheKernel ( ) ;
void normalizingClampsEverySetting ( ) ;
void clearPixelsStayClear ( ) ;
void everyBandOfATallImageIsDrawn ( ) ;
void bandsDrawAsOneBandDoes ( ) ;
void theGlowIsTheBlurredLightAtItsAmount ( ) ;
void theGlowScalesBackExactlyOffTheStep ( ) ;
void theGlowNeverPassesAlpha ( ) ;
void theSheetShowsScanlinesRows ( ) ;
void theGlowIsTheBlurredLightEasedNearWhite ( ) ;
void theFilterIsListedAndPreviewsWhole ( ) ;
void theSheetShowsSwiftsRows ( ) ;
void theRowsWriteTheirSettings ( ) ;
void twoColorsPickTheScreenAndTheLines ( ) ;
} ;
void ScanlinesTests : : scanlinesAreLinesOfLightThatBloomWithBrightness ( )
@@ -146,9 +165,15 @@ void ScanlinesTests::scanlinesAreLinesOfLightThatBloomWithBrightness()
}
QVERIFY ( std : : accumulate ( dim . begin ( ) , dim . end ( ) , 0 ) * 2 < std : : accumulate ( white . begin ( ) , white . end ( ) , 0 ) ) ;
QVERIFY ( std : : all_of ( black . begin ( ) , black . end ( ) , [ ] ( int value ) { return value = = 0 ; } ) ) ;
// The beam's half height, 2.8 white, about 1.98 at 0.35.
QCOMPARE ( white , repeated ( { 0 , 255 , 255 , 255 , 255 , 255 , 255 , 0 } , 4 ) ) ;
QCOMPARE ( dim , repeated ( { 0 , 0 , 118 , 120 , 120 , 118 , 0 , 0 } , 4 ) ) ;
}
void ScanlinesTests : : linesShowOnSolidWhiteAndBlack ( )
{
const std : : vector < int > white = gray ( 255 , 8 , 100 ) ;
QVERIFY ( * std : : max_element ( white . begin ( ) , white . end ( ) ) - * std : : min_element ( white . begin ( ) , white . end ( ) ) > 40 ) ;
const std : : vector < int > black = column ( render ( filled ( 16 , 32 , Qt : : black ) , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . blackLevel = 40 ; } ) , 8 ) ;
const std : : vector < int > band ( black . begin ( ) , black . begin ( ) + 8 ) ;
QVERIFY ( * std : : max_element ( band . begin ( ) , band . end ( ) ) > 60 & & * std : : min_element ( band . begin ( ) , band . end ( ) ) < 10 ) ;
}
void ScanlinesTests : : glowLightsBetweenTheLines ( )
@@ -157,19 +182,14 @@ void ScanlinesTests::glowLightsBetweenTheLines()
QVERIFY2 ( glowing [ 0 ] > plain [ 0 ] + 40 , qPrintable ( QString : : number ( glowing [ 0 ] ) ) ) ;
}
void ScanlinesTests : : dotsBreakTheLinesIntoBeads ( )
void ScanlinesTests : : dotsBeadTheLinesBelowTheirLevel ( )
{
const QImage white = filled ( 64 , 16 , Qt : : white ) ;
DitherSettings settings = scanlines ( 8 ) ;
QCOMPARE ( row ( settings . apply ( white ) , 4 ) , std : : vector < int > ( 64 , 255 ) ) ;
settings . dots = 100 ;
// Each bead lit at its middle, dark at its ends.
const std : : vector < int > lit = row ( settings . apply ( white ) , 4 ) ;
QCOMPARE ( std : : vector < int > ( lit . begin ( ) , lit . begin ( ) + 8 ) , ( std : : vector < int > { 0 , 255 , 255 , 255 , 255 , 255 , 255 , 0 } ) ) ;
QCOMPARE ( std : : vector < int > ( lit . begin ( ) + 8 , lit . begin ( ) + 16 ) , ( std : : vector < int > { 0 , 255 , 255 , 255 , 255 , 255 , 255 , 0 } ) ) ;
// Half dots: the bead ends dim off the middle.
settings . dots = 50 ;
QCOMPARE ( row ( settings . apply ( white ) , 1 ) [ 0 ] , 85 ) ;
const auto middle = [ ] ( int level ) {
return row ( render ( filled ( 64 , 16 , QColor ( level , level , level ) ) , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . dots = 70 ; } ) , 4 ) ;
} ;
QCOMPARE ( middle ( 255 ) , std : : vector < int > ( 64 , 255 ) ) ;
const std : : vector < int > grey = middle ( 102 ) ;
QVERIFY ( grey [ 3 ] > 100 & & grey [ 4 ] > 100 & & grey [ 0 ] < 40 & & grey [ 8 ] < 40 ) ;
}
void ScanlinesTests : : wobbleMovesLinesSideways ( )
@@ -177,106 +197,129 @@ void ScanlinesTests::wobbleMovesLinesSideways()
QImage edge = filled ( 64 , 64 , Qt : : black ) ;
QPainter ( & edge ) . fillRect ( QRect ( 32 , 0 , 32 , 64 ) , Qt : : white ) ;
const auto edges = [ & edge ] ( double wobble ) {
DitherSettings settings = scanlines ( 8 ) ;
settings . wobble = wobble ;
const QImage result = settings . apply ( edge ) ;
std : : vector < int > found ;
const QImage result = render ( edge , [ wobble ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . wobble = wobble ; } ) ;
std : : set < int > found ;
for ( int line = 0 ; line < 8 ; + + line ) {
const std : : vector < int > middle = row ( result , line * 8 + 4 ) ;
found . push_back ( int ( std : : find_if ( middle . begin ( ) , middle . end ( ) , [ ] ( int value ) { return value > 128 ; } ) - middle . begin ( ) ) ) ;
found . insert ( int ( std : : find_if ( middle . begin ( ) , middle . end ( ) , [ ] ( int value ) { return value > 128 ; } ) - middle . begin ( ) ) ) ;
}
return found ;
} ;
QCOMPARE ( edges ( 0 ) , std : : vector < int > ( 8 , 32 ) ) ;
// Each line's shift: 12 times the two waves, rounded.
QCOMPARE ( edges ( 12 ) , ( std : : vector < int > { 35 , 36 , 35 , 41 , 44 , 37 , 32 , 34 } ) ) ;
QCOMPARE ( edges ( 0 ) , std : : set < int > { 32 } ) ;
QVERIFY ( edges ( 12 ) . size ( ) > = 3 ) ;
}
void ScanlinesTests : : theStyleDrawsAtFullResolutionWithoutMarks ( )
void ScanlinesTests : : displaceLiftsLinesWhereThePictureIsBright ( )
{
QVERIFY ( ! usesPixelSize ( DitherStyle : : scanlines ) & & ! usesPixelSize ( DitherStyle : : ascii ) & & usesPixelSize ( DitherStyle : : patterns ) ) ;
QVERIFY ( ! drawsMarks ( DitherStyle : : scanlines ) & & drawsMarks ( DitherStyle : : ascii ) ) ;
QVERIFY ( ! hasTones ( DitherStyle : : scanlines ) & & ! isHalftone ( DitherStyle : : scanlines ) & & ditherGroup ( DitherStyle : : scanlines ) = = 3 ) ;
// A pixel size changes nothing: no chunks, no dots.
const QImage image = filled ( 20 , 24 , QColor ( 180 , 90 , 30 ) ) ;
DitherSettings settings = scanlines ( 4 ) ;
const QImage plain = settings . apply ( image ) ;
settings . pixelSize = 5 ;
settings . pixelShape = DitherPixelShape : : dot ;
QCOMPARE ( settings . apply ( image ) , plain ) ;
// The cell is the line spacing, not the cell size.
settings . cellSize = 32 ;
QCOMPARE ( settings . apply ( image ) , plain ) ;
QImage half = filled ( 64 , 64 , Qt : : black ) ;
QPainter ( & half ) . fillRect ( QRect ( 32 , 0 , 32 , 64 ) , Qt : : white ) ;
const QImage result = render ( half , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 16 , s . displace = 6 , s . colors = DitherColors : : original , s . blackLevel = 50 ; } ) ;
const auto brightest = [ & result ] ( int x ) {
const std : : vector < int > values = column ( result , x ) ;
return int ( std : : max_element ( values . begin ( ) + 16 , values . begin ( ) + 32 ) - values . begin ( ) ) ;
} ;
QVERIFY ( brightest ( 48 ) < 24 ) ;
// Over black the line keeps its middle, row 24.
const std : : vector < int > still = column ( result , 8 ) ;
QVERIFY ( still [ 24 ] > 0 & & still [ 24 ] = = * std : : max_element ( still . begin ( ) + 16 , still . begin ( ) + 32 ) & & still [ 16 ] < still [ 24 ] ) ;
}
void ScanlinesTests : : normalizingClampsTheFourSettings ( )
void ScanlinesTests : : aLiftedLineHidesTheLinesBehindIt ( )
{
DitherSettings settings ;
QCOMPARE ( settings . lineSpacing , 4.0 ) ;
QCOMPARE ( settings . glow , 35.0 ) ;
QCOMPARE ( settings . dots , 0.0 ) ;
QCOMPARE ( settings . wobble , 0.0 ) ;
settings . lineSpacing = 1.4 ;
settings . glow = 150 ;
settings . dots = - 5 ;
settings . wobble = 3.5 ;
DitherSettings result = settings . normalized ( ) ;
QCOMPARE ( result . lineSpacing , 2.0 ) ;
QCOMPARE ( result . glow , 100.0 ) ;
QCOMPARE ( result . dots , 0.0 ) ;
QCOMPARE ( result . wobble , 3.5 ) ;
settings . lineSpacing = 40 ;
settings . glow = - 1 ;
settings . dots = 120 ;
settings . wobble = 70 ;
result = settings . normalized ( ) ;
QCOMPARE ( result . lineSpacing , 32.0 ) ;
QCOMPARE ( result . glow , 0.0 ) ;
QCOMPARE ( result . dots , 100.0 ) ;
QCOMPARE ( result . wobble , 64.0 ) ;
// Rounded within; not a number takes the default.
settings . lineSpacing = 5.5 ;
settings . glow = std : : nan ( " " ) ;
settings . dots = std : : nan ( " " ) ;
settings . wobble = std : : nan ( " " ) ;
result = settings . normalized ( ) ;
QCOMPARE ( result . lineSpacing , 6.0 ) ;
QCOMPARE ( result . glow , 35.0 ) ;
QCOMPARE ( result . dots , 0.0 ) ;
QCOMPARE ( result . wobble , 0.0 ) ;
settings . lineSpacing = std : : nan ( " " ) ;
QCOMPARE ( settings . normalized ( ) . lineSpacing , 4.0 ) ;
// Glow and dots keep their fractions.
settings . glow = 12.5 ;
settings . dots = 33.3 ;
QCOMPARE ( settings . normalized ( ) . glow , 12.5 ) ;
QCOMPARE ( settings . normalized ( ) . dots , 33.3 ) ;
QImage band = filled ( 64 , 80 , QColor ( 64 , 64 , 64 ) ) ;
QPainter ( & band ) . fillRect ( QRect ( 16 , 32 , 32 , 16 ) , Qt : : white ) ;
const QImage result = render ( band , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 16 , s . displace = 40 , s . smoothness = 0 ; } ) ;
QVERIFY ( result . constScanLine ( 14 ) [ 4 * 4 ] > 20 ) ;
QVERIFY ( result . constScanLine ( 14 ) [ 32 * 4 ] < 8 ) ;
QVERIFY ( result . constScanLine ( 2 ) [ 32 * 4 ] > 200 ) ;
}
void ScanlinesTests : : eachLineIsTheAverageOfItsRows ( )
void ScanlinesTests : : thresholdDarkensWhatsBelowIt ( )
{
// Rows of white and black: each line lights at half.
QImage stripes = filled ( 8 , 16 , Qt : : black ) ;
for ( int y = 0 ; y < 16 ; y + = 2 )
QPainter ( & stripes ) . fillRect ( QRect ( 0 , y , 8 , 1 ) , Qt : : white ) ;
QCOMPARE ( column ( scanlines ( 8 ) . apply ( stripes ) , 3 ) , repeated ( { 0 , 37 , 172 , 172 , 172 , 172 , 37 , 0 } , 2 ) ) ;
const QImage grey = filled ( 32 , 32 , QColor ( 77 , 77 , 77 ) ) ;
const std : : vector < int > shown = column ( render ( grey , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 ; } ) , 16 ) ;
const std : : vector < int > hidden = column ( render ( grey , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . threshold = 60 ; } ) , 16 ) ;
QVERIFY ( std : : any_of ( shown . begin ( ) , shown . end ( ) , [ ] ( int value ) { return value > 40 ; } ) ) ;
QVERIFY ( std : : none_of ( hidden . begin ( ) , hidden . end ( ) , [ ] ( int value ) { return value > 10 ; } ) ) ;
}
void ScanlinesTests : : theScreenIsTheDarkColourOrBlack ( )
// Swift's GCD kernel, built with clang's blocks, gave these.
void ScanlinesTests : : theKernelDrawsSwiftsOwnBytes ( )
{
const QImage image = filled ( 8 , 8 , QColor ( 255 , 128 , 0 ) ) ;
DitherSettings settings = scanlines ( 8 ) ;
settings . colors = DitherColors : : twoColors ;
settings . dark = AdjustmentColor ( 0 , 0 , 1 ) ;
settings . light = AdjustmentColor ( 1 , 1 , 0 ) ;
const QImage two = settings . apply ( image ) ;
// Off the line the dark colour; on it, the mix.
QCOMPARE ( two . pixelColor ( 2 , 0 ) , QColor ( 0 , 0 , 255 ) ) ;
QCOMPARE ( two . pixelColor ( 2 , 4 ) , QColor ( 197 , 197 , 147 ) ) ;
settings . colors = DitherColors : : original ;
const QImage original = settings . apply ( image ) ;
QCOMPARE ( original . pixelColor ( 2 , 0 ) , QColor ( 0 , 0 , 0 ) ) ;
QCOMPARE ( original . pixelColor ( 2 , 4 ) , QColor ( 255 , 173 , 0 ) ) ;
const QImage a = kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 4 ; p . thickness = 0.7f ; p . displace = 20 ; p . smoothness = 0.5f ; p . originalColors = 1 ; } ) ) ;
QCOMPARE ( fnv ( a ) , 14473728681183016108ULL ) ;
QCOMPARE ( QByteArray ( reinterpret_cast < const char * > ( a . constScanLine ( 2 ) ) + 4 , 4 ) , QByteArray ( " \xbd \xbf \x25 \xbf " , 4 ) ) ;
const QImage b = kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 6 ; p . thickness = 0.9f ; p . dots = 0.6f ; p . wobble = 6 ; p . displace = - 15 ; p . threshold = 0.3f ; p . split = 3 ; p . density = 0.2f ; p . contrast = - 0.3f ; p . blackLevel = 0.25f ; p . smoothness = 0.8f ; } ) ) ;
QCOMPARE ( fnv ( b ) , 3381563246094821038ULL ) ;
const QImage c = kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 5 ; p . thickness = 0.4f ; p . contrast = 0.6f ; p . smoothness = 0.5f ; rgb ( p . dark , 10 , 20 , 200 ) ; rgb ( p . light , 250 , 200 , 10 ) ; } ) ) ;
QCOMPARE ( fnv ( c ) , 11894396318800434257ULL ) ;
QCOMPARE ( QByteArray ( reinterpret_cast < const char * > ( c . constScanLine ( 2 ) ) + 4 , 4 ) , QByteArray ( " \x55 \x4d \x8f \xbf " , 4 ) ) ;
// Wide and tall: 32 bands of columns, lines and rows.
QCOMPARE ( fnv ( kernel ( 70 , 300 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 3 ; p . thickness = 0.7f ; p . dots = 0.3f ; p . displace = 30 ; p . split = 2 ; p . blackLevel = 0.1f ; p . smoothness = 1 ; p . originalColors = 1 ; } ) ) ) ,
5889846669877741430ULL ) ;
QCOMPARE ( fnv ( kernel ( 70 , 300 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 2 ; p . thickness = 1 ; p . wobble = 9 ; p . displace = - 40 ; p . threshold = 0.5f ; p . smoothness = 0.25f ; } ) ) ) ,
1843572660750656508ULL ) ;
// The glow eases near full brightness.
QImage lit = pattern ( 41 , 29 ) , glow = pattern ( 41 , 29 ) ;
for ( int y = 0 ; y < 29 ; + + y )
for ( int x = 0 ; x < 41 * 4 ; + + x )
glow . scanLine ( y ) [ x ] = uchar ( 255 - glow . constScanLine ( y ) [ x ] ) ;
dither_glow ( lit . bits ( ) , glow . constBits ( ) , 41 , 29 , size_t ( lit . bytesPerLine ( ) ) , 1.3f ) ;
QCOMPARE ( fnv ( lit ) , 14026896863046063157ULL ) ;
}
// Each setting reaches its own field, scaled as Swift's.
void ScanlinesTests : : theSettingsReachTheKernel ( )
{
ScanlinesSettings settings ;
settings . lineSpacing = 6 , settings . thickness = 90 , settings . dots = 60 , settings . wobble = 6 , settings . displace = - 15 , settings . threshold = 30 ;
settings . split = 3 , settings . density = 20 , settings . contrast = - 30 , settings . blackLevel = 25 , settings . smoothness = 80 ;
QCOMPARE ( settings . apply ( pattern ( 37 , 53 ) ) , kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 6 ; p . thickness = 0.9f ; p . dots = 0.6f ; p . wobble = 6 ; p . displace = - 15 ; p . threshold = 0.3f ; p . split = 3 ; p . density = 0.2f ; p . contrast = - 0.3f ; p . blackLevel = 0.25f ; p . smoothness = 0.8f ; } ) ) ) ;
settings = ScanlinesSettings ( ) ;
settings . lineSpacing = 5 , settings . thickness = 40 , settings . contrast = 60 , settings . colors = DitherColors : : twoColors ;
settings . dark = AdjustmentColor ( 10 / 255.0 , 20 / 255.0 , 200 / 255.0 ) , settings . light = AdjustmentColor ( 250 / 255.0 , 200 / 255.0 , 10 / 255.0 ) ;
QCOMPARE ( settings . apply ( pattern ( 37 , 53 ) ) , kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 5 ; p . thickness = 0.4f ; p . contrast = 0.6f ; p . smoothness = 0.5f ; rgb ( p . dark , 10 , 20 , 200 ) ; rgb ( p . light , 250 , 200 , 10 ) ; } ) ) ) ;
// Picked colours count only for Two Colors.
settings . colors = DitherColors : : blackWhite ;
QCOMPARE ( settings . apply ( pattern ( 37 , 53 ) ) , kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 5 ; p . thickness = 0.4f ; p . contrast = 0.6f ; p . smoothness = 0.5f ; } ) ) ) ;
settings . colors = DitherColors : : original , settings . displace = 20 , settings . lineSpacing = 4 , settings . thickness = 70 , settings . contrast = 0 ;
QCOMPARE ( fnv ( settings . apply ( pattern ( 37 , 53 ) ) ) , 14473728681183016108ULL ) ;
}
void ScanlinesTests : : normalizingClampsEverySetting ( )
{
const ScanlinesSettings defaults ;
QVERIFY ( defaults . lineSpacing = = 4 & & defaults . thickness = = 70 & & defaults . glow = = 0 & & defaults . dots = = 0 & & defaults . wobble = = 0 ) ;
QVERIFY ( defaults . displace = = 0 & & defaults . smoothness = = 50 & & defaults . threshold = = 0 & & defaults . split = = 0 & & defaults . blackLevel = = 0 ) ;
QVERIFY ( defaults . colors = = DitherColors : : blackWhite & & defaults . dark = = AdjustmentColor ( 0 , 0 , 0 ) & & defaults . light = = AdjustmentColor ( 1 , 1 , 1 ) ) ;
using Field = double ScanlinesSettings : : * ;
// Below, above, a fraction, not a number.
const std : : tuple < Field , double , double , double , double , double > fields [ ] = {
{ & ScanlinesSettings : : lineSpacing , 2 , 32 , 5.5 , 6 , 4 } , { & ScanlinesSettings : : thickness , 5 , 100 , 33.3 , 33.3 , 70 } ,
{ & ScanlinesSettings : : glow , 0 , 100 , 12.5 , 12.5 , 0 } , { & ScanlinesSettings : : dots , 0 , 100 , 33.3 , 33.3 , 0 } ,
{ & ScanlinesSettings : : wobble , 0 , 64 , 3.5 , 3.5 , 0 } , { & ScanlinesSettings : : displace , - 100 , 100 , - 7.5 , - 7.5 , 0 } ,
{ & ScanlinesSettings : : smoothness , 0 , 100 , 12.5 , 12.5 , 50 } , { & ScanlinesSettings : : threshold , 0 , 100 , 12.5 , 12.5 , 0 } ,
{ & ScanlinesSettings : : split , 0 , 16 , 2.5 , 3 , 0 } , { & ScanlinesSettings : : density , - 100 , 100 , 12.5 , 12.5 , 0 } ,
{ & ScanlinesSettings : : contrast , - 100 , 100 , - 12.5 , - 12.5 , 0 } , { & ScanlinesSettings : : blackLevel , 0 , 100 , 12.5 , 12.5 , 0 } } ;
for ( const auto & [ field , low , high , typed , kept , fallback ] : fields ) {
ScanlinesSettings settings ;
settings . * field = low - 1000 ;
QCOMPARE ( settings . normalized ( ) . * field , low ) ;
settings . * field = high + 1000 ;
QCOMPARE ( settings . normalized ( ) . * field , high ) ;
settings . * field = typed ;
QCOMPARE ( settings . normalized ( ) . * field , kept ) ;
settings . * field = std : : nan ( " " ) ;
QCOMPARE ( settings . normalized ( ) . * field , fallback ) ;
}
// Applying normalizes first: past the ends draws as the ends.
ScanlinesSettings wild ;
wild . lineSpacing = 5.6 , wild . thickness = 500 , wild . split = 2.4 , wild . displace = 300 ;
QCOMPARE ( wild . apply ( pattern ( 37 , 53 ) ) , wild . normalized ( ) . apply ( pattern ( 37 , 53 ) ) ) ;
QCOMPARE ( fnv ( wild . apply ( pattern ( 37 , 53 ) ) ) , fnv ( kernel ( 37 , 53 , made ( [ ] ( ScanlinesParams & p ) { p . spacing = 6 ; p . thickness = 1 ; p . split = 2 ; p . displace = 100 ; p . smoothness = 0.5f ; } ) ) ) ) ;
ScanlinesSettings colours ;
colours . dark = AdjustmentColor ( - 1 , 2 , 0.5 ) , colours . light = AdjustmentColor ( 3 , - 2 , 0.25 ) ;
QVERIFY ( colours . normalized ( ) . dark = = AdjustmentColor ( 0 , 1 , 0.5 ) & & colours . normalized ( ) . light = = AdjustmentColor ( 1 , 0 , 0.25 ) ) ;
}
void ScanlinesTests : : clearPixelsStayClear ( )
@@ -286,139 +329,159 @@ void ScanlinesTests::clearPixelsStayClear()
painter . setCompositionMode ( QPainter : : CompositionMode_Source ) ;
painter . fillRect ( QRect ( 0 , 0 , 8 , 8 ) , Qt : : transparent ) ;
painter . end ( ) ;
const QImage result = scanlines ( 8 , 100 ) . apply ( image ) ;
const QImage result = render ( image , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . glow = 100 ; } ) ;
QCOMPARE ( column ( result , 2 , 3 ) , ( std : : vector < int > { 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 255 , 255 , 255 , 255 , 255 , 255 , 255 , 255 } ) ) ;
QCOMPARE ( column ( result , 2 , 0 ) [ 4 ] , 0 ) ;
}
void ScanlinesTests : : everyBandOfATallImageIsDrawn ( )
{
// 150 two-row lines: 32 bands of 5, two empty.
const QImage image = filled ( 3 , 300 , Qt : : white ) ;
QCOMPARE ( column ( scanlines ( 2 ) . apply ( image ) , 1 ) , repeated ( { 241 , 241 } , 150 ) ) ;
// 70 lines of 8: bands of 3, the last one.
const std : : vector < int > tall = column ( scanlines ( 8 ) . apply ( filled ( 3 , 560 , Qt : : white ) ) , 1 ) ;
QCOMPARE ( tall , repeated ( { 0 , 255 , 255 , 255 , 255 , 255 , 255 , 0 } , 70 ) ) ;
}
void ScanlinesTests : : bandsDrawAsOneBandDoes ( )
{
// One band of 62 rows, 32 of 300: tops agree.
QImage noise = BrushRaster : : context ( 7 , 300 , false ) ;
for ( int y = 0 ; y < 300 ; + + y )
for ( int x = 0 ; x < 7 ; + + x )
noise . setPixelColor ( x , y , QColor ( ( x * 37 + y * 11 ) % 256 , ( x * 91 + y * 53 ) % 256 , ( y * 29 ) % 256 , 128 + ( x + y ) % 128 ) ) ;
DitherSettings settings = scanlines ( 2 ) ;
settings . colors = DitherColors : : original ;
settings . dots = 50 ;
settings . wobble = 5 ;
const QImage whole = settings . apply ( noise ) , one = settings . apply ( noise . copy ( 0 , 0 , 7 , 62 ) ) ;
QCOMPARE ( whole . copy ( 0 , 0 , 7 , 62 ) , one ) ;
}
void ScanlinesTests : : theGlowIsTheBlurredLightAtItsAmount ( )
// Swift's Core Image blur: the twin's within a few levels.
void ScanlinesTests : : theGlowIsTheBlurredLightEasedNearWhite ( )
{
QImage image = filled ( 64 , 160 , Qt : : black ) ;
QPainter ( & image ) . fillRect ( QRect ( 0 , 0 , 32 , 40 ) , Qt : : white ) ;
for ( const double glow : { 20.0 , 100.0 } ) {
// Three levels of the shrink's blur, times 2.5.
const int worst = glowError ( image , glow ) ;
const QImage lines = render ( image , [ ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 ; } ) ;
const QImage result = render ( image , [ glow ] ( ScanlinesSettings & s ) { s . lineSpacing = 8 , s . glow = glow ; } ) ;
const QImage blurred = PixelAdjust : : gaussianBlur ( lines , 8 * 3 + 3 , true ) ;
const float amount = float ( glow / 100 * 2.5 ) ;
int worst = 0 ;
for ( int y = 0 ; y < image . height ( ) ; + + y ) {
for ( int x = 0 ; x < image . width ( ) ; + + x ) {
float value = lines . constScanLine ( y ) [ x * 4 ] ;
const float added = blurred . constScanLine ( y ) [ x * 4 ] * amount , room = ( 255 - value ) * 0.7f ;
if ( room > 0 )
value + = room * ( 1 - std : : exp ( - added / room ) ) ;
worst = std : : max ( worst , std : : abs ( result . constScanLine ( y ) [ x * 4 ] - int ( std : : lround ( std : : min ( 255.0f , value ) ) ) ) ) ;
}
}
QVERIFY2 ( worst < = 8 , qPrintable ( QString : : number ( worst ) ) ) ;
}
// Below the light, at amount 0.5, it fades.
const std : : vector < int > fading = column ( scanlines ( 8 , 20 ) . apply ( image ) , 16 ) ;
QVERIFY ( fading [ 48 ] > 20 & & fading [ 48 ] < fading [ 40 ] & & fading [ 120 ] < 3 ) ;
}
void ScanlinesTests : : theGlowScalesBackExactlyOffTheStep ( )
void ScanlinesTests : : theFilterIsListedAndPreviewsWhole ( )
{
// 73 plus two 84-point pads is off the 6 step.
QImage image = filled ( 73 , 73 , Qt : : black ) ;
QPainter ( & image ) . fillRect ( QRect ( 0 , 0 , 40 , 40 ) , Qt : : white ) ;
const int worst = glowError ( image , 100 ) ;
QVERIFY2 ( worst < = 8 , qPrintable ( QString : : number ( worst ) ) ) ;
QCOMPARE ( rawValue ( FilterKind : : scanlines ) , QString ( " Scanlines " ) ) ;
const auto at = std : : find ( allFilterKinds . begin ( ) , allFilterKinds . end ( ) , FilterKind : : scanlines ) ;
QVERIFY ( at ! = allFilterKinds . end ( ) & & * ( at - 1 ) = = FilterKind : : dither & & * ( at + 1 ) = = FilterKind : : tonalContrast ) ;
QVERIFY ( ! isImageAdjustment ( FilterKind : : scanlines ) & & ! isAutomatic ( FilterKind : : scanlines ) & & repeatsAsLastFilter ( FilterKind : : scanlines ) ) ;
EditorSession session ;
session . createDocument ( 3000 , 20 ) ;
const QImage wide = filled ( 3000 , 20 , Qt : : white ) ;
session . insert ( ImportedImage ( wide , wide , QStringLiteral ( " Wide " ) ) ) ;
session . beginFilter ( FilterKind : : scanlines ) ;
QCOMPARE ( session . filterEdit ( ) . value ( ) . previewScale , 1.0 ) ;
// Its commit is one step that draws the lines.
QVERIFY ( session . filterEdit ( ) . value ( ) . settings . scanlines = = ScanlinesSettings ( ) ) ;
bool done = false ;
session . commitFilter ( [ & done ] { done = true ; } ) ;
QTRY_VERIFY ( done ) ;
QCOMPARE ( session . history . undoName ( ) , QString ( " Scanlines " ) ) ;
const QImage drawn = session . document ( ) . value ( ) . layers . front ( ) . asset . value ( ) . image ( ) ;
// Beam 1.4 round the middle 2: row 0 covers 0.4.
QCOMPARE ( column ( drawn , 5 ) [ 0 ] , int ( std : : lround ( 255 * 1.35f * 0.4f ) ) ) ;
QCOMPARE ( column ( drawn , 5 ) [ 2 ] , 255 ) ;
}
void ScanlinesTests : : theGlowNeverPassesAlpha ( )
{
QImage image = BrushRaster : : context ( 16 , 32 , false ) ;
image . fill ( QColor ( 255 , 255 , 255 , 128 ) ) ;
const QImage result = scanlines ( 4 , 100 ) . apply ( image ) ;
for ( int y = 0 ; y < 32 ; + + y ) {
for ( int x = 0 ; x < 16 ; + + x ) {
const uchar * at = result . constScanLine ( y ) + x * 4 ;
QCOMPARE ( int ( at [ 3 ] ) , 128 ) ;
QVERIFY ( at [ 0 ] < = 128 & & at [ 1 ] < = 128 & & at [ 2 ] < = 128 ) ;
}
}
QCOMPARE ( int ( result . constScanLine ( 0 ) [ 0 ] ) , 128 ) ;
}
void ScanlinesTests : : theSheetShowsScanlinesRows ( )
void ScanlinesTests : : theSheetShowsSwiftsRows ( )
{
Shown shown ;
QCOMPARE ( shown . rows ( ) , ( QStringList { " Style| " , " Line Spacing|How far apart the screen's lines are " ,
" Glow|Light blooming around the lines, like a CRT's phosphors " , " Dots|Break the lines into glowing beads " ,
" Wobble|Make the lines waver sideways down the screen, like a CRT losing sync " ,
" Density|More ink (darker) or less befo re d ithering " , " Contrast| " , " Colors| " } ) ) ;
// A value mid-range sits mid-travel; each unit is Swift's.
const std : : tuple < const char * , double DitherSettings : : * , double , QString > middles [ ] = {
{ " lineSpacing " , & DitherSettings : : lineSpacing , 17 , " px " } , { " glow " , & DitherSettings : : glow , 50 , " % " } ,
{ " dots " , & DitherSettings : : dots , 50 , " % " } , { " wobble " , & DitherSettings : : wobble , 32 , " px " } } ;
for ( const auto & [ name , setting , middle , unit ] : middles ) {
FilterSettings settings = shown . session . filterEdit ( ) . value ( ) . settings ;
settings . dither . * setting = middle ;
shown . session . updateFilter ( settings , true ) ;
QCOMPARE ( shown . rows ( ) ,
( QStringList { " Line Spacing|How far apart the screen's lines are " , " Thickness|How much of the gap a bright line fills; dimmer parts draw it thinner " ,
" Glow|Light blooming around the lines, like a CRT's phosphors " ,
" Dots|Break the lines into beads where the picture is darker than this, blending into solid lines whe re it's brighter " ,
" Displace|Lift the lines where the picture is bright, or lower them when negative, so they swell into its shapes " ,
" Smoothness|How far the brightness is smoothed before it displaces the lines, from sharp ridges to rounded hills " ,
" Threshold|Draw no line where the picture is darker than this, leaving the screen dark " ,
" Wobble|Make the lines waver sideways down the screen, like a CRT losing sync " ,
" Color Split|Move the red and blue apart, for colored fringes on the lines " , " Density|Darker or lighter before the lines are drawn " ,
" Contrast| " , " Black Level|How bright the lines are where the picture is black, so they show even over black " , " Colors| " } ) ) ;
using Field = double ScanlinesSettings : : * ;
const std : : tuple < const char * , Field , double , double , QString > sliders [ ] = {
{ " lineSpacing " , & ScanlinesSettings : : lineSpacing , 2 , 32 , " px " } , { " thickness " , & ScanlinesSettings : : thickness , 5 , 100 , " % " } ,
{ " glow " , & ScanlinesSettings : : glow , 0 , 100 , " % " } , { " dots " , & ScanlinesSettings : : dots , 0 , 100 , " % " } ,
{ " displace " , & ScanlinesSettings : : displace , - 100 , 100 , " px " } , { " smoothness " , & ScanlinesSettings : : smoothness , 0 , 100 , " % " } ,
{ " threshold " , & ScanlinesSettings : : threshold , 0 , 100 , " % " } , { " wobble " , & ScanlinesSettings : : wobble , 0 , 64 , " px " } ,
{ " colorSplit " , & ScanlinesSettings : : split , 0 , 16 , " px " } , { " density " , & ScanlinesSettings : : density , - 100 , 100 , " " } ,
{ " contrast " , & ScanlinesSettings : : contrast , - 100 , 100 , " " } , { " blackLevel " , & ScanlinesSettings : : blackLevel , 0 , 100 , " % " } } ;
for ( const auto & [ name , field , low , high , unit ] : sliders ) {
QSlider * slider = shown . sheet - > findChild < QSlider * > ( QString : : fromLatin1 ( name ) + " Slider " ) ;
QLineEdit * field = shown . sheet - > findChild < QLineEdit * > ( QString : : fromLatin1 ( name ) + " Field " ) ;
QVERIFY2 ( slider & & field , name ) ;
QCOMPARE ( slider - > value ( ) , slider - > maximum ( ) / 2 ) ;
QCOMPARE ( slider - > minimum ( ) , 0 ) ;
const QList < QLabel * > labels = field - > parentWidget ( ) - > findChildren < QLabel * > ( QString ( ) , Qt : : FindDirectChildrenOnly ) ;
QCOMPARE ( labels . last ( ) - > text ( ) , unit ) ;
}
// Each slider's travel reaches its range's ends.
const std : : tuple < const char * , double DitherSettings : : * , double , double > sliders [ ] = {
{ " lineSpacingSlider " , & DitherSettings : : lineSpacing , 2 , 32 } , { " glowSlider " , & DitherSettings : : glow , 0 , 100 } ,
{ " dotsSlider " , & DitherSettings : : dots , 0 , 100 } , { " wobbleSlider " , & DitherSettings : : wobble , 0 , 64 } } ;
for ( const auto & [ name , setting , low , high ] : sliders ) {
QSlider * slider = shown . sheet - > findChild < QSlider * > ( QString : : fromLatin1 ( name ) ) ;
QVERIFY2 ( slider , name ) ;
QLineEdit * edit = shown . sheet - > findChild < QLineEdit * > ( QString : : fromLatin1 ( name ) + " Field " ) ;
QVERIFY2 ( slider & & edit , name ) ;
slider - > setValue ( slider - > maximum ( ) ) ;
QCOMPARE ( shown . dither ( ) . * setting , high ) ;
QCOMPARE ( shown . lines ( ) . * field , high ) ;
slider - > setValue ( slider - > minimum ( ) ) ;
QCOMPARE ( shown . dither ( ) . * setting , low ) ;
QCOMPARE ( shown . lines ( ) . * field , low ) ;
QCOMPARE ( edit - > parentWidget ( ) - > findChildren < QLabel * > ( QString ( ) , Qt : : FindDirectChildrenOnly ) . last ( ) - > text ( ) , unit ) ;
}
// Pixel Size and Pixel Shape stay away under Scanlines.
FilterSettings settings = shown . session . filterEdit ( ) . value ( ) . settings ;
settings . dither . pixelSize = 4 ;
shown . session . updateFilter ( settings , true ) ;
QVERIFY ( ! shown . rows ( ) . join ( ' , ' ) . contains ( " Pixel " ) ) ;
settings . dither . style = DitherStyle : : bayer4 ;
shown . session . updateFilter ( settings , true ) ;
QVERIFY ( shown . rows ( ) . contains ( " Pixel Shape|Draw each chunky pixel as a solid square, or as a round dot like a dot-matrix screen " ) ) ;
QVERIFY ( ! shown . rows ( ) . join ( ' , ' ) . contains ( " Glow " ) ) ;
}
void ScanlinesTests : : theRowsWriteTheirSettings ( )
{
Shown shown ;
const std : : tuple < const char * , double DitherSettings : : * , const char * , double > rows [ ] = {
{ " lineSpacingField " , & DitherSettings : : lineSpacing , " 12 " , 12 } , { " glowField " , & DitherSettings : : glow , " 60 " , 60 } ,
{ " dotsField " , & DitherSettings : : dots , " 25 " , 25 } , { " wobbleField " , & DitherSettings : : wobble , " 9 " , 9 } } ;
for ( const auto & [ name , setting , typed , expected ] : rows ) {
const DitherSettings before = shown . dither ( ) ;
QLineEdit * field = shown . sheet - > findChild < QLineEdit * > ( QString : : fromLatin1 ( name ) ) ;
QVERIFY2 ( field , name ) ;
field - > setFocus ( ) ;
field - > selectAll ( ) ;
QTest : : keyClicks ( field , QString : : fromLatin1 ( typed ) ) ;
QTest : : keyClick ( field , Qt : : Key_Return ) ;
DitherSettings wanted = before ;
wanted . * setting = expected ;
QVERIFY2 ( shown . dither ( ) = = wanted , name ) ;
const std : : tuple < const char * , double ScanlinesSettings : : * , const char * , double > rows [ ] = {
{ " lineSpacingField " , & ScanlinesSettings : : lineSpacing , " 12 " , 12 } , { " displaceField " , & ScanlinesSettings : : displace , " -30 " , - 30 } ,
{ " colorSplitField " , & ScanlinesSettings : : split , " 5 " , 5 } , { " blackLevelField " , & ScanlinesSettings : : blackLevel , " 40 " , 40 } ,
{ " smoothnessField " , & ScanlinesSettings : : smoothness , " 10 " , 10 } , { " thicknessField " , & ScanlinesSettings : : thickness , " 55 " , 55 } } ;
for ( const auto & [ name , field , typed , expected ] : rows ) {
ScanlinesSettings wanted = shown . lines ( ) ;
QLineEdit * edit = shown . sheet - > findChild < QLineEdit * > ( QString : : fromLatin1 ( name ) ) ;
QVERIFY2 ( edit , name ) ;
edit - > setFocus ( ) ;
edit - > selectAll ( ) ;
QTest : : keyClicks ( edit , QString : : fromLatin1 ( typed ) ) ;
QTest : : keyClick ( edit , Qt : : Key_Return ) ;
wanted . * field = expected ;
QVERIFY2 ( shown . lines ( ) = = wanted , name ) ;
}
}
void ScanlinesTests : : twoColorsPickTheScreenAndTheLines ( )
{
Shown shown ;
auto * colors = shown . sheet - > findChild < QComboBox * > ( QStringLiteral ( " scanlinesColors " ) ) ;
auto * dark = shown . sheet - > findChild < SwatchButton * > ( QStringLiteral ( " scanlinesDark " ) ) ;
auto * light = shown . sheet - > findChild < SwatchButton * > ( QStringLiteral ( " scanlinesLight " ) ) ;
QVERIFY ( colors & & dark & & light & & ! dark - > isVisible ( ) ) ;
QVERIFY ( dark - > toolTip ( ) = = QString ( " Choose the screen's color " ) & & light - > toolTip ( ) = = QString ( " Choose the lines' color " ) ) ;
colors - > activated ( int ( DitherColors : : twoColors ) ) ;
QVERIFY ( shown . lines ( ) . colors = = DitherColors : : twoColors & & dark - > isVisible ( ) & & light - > isVisible ( ) ) ;
// The menu follows the settings, however they change.
FilterSettings original = shown . session . filterEdit ( ) . value ( ) . settings ;
original . scanlines . colors = DitherColors : : original ;
shown . session . updateFilter ( original , true ) ;
QVERIFY ( colors - > currentIndex ( ) = = int ( DitherColors : : original ) & & ! dark - > isVisible ( ) ) ;
original . scanlines . colors = DitherColors : : twoColors ;
shown . session . updateFilter ( original , true ) ;
QCOMPARE ( colors - > currentIndex ( ) , int ( DitherColors : : twoColors ) ) ;
// The picker starts from the lines' own colour.
FilterSettings settings = shown . session . filterEdit ( ) . value ( ) . settings ;
settings . scanlines . light = AdjustmentColor ( 0 , 1 , 0 ) , settings . scanlines . dark = AdjustmentColor ( 0.2 , 0 , 0 ) ;
shown . session . updateFilter ( settings , true ) ;
light - > click ( ) ;
QCOMPARE ( shown . session . colorPicker ( ) . value ( ) . target . title ( ) , QString ( " Color Picker (Light Color) " ) ) ;
QVERIFY ( shown . session . colorPicker ( ) . value ( ) . original = = ( PaletteColor { 0 , 1 , 0 } ) ) ;
struct Paints : QObject {
int count = 0 ;
bool eventFilter ( QObject * , QEvent * event ) override { return count + = event - > type ( ) = = QEvent : : Paint , false ; }
} paints ;
QCoreApplication : : processEvents ( ) ;
light - > installEventFilter ( & paints ) ;
shown . session . setColorPickerHSB ( PickerHSB ( PaletteColor { 1 , 0 , 0 } ) ) ;
shown . session . previewDitherColor ( ) ;
// A new colour repaints the swatch.
QTRY_VERIFY ( paints . count > 0 ) ;
QVERIFY ( shown . lines ( ) . light = = AdjustmentColor ( 1 , 0 , 0 ) ) ;
QVERIFY ( shown . session . filterEdit ( ) . value ( ) . settings . dither . light = = AdjustmentColor ( 1 , 1 , 1 ) ) ;
QCOMPARE ( light - > grab ( ) . toImage ( ) . pixelColor ( 12 , 12 ) , QColor ( 255 , 0 , 0 ) ) ;
shown . session . closeColorPicker ( false ) ;
QVERIFY ( shown . lines ( ) . light = = AdjustmentColor ( 0 , 1 , 0 ) ) ;
dark - > click ( ) ;
QCOMPARE ( shown . session . colorPicker ( ) . value ( ) . target . title ( ) , QString ( " Color Picker (Dark Color) " ) ) ;
QVERIFY ( shown . session . colorPicker ( ) . value ( ) . original = = ( PaletteColor { 0.2 , 0 , 0 } ) ) ;
shown . session . setColorPickerHSB ( PickerHSB ( PaletteColor { 0 , 0 , 1 } ) ) ;
shown . session . closeColorPicker ( true ) ;
QVERIFY ( shown . lines ( ) . dark = = AdjustmentColor ( 0 , 0 , 1 ) ) ;
}
QTEST_MAIN ( ScanlinesTests )
# include "ScanlinesTests.moc"