Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Tile size algorithm
  • Loading branch information
elpaso committed Jan 10, 2023
1 parent 8282358 commit ddd010d
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 21 deletions.
Expand Up @@ -997,6 +997,14 @@ Evaluates a map of properties using the given ``context`` and returns a variant
.. versionadded:: 3.18
%End

static QSize tileSize( int width, int height, double &angleRad /In,Out/, const QSize maxSize = QSize() );
%Docstring
Calculate the minimum size in pixels of a symbol tile given the symbol ``width`` and ``height`` and the grid rotation ``angle`` in radians.
The method makes approximations in order to generate the smallest possible tile.

.. versionadded:: 3.30
%End


};

Expand Down
34 changes: 13 additions & 21 deletions src/core/symbology/qgsfillsymbollayer.cpp
Expand Up @@ -4409,37 +4409,26 @@ void QgsPointPatternFillSymbolLayer::toSld( QDomDocument &doc, QDomElement &elem

QImage QgsPointPatternFillSymbolLayer::toTiledPattern() const
{
const double angleRads { qDegreesToRadians( mAngle ) };
double distanceXPx = QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} );
double distanceYPx = QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} );

double angleRads { qDegreesToRadians( mAngle ) };

int distanceXPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceX, mDistanceXUnit, {} ) ) };
int distanceYPx { static_cast<int>( QgsSymbolLayerUtils::rescaleUom( mDistanceY, mDistanceYUnit, {} ) ) };

QSize size { static_cast<int>( distanceXPx ), static_cast<int>( distanceYPx ) };


if ( mAngle != 0 )
{
// adjust tile size to generate a tile with seamless edges
// from https://graphicdesign.stackexchange.com/questions/17132/how-to-create-a-pattern-or-tiles-from-rotated-elements
const double Hh = distanceYPx / std::cos( angleRads );
const double Hw = distanceXPx / std::sin( angleRads );
const double Wh = distanceYPx / std::sin( angleRads );
const double Ww = distanceXPx / std::cos( angleRads );
const double Qh = Hh / Hw; // Reduced fraction
const double Qw = Wh / Ww; // Reduced fraction
const double Pw = Wh * Qw / Ww;
const double Ph = Hh * Qh / Hw;
const double W1 = Pw * Ww;
const double H1 = Ph * Hw;

//size.setWidth( static_cast<int>( distanceXPx / std::cos( angleRads ) ) );
//size.setHeight( static_cast<int>( distanceYPx / std::sin( angleRads ) ) );
size.setWidth( std::round( W1 ) );
size.setHeight( std::round( H1 ) );
size = QgsSymbolLayerUtils::tileSize( distanceXPx, distanceYPx, angleRads );
}

/*
if ( size.isEmpty() || size.width() > 512 || size.height() > 512 )
{
return QImage( );
}
*/

QPixmap pixmap( size );
pixmap.fill( Qt::transparent );
Expand All @@ -4454,10 +4443,13 @@ QImage QgsPointPatternFillSymbolLayer::toTiledPattern() const
QgsSymbolRenderContext symbolContext( renderContext, QgsUnitTypes::RenderUnit::RenderPixels, 1.0, false, Qgis::SymbolRenderHints() );

std::unique_ptr< QgsPointPatternFillSymbolLayer > layerClone( clone() );

// !!!!!!!
layerClone->setAngle( qRadiansToDegrees( angleRads ) );

layerClone->drawPreviewIcon( symbolContext, pixmap.size() );
painter.end();
return pixmap.toImage();
return QImage();
}

QgsSymbolLayer *QgsPointPatternFillSymbolLayer::createFromSld( QDomElement &element )
Expand Down
116 changes: 116 additions & 0 deletions src/core/symbology/qgssymbollayerutils.cpp
Expand Up @@ -42,6 +42,7 @@
#include "qgsfillsymbol.h"
#include "qgssymbollayerreference.h"
#include "qgsmarkersymbollayer.h"
#include "qmath.h"

#include <QColor>
#include <QFont>
Expand Down Expand Up @@ -5096,3 +5097,118 @@ QgsStringMap QgsSymbolLayerUtils::evaluatePropertiesMap( const QMap<QString, Qgs
}
return properties;
}

