Skip to content

Commit

Permalink
Move generation of default symbol random colors to
Browse files Browse the repository at this point in the history
QgsColorSchemeRegistry

and add API to set a QgsColorScheme from which to pull
colors when creating a random symbol color.
  • Loading branch information
nyalldawson committed Mar 12, 2018
1 parent 40ceb7b commit bc449c4
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 8 deletions.
57 changes: 57 additions & 0 deletions python/core/qgscolorschemeregistry.sip.in
Expand Up @@ -104,6 +104,63 @@ Returns all color schemes in the registry which have a specified flag set



void setRandomStyleColorScheme( QgsColorScheme *scheme );
%Docstring
Sets the color ``scheme`` to use when fetching random colors to use for symbol styles.

``scheme`` should match a color scheme which is already present in the registry.

Note that calling this method takes a snapshot of the colors from the scheme's
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
in ``scheme`` are not automatically reflected by calls to fetchRandomStyleColor().
If ``scheme`` is updated, then another call to setRandomStyleColorScheme() must
be made in order to update the cached list of available style colors.

.. seealso:: :py:func:`randomStyleColorScheme`

.. seealso:: :py:func:`fetchRandomStyleColor`

.. versionadded:: 3.2
%End

QgsColorScheme *randomStyleColorScheme();
%Docstring
Returns the color scheme used when fetching random colors to use for symbol styles.

This may be None, in which case totally random colors are used for styles.

.. seealso:: :py:func:`setRandomStyleColorScheme`

.. seealso:: :py:func:`fetchRandomStyleColor`

.. versionadded:: 3.2
%End

QColor fetchRandomStyleColor() const;
%Docstring
Returns a random color for use with a new symbol style (e.g. for a newly created
map layer).

If a randomStyleColorScheme() is set then this color will be randomly taken from that
color scheme. If no randomStyleColorScheme() is set then a totally random color
will be generated.

Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
If the scheme is updated, then another call to setRandomStyleColorScheme() must
be made in order to update the cached list of available style colors from which
fetchRandomStyleColor() selects colors.

This method is thread safe.

.. seealso:: :py:func:`randomStyleColorScheme`

.. seealso:: :py:func:`setRandomStyleColorScheme`

.. versionadded:: 3.2
%End

};


Expand Down
62 changes: 61 additions & 1 deletion src/core/qgscolorschemeregistry.cpp
Expand Up @@ -20,7 +20,8 @@
#include "qgsapplication.h"
#include <QDir>
#include <QFileInfoList>

#include <QMutex>
#include <random>

QgsColorSchemeRegistry::~QgsColorSchemeRegistry()
{
Expand Down Expand Up @@ -98,8 +99,67 @@ QList<QgsColorScheme *> QgsColorSchemeRegistry::schemes( const QgsColorScheme::S
return matchingSchemes;
}

void QgsColorSchemeRegistry::setRandomStyleColorScheme( QgsColorScheme *scheme )
{
mRandomStyleColorScheme = scheme;
if ( scheme )
{
mRandomStyleColors = scheme->fetchColors();

std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> colorDist( 0, mRandomStyleColors.count() - 1 );
mNextRandomStyleColorIndex = colorDist( mt );
std::uniform_int_distribution<int> colorDir( 0, 1 );
mNextRandomStyleColorDirection = colorDir( mt ) == 0 ? -1 : 1;
}
else
{
mRandomStyleColors.clear();
}
}

QgsColorScheme *QgsColorSchemeRegistry::randomStyleColorScheme()
{
return mRandomStyleColorScheme;
}

QColor QgsColorSchemeRegistry::fetchRandomStyleColor() const
{
if ( mRandomStyleColors.empty() )
{
// no random color scheme available - so just use totally random colors

// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> hueDist( 0, 359 );
std::uniform_int_distribution<int> satDist( 64, 255 );
std::uniform_int_distribution<int> valueDist( 128, 255 );
return QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) );
}
else
{
static QMutex sMutex;
QMutexLocker locker( &sMutex );
QColor res = mRandomStyleColors.at( mNextRandomStyleColorIndex ).first;
mNextRandomStyleColorIndex += mNextRandomStyleColorDirection;
if ( mNextRandomStyleColorIndex < 0 )
mNextRandomStyleColorIndex = mRandomStyleColors.count() - 1;
if ( mNextRandomStyleColorIndex >= mRandomStyleColors.count() )
mNextRandomStyleColorIndex = 0;
return res;
}
}

