Skip to content

Commit 3fd75ac

Browse files
committed
fix line pattern fill misalignements, fixes #8974
1 parent c7141e0 commit 3fd75ac

File tree

1 file changed

+126
-67
lines changed

1 file changed

+126
-67
lines changed

src/core/symbology-ng/qgsfillsymbollayerv2.cpp

+126-67
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,14 @@ void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolV2RenderContext
15471547
{
15481548
Q_UNUSED( lineWidth );
15491549
Q_UNUSED( color );
1550+
1551+
mBrush.setTextureImage( QImage() ); // set empty in case we have to return
1552+
1553+
if ( !mFillLineSymbol )
1554+
{
1555+
return;
1556+
}
1557+
15501558
const QgsRenderContext& ctx = context.renderContext();
15511559
//double outlinePixelWidth = lineWidth * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( ctx, mLineWidthUnit );
15521560
double outputPixelDist = distance * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( ctx, mDistanceUnit );
@@ -1563,122 +1571,173 @@ void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolV2RenderContext
15631571
{
15641572
height = qAbs( outputPixelDist / cos( lineAngle * M_PI / 180 ) ); //keep perpendicular distance between lines constant
15651573
width = qAbs( height / tan( lineAngle * M_PI / 180 ) );
1574+
1575+
// recalculate real angle and distance after rounding to pixels
1576+
lineAngle = 180 * qAbs( atan2( height, width ) ) / M_PI;
1577+
outputPixelDist = height * cos( lineAngle * M_PI / 180 );
1578+
1579+
// Round offset to correspond to one pixel height, otherwise lines may
1580+
// be shifted on tile border if offset falls close to pixel center
1581+
int offsetHeight = qRound( qAbs( outputPixelOffset / cos( lineAngle * M_PI / 180 ) ) );
1582+
outputPixelOffset = offsetHeight * cos( lineAngle * M_PI / 180 );
15661583
}
15671584

15681585
//depending on the angle, we might need to render into a larger image and use a subset of it
1569-
int dx = 0;
1570-
int dy = 0;
1586+
double dx = 0;
1587+
double dy = 0;
1588+
1589+
// To get all patterns into image, we have to consider symbols size (estimateMaxBleed())
1590+
1591+
double bleed = 0;
1592+
for ( int i = 0; i < mFillLineSymbol->symbolLayerCount(); i++ )
1593+
{
1594+
QgsSymbolLayerV2 *layer = mFillLineSymbol->symbolLayer( i );
1595+
double layerBleed = layer->estimateMaxBleed();
1596+
// TODO: to get real bleed we have to scale it using context and units,
1597+
// unfortunately estimateMaxBleed() ignore units completely, e.g.
1598+
// QgsMarkerLineSymbolLayerV2::estimateMaxBleed() is mixing marker size and
1599+
// offset regardless units. This has to be fixed especially
1600+
// in estimateMaxBleed(), context probably has to be used.
1601+
// For now, we only support milimeters
1602+
double outputPixelBleed = layerBleed * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( ctx, QgsSymbolV2::MM );
1603+
1604+
bleed = qMax( bleed, outputPixelBleed );
1605+
}
1606+
1607+
// Add buffer based on bleed but keep precisely the height/width ratio (angle)
1608+
// thus we add integer multiplications of width and heigh covering the bleed
1609+
int bufferMulti = qMax( qCeil( bleed / width ), qCeil( bleed / width ) );
1610+
1611+
// Always buffer at least once so that center of line marker in upper right corner
1612+
// does not fall outside due to representation error
1613+
bufferMulti = qMax( bufferMulti, 1 );
1614+
1615+
bufferMulti = 0;
1616+
int xBuffer = width * bufferMulti;
1617+
int yBuffer = height * bufferMulti;
1618+
1619+
width += 2 * xBuffer;
1620+
height += 2 * yBuffer;
15711621

15721622
if ( width > 10000 || height > 10000 ) //protect symbol layer from eating too much memory
15731623
{
1574-
QImage img;
1575-
mBrush.setTextureImage( img );
15761624
return;
15771625
}
15781626

15791627
QImage patternImage( width, height, QImage::Format_ARGB32 );
15801628
patternImage.fill( 0 );
15811629

1582-
QPoint p1, p2, p3, p4, p5, p6;
1630+
QPointF p1, p2, p3, p4, p5, p6;
15831631
if ( qgsDoubleNear( lineAngle, 0.0 ) || qgsDoubleNear( lineAngle, 360.0 ) || qgsDoubleNear( lineAngle, 180.0 ) )
15841632
{
1585-
p1 = QPoint( 0, height );
1586-
p2 = QPoint( width, height );
1587-
p3 = QPoint( 0, 0 );
1588-
p4 = QPoint( width, 0 );
1589-
p5 = QPoint( 0, 2 * height );
1590-
p6 = QPoint( width, 2 * height );
1633+
p1 = QPointF( 0, height );
1634+
p2 = QPointF( width, height );
1635+
p3 = QPointF( 0, 0 );
1636+
p4 = QPointF( width, 0 );
1637+
p5 = QPointF( 0, 2 * height );
1638+
p6 = QPointF( width, 2 * height );
15911639
}
15921640
else if ( qgsDoubleNear( lineAngle, 90.0 ) || qgsDoubleNear( lineAngle, 270.0 ) )
15931641
{
1594-
p1 = QPoint( 0, height );
1595-
p2 = QPoint( 0, 0 );
1596-
p3 = QPoint( width, height );
1597-
p4 = QPoint( width, 0 );
1598-
p5 = QPoint( -width, height );
1599-
p6 = QPoint( -width, 0 );
1642+
p1 = QPointF( 0, height );
1643+
p2 = QPointF( 0, 0 );
1644+
p3 = QPointF( width, height );
1645+
p4 = QPointF( width, 0 );
1646+
p5 = QPointF( -width, height );
1647+
p6 = QPointF( -width, 0 );
16001648
}
16011649
else if (( lineAngle > 0 && lineAngle < 90 ) || ( lineAngle > 180 && lineAngle < 270 ) )
16021650
{
16031651
dx = outputPixelDist * cos(( 90 - lineAngle ) * M_PI / 180.0 );
16041652
dy = outputPixelDist * sin(( 90 - lineAngle ) * M_PI / 180.0 );
1605-
p1 = QPoint( 0, height );
1606-
p2 = QPoint( width, 0 );
1607-
p3 = QPoint( -dx, height - dy );
1608-
p4 = QPoint( width - dx, -dy ); //p4 = QPoint( p3.x() + width, p3.y() - height );
1609-
p5 = QPoint( dx, height + dy );
1610-
p6 = QPoint( width + dx, dy ); //p6 = QPoint( p5.x() + width, p5.y() - height );
1653+
p1 = QPointF( 0, height );
1654+
p2 = QPointF( width, 0 );
1655+
p3 = QPointF( -dx, height - dy );
1656+
p4 = QPointF( width - dx, -dy ); //p4 = QPoint( p3.x() + width, p3.y() - height );
1657+
p5 = QPointF( dx, height + dy );
1658+
p6 = QPointF( width + dx, dy ); //p6 = QPoint( p5.x() + width, p5.y() - height );
16111659
}
16121660
else if (( lineAngle < 180 ) || ( lineAngle > 270 && lineAngle < 360 ) )
16131661
{
16141662
dy = outputPixelDist * cos(( 180 - lineAngle ) * M_PI / 180 );
16151663
dx = outputPixelDist * sin(( 180 - lineAngle ) * M_PI / 180 );
1616-
p1 = QPoint( width, height );
1617-
p2 = QPoint( 0, 0 );
1618-
p5 = QPoint( width + dx, height - dy );
1619-
p6 = QPoint( p5.x() - width, p5.y() - height ); //p6 = QPoint( dx, -dy );
1620-
p3 = QPoint( width - dx, height + dy );
1621-
p4 = QPoint( p3.x() - width, p3.y() - height ); //p4 = QPoint( -dx, dy );
1664+
p1 = QPointF( width, height );
1665+
p2 = QPointF( 0, 0 );
1666+
p5 = QPointF( width + dx, height - dy );
1667+
p6 = QPointF( p5.x() - width, p5.y() - height ); //p6 = QPoint( dx, -dy );
1668+
p3 = QPointF( width - dx, height + dy );
1669+
p4 = QPointF( p3.x() - width, p3.y() - height ); //p4 = QPoint( -dx, dy );
16221670
}
16231671

16241672
if ( !qgsDoubleNear( mOffset, 0.0 ) ) //shift everything
16251673
{
16261674
QPointF tempPt;
16271675
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p3, outputPixelDist + outputPixelOffset );
1628-
p3 = QPoint( tempPt.x(), tempPt.y() );
1676+
p3 = QPointF( tempPt.x(), tempPt.y() );
16291677
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p4, outputPixelDist + outputPixelOffset );
1630-
p4 = QPoint( tempPt.x(), tempPt.y() );
1678+
p4 = QPointF( tempPt.x(), tempPt.y() );
16311679
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p5, outputPixelDist - outputPixelOffset );
1632-
p5 = QPoint( tempPt.x(), tempPt.y() );
1680+
p5 = QPointF( tempPt.x(), tempPt.y() );
16331681
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p6, outputPixelDist - outputPixelOffset );
1634-
p6 = QPoint( tempPt.x(), tempPt.y() );
1682+
p6 = QPointF( tempPt.x(), tempPt.y() );
16351683

