@@ -1495,31 +1495,30 @@ RendererAgg::draw_regpoly_collection(const Py::Tuple& args) {
14951495Py::Object
14961496RendererAgg::draw_lines (const Py::Tuple& args) {
14971497
1498-
1499- _VERBOSE (" RendererAgg::draw_lines" );
1498+ _VERBOSE (" RendererAgg::draw_lines" );
15001499 args.verify_length (4 );
1501-
1500+
15021501 Py::Object xo = args[1 ];
15031502 Py::Object yo = args[2 ];
1504-
1503+
15051504 PyArrayObject *xa = (PyArrayObject *) PyArray_ContiguousFromObject (xo.ptr (), PyArray_DOUBLE, 1 , 1 );
1506-
1505+
15071506 if (xa==NULL )
15081507 throw Py::TypeError (" RendererAgg::draw_lines expected numerix array" );
1509-
1510-
1508+
1509+
15111510 PyArrayObject *ya = (PyArrayObject *) PyArray_ContiguousFromObject (yo.ptr (), PyArray_DOUBLE, 1 , 1 );
1512-
1511+
15131512 if (ya==NULL )
15141513 throw Py::TypeError (" RendererAgg::draw_lines expected numerix array" );
1515-
1516-
1514+
1515+
15171516 size_t Nx = xa->dimensions [0 ];
15181517 size_t Ny = ya->dimensions [0 ];
1519-
1518+
15201519 if (Nx!=Ny)
15211520 throw Py::ValueError (Printf (" x and y must be equal length arrays; found %d and %d" , Nx, Ny).str ());
1522-
1521+
15231522 // call gc with snapto==True if line len is 2 to fix grid line
15241523 // problem
15251524 bool snapto = false ;
@@ -1531,116 +1530,274 @@ RendererAgg::draw_lines(const Py::Tuple& args) {
15311530 double y0 = *(double *)(ya->data + 0 *ya->strides [0 ]);
15321531 double y1 = *(double *)(ya->data + 1 *ya->strides [0 ]);
15331532 snapto = (x0==x1) || (y0==y1);
1534-
1533+
15351534 }
1536-
15371535 GCAgg gc = GCAgg (args[0 ], dpi, snapto);
1538-
1536+
15391537 set_clipbox_rasterizer (gc.cliprect );
1540- // path_t transpath(path, xytrans);
1541- _process_alpha_mask (gc);
1542-
1543-
1538+
15441539 Transformation* mpltransform = static_cast <Transformation*>(args[3 ].ptr ());
1545-
1540+
15461541 double a, b, c, d, tx, ty;
15471542 try {
15481543 mpltransform->affine_params_api (&a, &b, &c, &d, &tx, &ty);
15491544 }
15501545 catch (...) {
15511546 throw Py::ValueError (" Domain error on affine_params_api in RendererAgg::draw_lines" );
15521547 }
1553-
1548+
15541549 agg::trans_affine xytrans = agg::trans_affine (a,b,c,d,tx,ty);
1555-
1556-
1550+
1551+
15571552 agg::path_storage path;
1558-
1559-
1553+
1554+
15601555 bool needNonlinear = mpltransform->need_nonlinear_api ();
1561-
1562- double thisx, thisy;
1556+
1557+ double thisx (0.0 ), thisy (0.0 );
1558+ double origdx (0.0 ), origdy (0.0 ), origdNorm2 (0 );
15631559 bool moveto = true ;
15641560 double heightd = height;
1565-
1566- double lastx (-2.0 ), lasty (-2.0 );
1567-
1568- size_t i (0 );
1569- bool more (true );
1570- for (i=0 ; i<Nx; i++) {
1571- more = true ;
1561+
1562+ double lastx (0 ), lasty (0 );
1563+ double lastWrittenx (0 ), lastWritteny (0 );
1564+ bool clipped = false ;
1565+
1566+ bool haveMin = false , lastMax = true ;
1567+ double dnorm2Min (0 ), dnorm2Max (0 );
1568+ double maxX (0 ), maxY (0 ), minX (0 ), minY (0 );
1569+
1570+ double totdx, totdy, totdot;
1571+ double paradx, parady, paradNorm2;
1572+ double perpdx, perpdy, perpdNorm2;
1573+
1574+ int counter = 0 ;
1575+ // idea: we can skip drawing many lines: lines < 1 pixel in length, lines
1576+ // outside of the drawing area, and we can combine sequential parallel lines
1577+ // into a single line instead of redrawing lines over the same points.
1578+ // The loop below works a bit like a state machine, where what it does depends
1579+ // on what it did in the last looping. To test whether sequential lines
1580+ // are close to parallel, I calculate the distance moved perpendicular to the
1581+ // last line. Once it gets too big, the lines cannot be combined.
1582+ for (size_t i=0 ; i<Nx; i++) {
1583+
15721584 thisx = *(double *)(xa->data + i*xa->strides [0 ]);
15731585 thisy = *(double *)(ya->data + i*ya->strides [0 ]);
1574-
1575-
1586+
15761587 if (needNonlinear)
15771588 try {
1578- mpltransform->nonlinear_only_api (&thisx, &thisy);
1589+ mpltransform->nonlinear_only_api (&thisx, &thisy);
15791590 }
15801591 catch (...) {
1581- moveto = true ;
1582- continue ;
1592+ moveto = true ;
1593+ continue ;
15831594 }
1584- if (MPL_isnan64 (thisx) || MPL_isnan64 (thisy)) {
1585- moveto = true ;
1586- continue ;
1587- }
1588-
1595+ if (MPL_isnan64 (thisx) || MPL_isnan64 (thisy)) {
1596+ moveto = true ;
1597+ continue ;
1598+ }
1599+
15891600 // use agg's transformer?
15901601 xytrans.transform (&thisx, &thisy);
15911602 thisy = heightd - thisy; // flipy
15921603
1604+ if (snapto) {
1605+ // disable subpixel rendering for horizontal or vertical lines of len=2
1606+ // because it causes irregular line widths for grids and ticks
1607+ thisx = (int )thisx + 0.5 ;
1608+ thisy = (int )thisy + 0.5 ;
1609+ }
1610+
1611+ // if we are starting a new path segment, move to the first point + init
1612+ if (moveto){
1613+ path.move_to (thisx, thisy);
1614+ lastx = thisx;
1615+ lasty = thisy;
1616+ origdNorm2 = 0 ; // resets the orig-vector variables (see if-statement below)
1617+ moveto = false ;
1618+ continue ;
1619+ }
1620+
15931621 // don't render line segments less that on pixel long!
1594- if (!moveto && (i>0 ) && fabs (thisx-lastx)<1.0 && fabs (thisy-lasty)<1.0 ) {
1622+ if (fabs (thisx-lastx) < 1.0 && fabs (thisy-lasty) < 1.0 ){
1623+ continue ; // don't update lastx this time!
1624+ }
1625+
1626+ // skip any lines that are outside the drawing area. Note: More lines
1627+ // could be clipped, but a more involved calculation would be needed
1628+ if ( (thisx < 0 && lastx < 0 ) ||
1629+ (thisx > width && lastx > width ) ||
1630+ (thisy < 0 && lasty < 0 ) ||
1631+ (thisy > height && lasty > height) ){
1632+ lastx = thisx;
1633+ lasty = thisy;
1634+ clipped = true ;
15951635 continue ;
15961636 }
15971637
1638+ // if we have no orig vector, set it to this vector and continue.
1639+ // this orig vector is the reference vector we will build up the line to
1640+ if (origdNorm2 == 0 ){
1641+ // if we clipped after the moveto but before we got here, redo the moveto
1642+ if (clipped){
1643+ path.move_to (lastx, lasty);
1644+ clipped = false ;
1645+ }
1646+
1647+ origdx = thisx - lastx;
1648+ origdy = thisy - lasty;
1649+ origdNorm2 = origdx*origdx + origdy*origdy;
1650+
1651+ // set all the variables to reflect this new orig vecor
1652+ dnorm2Max = origdNorm2;
1653+ dnorm2Min = 0 ;
1654+ haveMin = false ;
1655+ lastMax = true ;
1656+ maxX = thisx;
1657+ maxY = thisy;
1658+ minX = lastx;
1659+ minY = lasty;
1660+
1661+ lastWrittenx = lastx;
1662+ lastWritteny = lasty;
1663+
1664+ // set the last point seen
1665+ lastx = thisx;
1666+ lasty = thisy;
1667+ continue ;
1668+ }
15981669
1599- lastx = thisx;
1600- lasty = thisy;
1601- if (snapto) {
1602- // disable subpixel rendering for horizontal or vertical lines
1603- // because it causes irregular line widths for grids and ticks
1670+ // if got to here, then we have an orig vector and we just got
1671+ // a vector in the sequence.
1672+
1673+ // check that the perpendicular distance we have moved from the
1674+ // last written point compared to the line we are building is not too
1675+ // much. If o is the orig vector (we are building on), and v is the vector
1676+ // from the last written point to the current point, then the perpendicular
1677+ // vector is p = v - (o.v)o, and we normalize o (by dividing the
1678+ // second term by o.o).
1679+
1680+ // get the v vector
1681+ totdx = thisx - lastWrittenx;
1682+ totdy = thisy - lastWritteny;
1683+ totdot = origdx*totdx + origdy*totdy;
1684+
1685+ // get the para vector ( = (o.v)o/(o.o) )
1686+ paradx = totdot*origdx/origdNorm2;
1687+ parady = totdot*origdy/origdNorm2;
1688+ paradNorm2 = paradx*paradx + parady*parady;
1689+
1690+ // get the perp vector ( = v - para )
1691+ perpdx = totdx - paradx;
1692+ perpdy = totdy - parady;
1693+ perpdNorm2 = perpdx*perpdx + perpdy*perpdy;
1694+
1695+ // if the perp vector is less than some number of (squared) pixels in size,
1696+ // then merge the current vector
1697+ if (perpdNorm2 < 0.25 ){
1698+ // check if the current vector is parallel or
1699+ // anti-parallel to the orig vector. If it is parallel, test
1700+ // if it is the longest of the vectors we are merging in that direction.
1701+ // If anti-p, test if it is the longest in the opposite direction (the
1702+ // min of our final line)
16041703
1605- thisx = (int )thisx + 0.5 ;
1606- thisy = (int )thisy + 0.5 ;
1704+ lastMax = false ;
1705+ if (totdot >= 0 ){
1706+ if (paradNorm2 > dnorm2Max){
1707+ lastMax = true ;
1708+ dnorm2Max = paradNorm2;
1709+ maxX = lastWrittenx + paradx;
1710+ maxY = lastWritteny + parady;
1711+ }
1712+ }
1713+ else {
1714+
1715+ haveMin = true ;
1716+ if (paradNorm2 > dnorm2Min){
1717+ dnorm2Min = paradNorm2;
1718+ minX = lastWrittenx + paradx;
1719+ minY = lastWritteny + parady;
1720+ }
1721+ }
1722+
1723+ lastx = thisx;
1724+ lasty = thisy;
1725+ continue ;
16071726 }
16081727
1728+ // if we get here, then this vector was not similar enough to the line
1729+ // we are building, so we need to draw that line and start the next one.
16091730
1610- if (moveto)
1611- path.move_to (thisx, thisy);
1612- else
1613- path.line_to (thisx, thisy);
1614-
1615- moveto = false ;
1616- if ((i>0 ) & (i%10000 )==0 ) {
1617- // draw the path in chunks
1618- // std::cout << "rendering chunk " << i << std::endl;
1619- _render_lines_path (path, gc);
1620- path.remove_all ();
1621- path.move_to (thisx, thisy);
1622- more = false ;
1731+ // if the line needs to extend in the opposite direction from the direction
1732+ // we are drawing in, move back to we start drawing from back there.
1733+ if (haveMin){
1734+ path.line_to (minX, minY); // would be move_to if not for artifacts
16231735 }
16241736
1737+ path.line_to (maxX, maxY);
16251738
1626- // std::cout << "draw lines " << thisx << " " << thisy << std::endl;
1739+ // if we clipped some segments between this line and the next line
1740+ // we are starting, we also need to move to the last point.
1741+ if (clipped){
1742+ path.move_to (lastx, lasty);
1743+ }
1744+ else if (!lastMax){
1745+ // if the last line was not the longest line, then move back to the end
1746+ // point of the last line in the sequence. Only do this if not clipped,
1747+ // since in that case lastx,lasty is not part of the line just drawn.
1748+ path.line_to (lastx, lasty); // would be move_to if not for artifacts
1749+ }
1750+
1751+ // std::cout << "draw lines (" << lastx << ", " << lasty << ")" << std::endl;
1752+
1753+ // now reset all the variables to get ready for the next line
1754+
1755+ origdx = thisx - lastx;
1756+ origdy = thisy - lasty;
1757+ origdNorm2 = origdx*origdx + origdy*origdy;
1758+
1759+ dnorm2Max = origdNorm2;
1760+ dnorm2Min = 0 ;
1761+ haveMin = false ;
1762+ lastMax = true ;
1763+ maxX = thisx;
1764+ maxY = thisy;
1765+ minX = lastx;
1766+ minY = lasty;
1767+
1768+ lastWrittenx = lastx;
1769+ lastWritteny = lasty;
1770+
1771+ clipped = false ;
1772+
1773+ lastx = thisx;
1774+ lasty = thisy;
1775+
1776+ counter++;
16271777 }
1628-
1778+
1779+ // draw the last line, which is usually not drawn in the loop
1780+ if (origdNorm2 != 0 ){
1781+ if (haveMin){
1782+ path.line_to (minX, minY); // would be move_to if not for artifacts
1783+ }
1784+
1785+ path.line_to (maxX, maxY);
1786+ }
1787+
1788+ // std::cout << "drew " << counter+1 << " lines" << std::endl;
1789+
16291790 Py_XDECREF (xa);
16301791 Py_XDECREF (ya);
1631-
1792+
16321793 // typedef agg::conv_transform<agg::path_storage, agg::trans_affine> path_t;
16331794 // path_t transpath(path, xytrans);
16341795 _VERBOSE (" RendererAgg::draw_lines rendering lines path" );
1635- if (more){
1636- // render the rest
1637- // /std::cout << "rendering the rest" << std::endl;
1638- _render_lines_path (path, gc);
1639- }
1640-
1796+ _render_lines_path (path, gc);
1797+
16411798 _VERBOSE (" RendererAgg::draw_lines DONE" );
16421799 return Py::Object ();
1643-
1800+
16441801}
16451802
16461803bool
0 commit comments