bool QgsColorSchemeRegistry::removeColorScheme( QgsColorScheme *scheme )
{
if ( mRandomStyleColorScheme == scheme )
{
mRandomStyleColorScheme = nullptr;
mRandomStyleColors.clear();
}

if ( mColorSchemeList.indexOf( scheme ) != -1 )
{
mColorSchemeList.removeAll( scheme );
Expand Down
60 changes: 60 additions & 0 deletions src/core/qgscolorschemeregistry.h
Expand Up @@ -119,10 +119,70 @@ class CORE_EXPORT QgsColorSchemeRegistry
}
#endif

/**
* Sets the color \a scheme to use when fetching random colors to use for symbol styles.
*
* \a scheme should match a color scheme which is already present in the registry.
*
* Note that calling this method takes a snapshot of the colors from the scheme's
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
* in \a scheme are not automatically reflected by calls to fetchRandomStyleColor().
* If \a scheme is updated, then another call to setRandomStyleColorScheme() must
* be made in order to update the cached list of available style colors.
*
* \see randomStyleColorScheme()
* \see fetchRandomStyleColor()
*
* \since QGIS 3.2
*/
void setRandomStyleColorScheme( QgsColorScheme *scheme );

/**
* Returns the color scheme used when fetching random colors to use for symbol styles.
*
* This may be nullptr, in which case totally random colors are used for styles.
*
* \see setRandomStyleColorScheme()
* \see fetchRandomStyleColor()
*
* \since QGIS 3.2
*/
QgsColorScheme *randomStyleColorScheme();

/**
* Returns a random color for use with a new symbol style (e.g. for a newly created
* map layer).
*
* If a randomStyleColorScheme() is set then this color will be randomly taken from that
* color scheme. If no randomStyleColorScheme() is set then a totally random color
* will be generated.
*
* Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
* in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
* If the scheme is updated, then another call to setRandomStyleColorScheme() must
* be made in order to update the cached list of available style colors from which
* fetchRandomStyleColor() selects colors.
*
* This method is thread safe.
*
* \see randomStyleColorScheme()
* \see setRandomStyleColorScheme()
* \since QGIS 3.2
*/
QColor fetchRandomStyleColor() const;

private:

QList< QgsColorScheme * > mColorSchemeList;

QgsColorScheme *mRandomStyleColorScheme = nullptr;
QgsNamedColorList mRandomStyleColors;

mutable int mNextRandomStyleColorIndex = 0;

int mNextRandomStyleColorDirection = 1;

};


Expand Down
9 changes: 2 additions & 7 deletions src/core/symbology/qgssymbol.cpp
Expand Up @@ -39,6 +39,7 @@
#include "qgspolygon.h"
#include "qgsclipper.h"
#include "qgsproperty.h"
#include "qgscolorschemeregistry.h"

#include <QColor>
#include <QImage>
Expand Down Expand Up @@ -322,13 +323,7 @@ QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
if ( defaultSymbol.isEmpty() ||
QgsProject::instance()->readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) )
{
// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> hueDist( 0, 359 );
std::uniform_int_distribution<int> satDist( 64, 255 );
std::uniform_int_distribution<int> valueDist( 128, 255 );
s->setColor( QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) ) );
s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
}

return s;
Expand Down
66 changes: 66 additions & 0 deletions tests/src/core/testqgscolorschemeregistry.cpp
Expand Up @@ -56,6 +56,29 @@ class DummyColorScheme : public QgsColorScheme

};

class DummyColorScheme2 : public QgsColorScheme
{
public:

DummyColorScheme2() = default;

QString schemeName() const override { return QStringLiteral( "Dummy scheme2" ); }

QgsNamedColorList fetchColors( const QString & = QString(),
const QColor & = QColor() ) override
{
QList< QPair< QColor, QString> > colors;
colors << qMakePair( QColor( 255, 255, 0 ), QStringLiteral( "schemetest" ) );
return colors;
}

QgsColorScheme *clone() const override
{
return new DummyColorScheme2();
}

};

class TestQgsColorSchemeRegistry : public QObject
{
Q_OBJECT
Expand All @@ -73,6 +96,7 @@ class TestQgsColorSchemeRegistry : public QObject
void populateFromInstance(); // check populating an empty scheme from the registry
void removeScheme(); // check removing a scheme from a registry
void matchingSchemes(); //check fetching schemes of specific type
void fetchRandomStyleColor();

private:

Expand Down Expand Up @@ -185,5 +209,47 @@ void TestQgsColorSchemeRegistry::matchingSchemes()
QCOMPARE( dummySchemes.at( 0 ), dummyScheme );
}

void TestQgsColorSchemeRegistry::fetchRandomStyleColor()
{
std::unique_ptr<QgsColorSchemeRegistry> registry = qgis::make_unique< QgsColorSchemeRegistry >();

// no randomStyleColorScheme set - test lots of colors to make sure their valid
for ( int i = 0; i < 10000; ++i )
{
QVERIFY( registry->fetchRandomStyleColor().isValid() );
}

// set a randomStyleColorScheme
DummyColorScheme2 *dummyScheme = new DummyColorScheme2();
registry->addColorScheme( dummyScheme );
registry->setRandomStyleColorScheme( dummyScheme );

// only one color in scheme

for ( int i = 0; i < 10; ++i )
{
QCOMPARE( registry->fetchRandomStyleColor().name(), QStringLiteral( "#ffff00" ) );
}

DummyColorScheme *dummyScheme2 = new DummyColorScheme();
registry->addColorScheme( dummyScheme2 );
registry->setRandomStyleColorScheme( dummyScheme2 );
for ( int i = 0; i < 10; ++i )
{
QString color = registry->fetchRandomStyleColor().name();
QVERIFY( color == QStringLiteral( "#ff0000" ) || color == QStringLiteral( "#00ff00" ) );
}

// remove current random style color scheme
registry->removeColorScheme( dummyScheme2 );
QVERIFY( !registry->randomStyleColorScheme() );
// no crash!
for ( int i = 0; i < 10; ++i )
{
QVERIFY( registry->fetchRandomStyleColor().isValid() );
}

}

QGSTEST_MAIN( TestQgsColorSchemeRegistry )
#include "testqgscolorschemeregistry.moc"

0 comments on commit bc449c4

Please sign in to comment.