16361684
//update p1, p2 last
1637-
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p3, outputPixelOffset ).toPoint();
1638-
p1 = QPoint( tempPt.x(), tempPt.y() );
1639-
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p4, outputPixelOffset ).toPoint();
1640-
p2 = QPoint( tempPt.x(), tempPt.y() );;
1685+
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p1, p3, outputPixelOffset );
1686+
p1 = QPointF( tempPt.x(), tempPt.y() );
1687+
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance( p2, p4, outputPixelOffset );
1688+
p2 = QPointF( tempPt.x(), tempPt.y() );;
16411689
}
16421690

1643-
if ( mFillLineSymbol )
1644-
{
1645-
QPainter p( &patternImage );
1691+
QPainter p( &patternImage );
16461692

16471693
#if 0
1648-
// DEBUG: Draw rectangle
1649-
//p.setRenderHint( QPainter::Antialiasing, true );
1650-
QPen pen( QColor( Qt::black ) );
1651-
pen.setWidthF( 0.1 );
1652-
pen.setCapStyle( Qt::FlatCap );
1653-
p.setPen( pen );
1654-
QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width, 0 ) << QPoint( width, height ) << QPoint( 0, height ) << QPoint( 0, 0 ) ;
1655-
p.drawPolygon( polygon );
1694+
// DEBUG: Draw rectangle
1695+
p.setRenderHint( QPainter::Antialiasing, false ); // get true rect
1696+
QPen pen( QColor( Qt::black ) );
1697+
pen.setWidthF( 0.1 );
1698+
pen.setCapStyle( Qt::FlatCap );
1699+
p.setPen( pen );
1700+
1701+
// To see this rectangle, comment buffer cut below.
1702+
// Subtract 1 because not antialiased are rendered to the right/down by 1 pixel
1703+
QPolygon polygon = QPolygon() << QPoint( 0, 0 ) << QPoint( width - 1, 0 ) << QPoint( width - 1, height - 1 ) << QPoint( 0, height - 1 ) << QPoint( 0, 0 ) ;
1704+
p.drawPolygon( polygon );
1705+
1706+
polygon = QPolygon() << QPoint( xBuffer, yBuffer ) << QPoint( width - xBuffer - 1, yBuffer ) << QPoint( width - xBuffer - 1, height - yBuffer - 1 ) << QPoint( xBuffer, height - yBuffer - 1 ) << QPoint( xBuffer, yBuffer ) ;
1707+
p.drawPolygon( polygon );
16561708
#endif
16571709

