1 // An OpenVG path on the Canvas
3 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Library General Public
7 // License as published by the Free Software Foundation; either
8 // version 2 of the License, or (at your option) any later version.
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Library General Public License for more details.
15 // You should have received a copy of the GNU Library General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include "CanvasPath.hxx"
20 #include <simgear/scene/util/parse_color.hxx>
22 #include <osg/Drawable>
23 #include <osg/Version>
25 #include <vg/openvg.h>
32 typedef std::vector<VGubyte> CmdList;
33 typedef std::vector<VGfloat> CoordList;
36 static const VGubyte shCoordsPerCommand[] = {
37 0, /* VG_CLOSE_PATH */
46 5, /* VG_SCCWARC_TO */
48 5, /* VG_LCCWARC_TO */
51 static const VGubyte shNumCommands = sizeof(shCoordsPerCommand)
52 / sizeof(shCoordsPerCommand[0]);
55 * Helper to split and convert comma/whitespace separated floating point
58 std::vector<float> splitAndConvert(const char del[], const std::string& str);
60 class Path::PathDrawable:
64 PathDrawable(Path* path):
66 _path(VG_INVALID_HANDLE),
67 _paint(VG_INVALID_HANDLE),
68 _paint_fill(VG_INVALID_HANDLE),
69 _attributes_dirty(~0),
71 _fill_rule(VG_EVEN_ODD),
73 _stroke_linecap(VG_CAP_BUTT),
74 _stroke_linejoin(VG_JOIN_MITER)
76 setSupportsDisplayList(false);
77 setDataVariance(Object::DYNAMIC);
79 setUpdateCallback(new PathUpdateCallback());
82 virtual ~PathDrawable()
84 if( _path != VG_INVALID_HANDLE )
86 if( _paint != VG_INVALID_HANDLE )
87 vgDestroyPaint(_paint);
88 if( _paint_fill != VG_INVALID_HANDLE )
89 vgDestroyPaint(_paint_fill);
92 virtual const char* className() const { return "PathDrawable"; }
93 virtual osg::Object* cloneType() const { return new PathDrawable(_path_element); }
94 virtual osg::Object* clone(const osg::CopyOp&) const { return new PathDrawable(_path_element); }
97 * Replace the current path segments with the new ones
99 * @param cmds List of OpenVG path commands
100 * @param coords List of coordinates/parameters used by #cmds
102 void setSegments(const CmdList& cmds, const CoordList& coords)
107 _attributes_dirty |= (PATH | BOUNDING_BOX);
111 * Set path fill paint ("none" if not filled)
113 void setFill(const std::string& fill)
115 if( fill.empty() || fill == "none" )
117 _mode &= ~VG_FILL_PATH;
119 else if( parseColor(fill, _fill_color) )
121 _mode |= VG_FILL_PATH;
122 _attributes_dirty |= FILL_COLOR;
130 "canvas::Path Unknown fill: " << fill
136 * Set path fill rule ("pseudo-nonzero" or "evenodd")
138 * @warning As the current nonzero implementation causes sever artifacts
139 * for every concave path we call it pseudo-nonzero, so that
140 * everyone is warned that it won't work as expected :)
142 void setFillRule(const std::string& fill_rule)
144 if( fill_rule == "pseudo-nonzero" )
145 _fill_rule = VG_NON_ZERO;
146 else // if( fill_rule == "evenodd" )
147 _fill_rule = VG_EVEN_ODD;
151 * Set path stroke paint ("none" if no stroke)
153 void setStroke(const std::string& stroke)
155 if( stroke.empty() || stroke == "none" )
157 _mode &= ~VG_STROKE_PATH;
159 else if( parseColor(stroke, _stroke_color) )
161 _mode |= VG_STROKE_PATH;
162 _attributes_dirty |= STROKE_COLOR;
170 "canvas::Path Unknown stroke: " << stroke
178 void setStrokeWidth(float width)
180 _stroke_width = width;
181 _attributes_dirty |= BOUNDING_BOX;
185 * Set stroke dash (line stipple)
187 void setStrokeDashArray(const std::string& dash)
189 _stroke_dash = splitAndConvert(",\t\n ", dash);
195 * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
197 void setStrokeLinecap(const std::string& linecap)
199 if( linecap == "round" )
200 _stroke_linecap = VG_CAP_ROUND;
201 else if( linecap == "square" )
202 _stroke_linecap = VG_CAP_SQUARE;
204 _stroke_linecap = VG_CAP_BUTT;
208 * Set stroke-linejoin
210 * @see http://www.w3.org/TR/SVG/painting.html#StrokeLinejoinProperty
212 void setStrokeLinejoin(const std::string& linejoin)
214 if( linejoin == "round" )
215 _stroke_linejoin = VG_JOIN_ROUND;
216 else if( linejoin == "bevel" )
217 _stroke_linejoin = VG_JOIN_BEVEL;
219 _stroke_linejoin = VG_JOIN_MITER;
225 virtual void drawImplementation(osg::RenderInfo& renderInfo) const
227 if( _attributes_dirty & PATH )
230 osg::State* state = renderInfo.getState();
233 state->setActiveTextureUnit(0);
234 state->setClientActiveTextureUnit(0);
235 state->disableAllVertexArrays();
237 bool was_blend_enabled = state->getLastAppliedMode(GL_BLEND);
238 bool was_stencil_enabled = state->getLastAppliedMode(GL_STENCIL_TEST);
239 osg::StateAttribute const* blend_func =
240 state->getLastAppliedAttribute(osg::StateAttribute::BLENDFUNC);
242 // Initialize/Update the paint
243 if( _attributes_dirty & STROKE_COLOR )
245 if( _paint == VG_INVALID_HANDLE )
246 _paint = vgCreatePaint();
248 vgSetParameterfv(_paint, VG_PAINT_COLOR, 4, _stroke_color._v);
250 _attributes_dirty &= ~STROKE_COLOR;
253 // Initialize/update fill paint
254 if( _attributes_dirty & FILL_COLOR )
256 if( _paint_fill == VG_INVALID_HANDLE )
257 _paint_fill = vgCreatePaint();
259 vgSetParameterfv(_paint_fill, VG_PAINT_COLOR, 4, _fill_color._v);
261 _attributes_dirty &= ~FILL_COLOR;
265 if( _mode & VG_STROKE_PATH )
267 vgSetPaint(_paint, VG_STROKE_PATH);
269 vgSetf(VG_STROKE_LINE_WIDTH, _stroke_width);
270 vgSeti(VG_STROKE_CAP_STYLE, _stroke_linecap);
271 vgSeti(VG_STROKE_JOIN_STYLE, _stroke_linejoin);
272 vgSetfv( VG_STROKE_DASH_PATTERN,
274 _stroke_dash.empty() ? 0 : &_stroke_dash[0] );
276 if( _mode & VG_FILL_PATH )
278 vgSetPaint(_paint_fill, VG_FILL_PATH);
280 vgSeti(VG_FILL_RULE, _fill_rule);
283 // And finally draw the path
285 vgDrawPath(_path, _mode);
287 VGErrorCode err = vgGetError();
288 if( err != VG_NO_ERROR )
289 SG_LOG(SG_GL, SG_ALERT, "vgError: " << err);
291 // Restore OpenGL state (TODO check if more is needed or integrate
292 // better with OpenSceneGraph)
293 if( was_blend_enabled ) glEnable(GL_BLEND);
294 if( was_stencil_enabled ) glEnable(GL_STENCIL_TEST);
295 if( blend_func ) blend_func->apply(*state);
298 osg::BoundingBox getTransformedBounds(const osg::Matrix& mat) const
302 osg::Vec2f cur(0, 0), // VG "Current point" (in local coordinates)
303 sub(0, 0); // beginning of current sub path
304 VGubyte cmd_index = 0;
305 for( size_t i = 0, ci = 0;
306 i < _cmds.size() && ci < _coords.size();
307 ++i, ci += shCoordsPerCommand[cmd_index] )
309 VGubyte rel = _cmds[i] & 1,
313 if( cmd_index >= shNumCommands )
314 return osg::BoundingBox();
316 const VGubyte max_coords = 3;
317 osg::Vec2f points[max_coords];
318 VGubyte num_coords = 0;
329 points[ num_coords++ ].set(_coords[ci], _coords[ci + 1]);
333 points[ num_coords++ ].set( _coords[ci] + (rel ? cur.x() : 0),
335 // We have handled rel/abs already, so no need to do it again...
340 points[ num_coords++ ].set( cur.x(),
341 _coords[ci] + (rel ? cur.y() : 0) );
342 // We have handled rel/abs already, so no need to do it again...
348 points[ num_coords++ ].set(_coords[ci ], _coords[ci + 1]);
349 points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
353 points[ num_coords++ ].set(_coords[ci ], _coords[ci + 1]);
354 points[ num_coords++ ].set(_coords[ci + 2], _coords[ci + 3]);
355 points[ num_coords++ ].set(_coords[ci + 4], _coords[ci + 5]);
362 points[ num_coords++ ].set(_coords[ci + 3], _coords[ci + 4]);
365 SG_LOG(SG_GL, SG_WARN, "Unknown VG command: " << (int)cmd);
366 return osg::BoundingBox();
369 assert(num_coords <= max_coords);
370 for(VGubyte i = 0; i < num_coords; ++i)
375 bb.expandBy( transformPoint(mat, points[i]) );
380 cur = points[ num_coords - 1 ];
382 if( cmd == VG_MOVE_TO )
391 * Compute the bounding box
393 virtual osg::BoundingBox
394 #if OSG_VERSION_LESS_THAN(3,3,2)
401 if( _path == VG_INVALID_HANDLE || (_attributes_dirty & PATH) )
402 return osg::BoundingBox();
404 VGfloat min[2], size[2];
405 vgPathBounds(_path, &min[0], &min[1], &size[0], &size[1]);
407 _attributes_dirty &= ~BOUNDING_BOX;
409 // vgPathBounds doesn't take stroke width into account
410 float ext = 0.5 * _stroke_width;
412 return osg::BoundingBox
414 min[0] - ext, min[1] - ext, -0.1,
415 min[0] + size[0] + ext, min[1] + size[1] + ext, 0.1
424 STROKE_COLOR = PATH << 1,
425 FILL_COLOR = STROKE_COLOR << 1,
426 BOUNDING_BOX = FILL_COLOR << 1
431 mutable VGPath _path;
432 mutable VGPaint _paint;
433 mutable VGPaint _paint_fill;
434 mutable uint32_t _attributes_dirty;
440 osg::Vec4f _fill_color;
441 VGFillRule _fill_rule;
442 osg::Vec4f _stroke_color;
443 VGfloat _stroke_width;
444 std::vector<VGfloat> _stroke_dash;
445 VGCapStyle _stroke_linecap;
446 VGJoinStyle _stroke_linejoin;
448 osg::Vec3f transformPoint( const osg::Matrix& m,
449 osg::Vec2f pos ) const
453 m(0, 0) * pos[0] + m(1, 0) * pos[1] + m(3, 0),
454 m(0, 1) * pos[0] + m(1, 1) * pos[1] + m(3, 1),
460 * Initialize/Update the OpenVG path
464 if( !vgHasContextSH() )
467 if( _attributes_dirty & PATH )
469 const VGbitfield caps = VG_PATH_CAPABILITY_APPEND_TO
470 | VG_PATH_CAPABILITY_MODIFY
471 | VG_PATH_CAPABILITY_PATH_BOUNDS;
473 if( _path == VG_INVALID_HANDLE )
474 _path = vgCreatePath(
475 VG_PATH_FORMAT_STANDARD,
477 1.f, 0.f, // scale,bias
478 _cmds.size(), _coords.size(),
482 vgClearPath(_path, caps);
484 if( !_cmds.empty() && !_coords.empty() )
485 vgAppendPathData(_path, _cmds.size(), &_cmds[0], &_coords[0]);
487 _attributes_dirty &= ~PATH;
488 _attributes_dirty |= BOUNDING_BOX;
491 if( _attributes_dirty & BOUNDING_BOX )
495 // Recalculate bounding box now (prevent race condition)
500 struct PathUpdateCallback:
501 public osg::Drawable::UpdateCallback
503 virtual void update(osg::NodeVisitor*, osg::Drawable* drawable)
505 static_cast<PathDrawable*>(drawable)->update();
510 //----------------------------------------------------------------------------
511 const std::string Path::TYPE_NAME = "path";
513 //----------------------------------------------------------------------------
514 void Path::staticInit()
519 PathDrawableRef Path::*path = &Path::_path;
521 addStyle("fill", "color", &PathDrawable::setFill, path);
522 addStyle("fill-rule", "", &PathDrawable::setFillRule, path);
523 addStyle("stroke", "color", &PathDrawable::setStroke, path);
524 addStyle("stroke-width", "numeric", &PathDrawable::setStrokeWidth, path);
525 addStyle("stroke-dasharray", "", &PathDrawable::setStrokeDashArray, path);
526 addStyle("stroke-linecap", "", &PathDrawable::setStrokeLinecap, path);
527 addStyle("stroke-linejoin", "", &PathDrawable::setStrokeLinejoin, path);
530 //----------------------------------------------------------------------------
531 Path::Path( const CanvasWeakPtr& canvas,
532 const SGPropertyNode_ptr& node,
533 const Style& parent_style,
535 Element(canvas, node, parent_style, parent),
536 _path( new PathDrawable(this) )
544 //----------------------------------------------------------------------------
550 //----------------------------------------------------------------------------
551 void Path::update(double dt)
553 if( _attributes_dirty & (CMDS | COORDS) )
557 _node->getChildValues<VGubyte, int>("cmd"),
558 _node->getChildValues<VGfloat, float>("coord")
561 _attributes_dirty &= ~(CMDS | COORDS);
567 //----------------------------------------------------------------------------
568 osg::BoundingBox Path::getTransformedBounds(const osg::Matrix& m) const
570 return _path->getTransformedBounds(m);
573 //----------------------------------------------------------------------------
574 Path& Path::moveTo(float x_abs, float y_abs)
576 return addSegment(VG_MOVE_TO_ABS, x_abs, y_abs);
579 //----------------------------------------------------------------------------
580 Path& Path::move(float x_rel, float y_rel)
582 return addSegment(VG_MOVE_TO_REL, x_rel, y_rel);
585 //----------------------------------------------------------------------------
586 Path& Path::lineTo(float x_abs, float y_abs)
588 return addSegment(VG_LINE_TO_ABS, x_abs, y_abs);
591 //----------------------------------------------------------------------------
592 Path& Path::line(float x_rel, float y_rel)
594 return addSegment(VG_LINE_TO_REL, x_rel, y_rel);
597 //----------------------------------------------------------------------------
598 Path& Path::horizTo(float x_abs)
600 return addSegment(VG_HLINE_TO_ABS, x_abs);
603 //----------------------------------------------------------------------------
604 Path& Path::horiz(float x_rel)
606 return addSegment(VG_HLINE_TO_REL, x_rel);
609 //----------------------------------------------------------------------------
610 Path& Path::vertTo(float y_abs)
612 return addSegment(VG_VLINE_TO_ABS, y_abs);
615 //----------------------------------------------------------------------------
616 Path& Path::vert(float y_rel)
618 return addSegment(VG_VLINE_TO_REL, y_rel);
621 //----------------------------------------------------------------------------
624 return addSegment(VG_CLOSE_PATH);
627 //----------------------------------------------------------------------------
628 void Path::childRemoved(SGPropertyNode* child)
633 //----------------------------------------------------------------------------
634 void Path::childChanged(SGPropertyNode* child)
636 if( child->getParent() != _node )
639 if( child->getNameString() == "cmd" )
640 _attributes_dirty |= CMDS;
641 else if( child->getNameString() == "coord" )
642 _attributes_dirty |= COORDS;
645 //----------------------------------------------------------------------------
646 std::vector<float> splitAndConvert(const char del[], const std::string& str)
648 std::vector<float> values;
652 pos = str.find_first_not_of(del, pos);
653 if( pos == std::string::npos )
657 float val = strtod(&str[pos], &end);
658 if( end == &str[pos] || !end )
661 values.push_back(val);
667 } // namespace canvas
668 } // namespace simgear