@@ -1547,6 +1547,14 @@ void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolV2RenderContext
1547
1547
{
1548
1548
Q_UNUSED ( lineWidth );
1549
1549
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
+
1550
1558
const QgsRenderContext& ctx = context.renderContext ();
1551
1559
// double outlinePixelWidth = lineWidth * QgsSymbolLayerV2Utils::pixelSizeScaleFactor( ctx, mLineWidthUnit );
1552
1560
double outputPixelDist = distance * QgsSymbolLayerV2Utils::pixelSizeScaleFactor ( ctx, mDistanceUnit );
@@ -1563,122 +1571,173 @@ void QgsLinePatternFillSymbolLayer::applyPattern( const QgsSymbolV2RenderContext
1563
1571
{
1564
1572
height = qAbs ( outputPixelDist / cos ( lineAngle * M_PI / 180 ) ); // keep perpendicular distance between lines constant
1565
1573
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 );
1566
1583
}
1567
1584
1568
1585
// 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;
1571
1621
1572
1622
if ( width > 10000 || height > 10000 ) // protect symbol layer from eating too much memory
1573
1623
{
1574
- QImage img;
1575
- mBrush .setTextureImage ( img );
1576
1624
return ;
1577
1625
}
1578
1626
1579
1627
QImage patternImage ( width, height, QImage::Format_ARGB32 );
1580
1628
patternImage.fill ( 0 );
1581
1629
1582
- QPoint p1, p2, p3, p4, p5, p6;
1630
+ QPointF p1, p2, p3, p4, p5, p6;
1583
1631
if ( qgsDoubleNear ( lineAngle, 0.0 ) || qgsDoubleNear ( lineAngle, 360.0 ) || qgsDoubleNear ( lineAngle, 180.0 ) )
1584
1632
{
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 );
1591
1639
}
1592
1640
else if ( qgsDoubleNear ( lineAngle, 90.0 ) || qgsDoubleNear ( lineAngle, 270.0 ) )
1593
1641
{
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 );
1600
1648
}
1601
1649
else if (( lineAngle > 0 && lineAngle < 90 ) || ( lineAngle > 180 && lineAngle < 270 ) )
1602
1650
{
1603
1651
dx = outputPixelDist * cos (( 90 - lineAngle ) * M_PI / 180.0 );
1604
1652
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 );
1611
1659
}
1612
1660
else if (( lineAngle < 180 ) || ( lineAngle > 270 && lineAngle < 360 ) )
1613
1661
{
1614
1662
dy = outputPixelDist * cos (( 180 - lineAngle ) * M_PI / 180 );
1615
1663
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 );
1622
1670
}
1623
1671
1624
1672
if ( !qgsDoubleNear ( mOffset , 0.0 ) ) // shift everything
1625
1673
{
1626
1674
QPointF tempPt;
1627
1675
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance ( p1, p3, outputPixelDist + outputPixelOffset );
1628
- p3 = QPoint ( tempPt.x (), tempPt.y () );
1676
+ p3 = QPointF ( tempPt.x (), tempPt.y () );
1629
1677
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance ( p2, p4, outputPixelDist + outputPixelOffset );
1630
- p4 = QPoint ( tempPt.x (), tempPt.y () );
1678
+ p4 = QPointF ( tempPt.x (), tempPt.y () );
1631
1679
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance ( p1, p5, outputPixelDist - outputPixelOffset );
1632
- p5 = QPoint ( tempPt.x (), tempPt.y () );
1680
+ p5 = QPointF ( tempPt.x (), tempPt.y () );
1633
1681
tempPt = QgsSymbolLayerV2Utils::pointOnLineWithDistance ( p2, p6, outputPixelDist - outputPixelOffset );
1634
- p6 = QPoint ( tempPt.x (), tempPt.y () );
1682
+ p6 = QPointF ( tempPt.x (), tempPt.y () );
1635
1683
1636
1684
// 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 () );;
1641
1689
}
1642
1690
1643
- if ( mFillLineSymbol )
1644
- {
1645
- QPainter p ( &patternImage );
1691
+ QPainter p ( &patternImage );
1646
1692
1647
1693
#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 );
1656
1708
#endif
1657
1709
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 );
1666
1714
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 );
1668
1723
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 );
1677
1725
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 () );
1680
1733
}
1681
1734
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
+
1682
1741
// set image to mBrush
1683
1742
if ( !qgsDoubleNear ( context.alpha (), 1.0 ) )
1684
1743
{
0 commit comments