1658-
// line rendering needs context for drawing on patternImage
1659-
QgsRenderContext lineRenderContext;
1660-
lineRenderContext.setPainter( &p );
1661-
lineRenderContext.setRasterScaleFactor( 1.0 );
1662-
lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() * context.renderContext().rasterScaleFactor() );
1663-
QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() / context.renderContext().rasterScaleFactor() );
1664-
lineRenderContext.setMapToPixel( mtp );
1665-
lineRenderContext.setForceVectorOutput( false );
1710+
// Use antialiasing because without antialiasing lines are rendered to the
1711+
// right and below the mathematically defined points (not symetrical)
1712+
// and such tiles become useless for are filling
1713+
p.setRenderHint( QPainter::Antialiasing, true );
16661714

1667-
mFillLineSymbol->startRender( lineRenderContext );
1715+
// line rendering needs context for drawing on patternImage
1716+
QgsRenderContext lineRenderContext;
1717+
lineRenderContext.setPainter( &p );
1718+
lineRenderContext.setRasterScaleFactor( 1.0 );
1719+
lineRenderContext.setScaleFactor( context.renderContext().scaleFactor() * context.renderContext().rasterScaleFactor() );
1720+
QgsMapToPixel mtp( context.renderContext().mapToPixel().mapUnitsPerPixel() / context.renderContext().rasterScaleFactor() );
1721+
lineRenderContext.setMapToPixel( mtp );
1722+
lineRenderContext.setForceVectorOutput( false );
16681723

1669-
QVector<QPolygon> polygons;
1670-
polygons.append( QPolygon() << p1 << p2 );
1671-
polygons.append( QPolygon() << p3 << p4 );
1672-
polygons.append( QPolygon() << p5 << p6 );
1673-
foreach ( QPolygon polygon, polygons )
1674-
{
1675-
mFillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
1676-
}
1724+
mFillLineSymbol->startRender( lineRenderContext );
16771725

1678-
mFillLineSymbol->stopRender( lineRenderContext );
1679-
p.end();
1726+
QVector<QPolygonF> polygons;
1727+
polygons.append( QPolygonF() << p1 << p2 );
1728+
polygons.append( QPolygonF() << p3 << p4 );
1729+
polygons.append( QPolygonF() << p5 << p6 );
1730+
foreach ( QPolygonF polygon, polygons )
1731+
{
1732+
mFillLineSymbol->renderPolyline( polygon, context.feature(), lineRenderContext, -1, context.selected() );
16801733
}
16811734

1735+
mFillLineSymbol->stopRender( lineRenderContext );
1736+
p.end();
1737+
1738+
// Cut off the buffer
1739+
patternImage = patternImage.copy( xBuffer, yBuffer, patternImage.width() - 2 * xBuffer, patternImage.height() - 2 * yBuffer );
1740+
16821741
//set image to mBrush
16831742
if ( !qgsDoubleNear( context.alpha(), 1.0 ) )
16841743
{

0 commit comments

Comments
 (0)