1 // An image 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 "CanvasImage.hxx"
21 #include <simgear/canvas/Canvas.hxx>
22 #include <simgear/canvas/CanvasMgr.hxx>
23 #include <simgear/canvas/CanvasSystemAdapter.hxx>
24 #include <simgear/canvas/MouseEvent.hxx>
25 #include <simgear/scene/util/OsgMath.hxx>
26 #include <simgear/scene/util/parse_color.hxx>
27 #include <simgear/misc/sg_path.hxx>
30 #include <osg/Geometry>
31 #include <osg/PrimitiveSet>
33 #include <boost/algorithm/string/predicate.hpp>
40 * Callback to enable/disable rendering of canvas displayed inside windows or
44 public osg::Drawable::CullCallback
47 CullCallback(const CanvasWeakPtr& canvas);
51 CanvasWeakPtr _canvas;
52 mutable bool _cull_next_frame;
54 virtual bool cull( osg::NodeVisitor* nv,
55 osg::Drawable* drawable,
56 osg::RenderInfo* renderInfo ) const;
59 //----------------------------------------------------------------------------
60 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
62 _cull_next_frame( false )
67 //----------------------------------------------------------------------------
68 void CullCallback::cullNextFrame()
70 _cull_next_frame = true;
73 //----------------------------------------------------------------------------
74 bool CullCallback::cull( osg::NodeVisitor* nv,
75 osg::Drawable* drawable,
76 osg::RenderInfo* renderInfo ) const
78 if( !_canvas.expired() )
79 _canvas.lock()->enableRendering();
81 if( !_cull_next_frame )
82 // TODO check if window/image should be culled
85 _cull_next_frame = false;
89 //----------------------------------------------------------------------------
90 Image::Image( const CanvasWeakPtr& canvas,
91 const SGPropertyNode_ptr& node,
92 const Style& parent_style,
94 Element(canvas, node, parent_style, parent),
95 _texture(new osg::Texture2D),
96 _node_src_rect( node->getNode("source", 0, true) ),
100 _geom = new osg::Geometry;
101 _geom->setUseDisplayList(false);
103 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
104 stateSet->setTextureAttributeAndModes(0, _texture.get());
105 stateSet->setDataVariance(osg::Object::STATIC);
107 // allocate arrays for the image
108 _vertices = new osg::Vec3Array(4);
109 _vertices->setDataVariance(osg::Object::DYNAMIC);
110 _geom->setVertexArray(_vertices);
112 _texCoords = new osg::Vec2Array(4);
113 _texCoords->setDataVariance(osg::Object::DYNAMIC);
114 _geom->setTexCoordArray(0, _texCoords);
116 _colors = new osg::Vec4Array(1);
117 _colors->setDataVariance(osg::Object::DYNAMIC);
118 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
119 _geom->setColorArray(_colors);
121 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
122 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
123 _prim->setDataVariance(osg::Object::DYNAMIC);
124 _geom->addPrimitiveSet(_prim);
128 if( !isInit<Image>() )
130 addStyle("fill", "color", &Image::setFill);
131 addStyle("slice", "", &Image::setSlice);
132 addStyle("slice-width", "numeric", &Image::setSliceWidth);
133 addStyle("outset", "", &Image::setOutset);
136 setFill("#ffffff"); // TODO how should we handle default values?
141 //----------------------------------------------------------------------------
147 //----------------------------------------------------------------------------
148 void Image::update(double dt)
152 if( !_attributes_dirty )
155 const SGRect<int>& tex_dim = getTextureDimensions();
157 // http://www.w3.org/TR/css3-background/#border-image-slice
159 // The ‘fill’ keyword, if present, causes the middle part of the image to be
160 // preserved. (By default it is discarded, i.e., treated as empty.)
161 bool fill = (_slice.getKeyword() == "fill");
163 if( _attributes_dirty & DEST_SIZE )
165 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
167 if( num_vertices != _prim->getNumPrimitives() )
169 _vertices->resize(num_vertices);
170 _texCoords->resize(num_vertices);
171 _prim->setCount(num_vertices);
174 _attributes_dirty |= SRC_RECT;
177 // http://www.w3.org/TR/css3-background/#border-image-outset
178 SGRect<float> region = _region;
179 if( _outset.isValid() )
181 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
182 region.t() -= outset.t;
183 region.r() += outset.r;
184 region.b() += outset.b;
185 region.l() -= outset.l;
188 if( !_slice.isValid() )
190 setQuad(0, region.getMin(), region.getMax());
195 Image slice, 9-scale, whatever it is called. The four corner images
196 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
197 fill the remaining space up to the specified size.
201 -------------------- - y[0]
203 -------------------- - y[1]
210 -------------------- - y[2]
212 -------------------- - y[3]
215 const CSSBorder::Offsets& slice =
216 (_slice_width.isValid() ? _slice_width : _slice)
217 .getAbsOffsets(tex_dim);
220 region.l() + slice.l,
221 region.r() - slice.r,
226 region.t() + slice.t,
227 region.b() - slice.b,
232 for(int ix = 0; ix < 3; ++ix)
233 for(int iy = 0; iy < 3; ++iy)
235 if( ix == 1 && iy == 1 && !fill )
236 // The ‘fill’ keyword, if present, causes the middle part of the
237 // image to be filled.
241 SGVec2f(x[ix ], y[iy ]),
242 SGVec2f(x[ix + 1], y[iy + 1]) );
247 _attributes_dirty &= ~DEST_SIZE;
249 setBoundingBox(_geom->getBound());
252 if( _attributes_dirty & SRC_RECT )
254 SGRect<float> src_rect = _src_rect;
255 if( !_node_src_rect->getBoolValue("normalized", true) )
257 src_rect.t() /= tex_dim.height();
258 src_rect.r() /= tex_dim.width();
259 src_rect.b() /= tex_dim.height();
260 src_rect.l() /= tex_dim.width();
263 // Image coordinate systems y-axis is flipped
264 std::swap(src_rect.t(), src_rect.b());
266 if( !_slice.isValid() )
268 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
272 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
275 src_rect.l() + slice.l,
276 src_rect.r() - slice.r,
281 src_rect.t() - slice.t,
282 src_rect.b() + slice.b,
287 for(int ix = 0; ix < 3; ++ix)
288 for(int iy = 0; iy < 3; ++iy)
290 if( ix == 1 && iy == 1 && !fill )
291 // The ‘fill’ keyword, if present, causes the middle part of the
292 // image to be filled.
296 SGVec2f(x[ix ], y[iy ]),
297 SGVec2f(x[ix + 1], y[iy + 1]) );
302 _attributes_dirty &= ~SRC_RECT;
306 //----------------------------------------------------------------------------
307 void Image::valueChanged(SGPropertyNode* child)
309 // If the image is switched from invisible to visible, and it shows a
310 // canvas, we need to delay showing it by one frame to ensure the canvas is
311 // updated before the image is displayed.
313 // As canvas::Element handles and filters changes to the "visible" property
314 // we can not check this in Image::childChanged but instead have to override
315 // Element::valueChanged.
317 && child->getParent() == _node
318 && child->getNameString() == "visible"
319 && child->getBoolValue() )
321 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
326 Element::valueChanged(child);
329 //----------------------------------------------------------------------------
330 void Image::setSrcCanvas(CanvasPtr canvas)
332 if( !_src_canvas.expired() )
333 _src_canvas.lock()->removeParentCanvas(_canvas);
334 if( !_canvas.expired() )
335 _canvas.lock()->removeChildCanvas(_src_canvas);
337 _src_canvas = canvas;
338 _geom->getOrCreateStateSet()
339 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
340 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
342 if( !_src_canvas.expired() )
344 setupDefaultDimensions();
345 _src_canvas.lock()->addParentCanvas(_canvas);
347 if( !_canvas.expired() )
348 _canvas.lock()->addChildCanvas(_src_canvas);
352 //----------------------------------------------------------------------------
353 CanvasWeakPtr Image::getSrcCanvas() const
358 //----------------------------------------------------------------------------
359 void Image::setImage(osg::Image *img)
362 setSrcCanvas( CanvasPtr() );
364 _texture->setImage(img);
365 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
366 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
367 _geom->getOrCreateStateSet()
368 ->setTextureAttributeAndModes(0, _texture);
371 setupDefaultDimensions();
374 //----------------------------------------------------------------------------
375 void Image::setFill(const std::string& fill)
378 if( !parseColor(fill, color) )
381 _colors->front() = color;
385 //----------------------------------------------------------------------------
386 void Image::setSlice(const std::string& slice)
388 _slice = CSSBorder::parse(slice);
389 _attributes_dirty |= SRC_RECT | DEST_SIZE;
392 //----------------------------------------------------------------------------
393 void Image::setSliceWidth(const std::string& width)
395 _slice_width = CSSBorder::parse(width);
396 _attributes_dirty |= DEST_SIZE;
399 //----------------------------------------------------------------------------
400 void Image::setOutset(const std::string& outset)
402 _outset = CSSBorder::parse(outset);
403 _attributes_dirty |= DEST_SIZE;
406 //----------------------------------------------------------------------------
407 const SGRect<float>& Image::getRegion() const
412 //----------------------------------------------------------------------------
413 bool Image::handleEvent(EventPtr event)
415 bool handled = Element::handleEvent(event);
417 CanvasPtr src_canvas = _src_canvas.lock();
421 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
424 mouse_event.reset( new MouseEvent(*mouse_event) );
427 mouse_event->client_pos = mouse_event->local_pos
428 - toOsg(_region.getMin());
430 osg::Vec2f size(_region.width(), _region.height());
431 if( _outset.isValid() )
433 CSSBorder::Offsets outset =
434 _outset.getAbsOffsets(getTextureDimensions());
436 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
437 size.x() += outset.l + outset.r;
438 size.y() += outset.t + outset.b;
441 // Scale event pos according to canvas view size vs. displayed/screen size
442 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
443 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
444 mouse_event->local_pos = mouse_event->client_pos;
447 return handled || src_canvas->handleMouseEvent(mouse_event);
450 //----------------------------------------------------------------------------
451 void Image::childChanged(SGPropertyNode* child)
453 const std::string& name = child->getNameString();
455 if( child->getParent() == _node_src_rect )
457 _attributes_dirty |= SRC_RECT;
460 _src_rect.setLeft( child->getFloatValue() );
461 else if( name == "right" )
462 _src_rect.setRight( child->getFloatValue() );
463 else if( name == "top" )
464 _src_rect.setTop( child->getFloatValue() );
465 else if( name == "bottom" )
466 _src_rect.setBottom( child->getFloatValue() );
470 else if( child->getParent() != _node )
475 _region.setX( child->getFloatValue() );
476 _attributes_dirty |= DEST_SIZE;
478 else if( name == "y" )
480 _region.setY( child->getFloatValue() );
481 _attributes_dirty |= DEST_SIZE;
483 else if( name == "size" )
485 if( child->getIndex() == 0 )
486 _region.setWidth( child->getFloatValue() );
488 _region.setHeight( child->getFloatValue() );
490 _attributes_dirty |= DEST_SIZE;
492 else if( name == "file" )
494 static const std::string CANVAS_PROTOCOL = "canvas://";
495 const std::string& path = child->getStringValue();
497 CanvasPtr canvas = _canvas.lock();
500 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
504 if( boost::starts_with(path, CANVAS_PROTOCOL) )
506 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
509 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
513 const SGPropertyNode* canvas_node =
514 canvas_mgr->getPropertyRoot()
516 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
519 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
523 // TODO add support for other means of addressing canvases (eg. by
525 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
528 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
532 setSrcCanvas(src_canvas);
536 setImage( canvas->getSystemAdapter()->getImage(path) );
541 //----------------------------------------------------------------------------
542 void Image::setupDefaultDimensions()
544 if( !_src_rect.width() || !_src_rect.height() )
546 // Show whole image by default
547 _node_src_rect->setBoolValue("normalized", true);
548 _node_src_rect->setFloatValue("right", 1);
549 _node_src_rect->setFloatValue("bottom", 1);
552 if( !_region.width() || !_region.height() )
554 // Default to image size.
555 // TODO handle showing only part of image?
556 const SGRect<int>& dim = getTextureDimensions();
557 _node->setFloatValue("size[0]", dim.width());
558 _node->setFloatValue("size[1]", dim.height());
562 //----------------------------------------------------------------------------
563 SGRect<int> Image::getTextureDimensions() const
565 CanvasPtr canvas = _src_canvas.lock();
566 SGRect<int> dim(0,0);
568 // Use canvas/image dimensions rather than texture dimensions, as they could
569 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
572 dim.setRight( canvas->getViewWidth() );
573 dim.setBottom( canvas->getViewHeight() );
577 osg::Image* img = _texture->getImage();
581 dim.setRight( img->s() );
582 dim.setBottom( img->t() );
589 //----------------------------------------------------------------------------
590 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
593 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
594 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
595 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
596 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
599 //----------------------------------------------------------------------------
600 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
603 (*_texCoords)[i + 0].set(tl.x(), tl.y());
604 (*_texCoords)[i + 1].set(br.x(), tl.y());
605 (*_texCoords)[i + 2].set(br.x(), br.y());
606 (*_texCoords)[i + 3].set(tl.x(), br.y());
609 } // namespace canvas
610 } // namespace simgear