QSize QgsSymbolLayerUtils::tileSize( int width, int height, double &angleRad, const QSize maxSize )
{

const QSize maxSizeActual { ! maxSize.isValid() ? QSize( width * 10, height * 10 ) : maxSize };

struct rationalTangent
{
int a;
int b;
double angle;
};

static const QList<rationalTangent> rationalTangents
{
{ 1, 10, qDegreesToRadians( 5.71059 ) },
{ 1, 5, qDegreesToRadians( 11.3099 ) },
{ 1, 4, qDegreesToRadians( 14.0362 ) },
{ 1, 4, qDegreesToRadians( 18.4349 ) },
{ 1, 2, qDegreesToRadians( 26.5651 ) },
{ 2, 3, qDegreesToRadians( 33.6901 ) },
{ 1, 1, qDegreesToRadians( 45.0 ) },
{ 3, 2, qDegreesToRadians( 56.3099 ) },
{ 2, 1, qDegreesToRadians( 63.4349 ) },
{ 3, 1, qDegreesToRadians( 71.5651 ) },
{ 4, 1, qDegreesToRadians( 75.9638 ) },
{ 10, 1, qDegreesToRadians( 84.2894 ) },
};

// TODO: clean angleRad


if ( qgsDoubleNear( angleRad, 0 ) )
{
angleRad = 0;
return QSize( width, height );
}

if ( qgsDoubleNear( angleRad, M_PI_2 ) )
{
angleRad = M_PI_2;
return QSize( height, width );
}

int rTanIdx = 0;

for ( int idx = 0; idx < rationalTangents.count(); ++idx )
{
const auto item = rationalTangents.at( idx );
if ( qgsDoubleNear( item.angle, angleRad ) || item.angle > angleRad )
{
angleRad = item.angle;
rTanIdx = idx;
break;
}
}

bool asc { true };
int increment { 1 };
bool endTouched { false };
bool startTouched { false };

while ( !( startTouched && endTouched ) && rTanIdx >= 0 && rTanIdx < rationalTangents.count() )
{

rationalTangent bTan { rationalTangents.at( rTanIdx ) };
angleRad = bTan.angle;
const double k { bTan.b *height *width / std::cos( angleRad ) };
const int hcfH = std::gcd( bTan.a * height, bTan.b * width );
const int hcfW = std::gcd( bTan.b * height, bTan.a * width );
const double W1 = k / hcfW;
const double H1 = k / hcfH;

qDebug() << "TO TILE" << rTanIdx << qRadiansToDegrees( angleRad ) << angleRad << W1 << H1 << "K:" << k << hcfW << hcfH << bTan.a << bTan.b;

return QSize( W1, H1 );

if ( W1 <= maxSizeActual.width() && H1 <= maxSizeActual.height() )
{
return QSize( W1, H1 );
}
else
{
if ( rTanIdx == 0 )
{
startTouched = true;
rTanIdx = rTanIdx + increment;
continue;
}
else if ( rTanIdx == rationalTangents.count() - 1 )
{
endTouched = true;
rTanIdx = rTanIdx - increment;
continue;
}

if ( startTouched )
{
rTanIdx += 1;
}
else if ( endTouched )
{
rTanIdx -= 1;
}
else
{
rTanIdx = asc ? rTanIdx + increment : rTanIdx - increment;
asc = ! asc;
increment += 1;
}
}
}

return QSize( );
}
7 changes: 7 additions & 0 deletions src/core/symbology/qgssymbollayerutils.h
Expand Up @@ -897,6 +897,13 @@ class CORE_EXPORT QgsSymbolLayerUtils
*/
static QgsStringMap evaluatePropertiesMap( const QMap<QString, QgsProperty> &propertiesMap, const QgsExpressionContext &context );

/**
* Calculate the minimum size in pixels of a symbol tile given the symbol \a width and \a height and the grid rotation \a angle in radians.
* The method makes approximations in order to generate the smallest possible tile.
* \since QGIS 3.30
*/
static QSize tileSize( int width, int height, double &angleRad SIP_INOUT, const QSize maxSize = QSize() );

///@cond PRIVATE
#ifndef SIP_RUN
static QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property )
Expand Down

0 comments on commit ddd010d

Please sign in to comment.