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 const std::string Image::TYPE_NAME = "image";
92 //----------------------------------------------------------------------------
93 Image::Image( const CanvasWeakPtr& canvas,
94 const SGPropertyNode_ptr& node,
95 const Style& parent_style,
97 Element(canvas, node, parent_style, parent),
98 _texture(new osg::Texture2D),
99 _node_src_rect( node->getNode("source", 0, true) ),
103 _geom = new osg::Geometry;
104 _geom->setUseDisplayList(false);
106 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
107 stateSet->setTextureAttributeAndModes(0, _texture.get());
108 stateSet->setDataVariance(osg::Object::STATIC);
110 // allocate arrays for the image
111 _vertices = new osg::Vec3Array(4);
112 _vertices->setDataVariance(osg::Object::DYNAMIC);
113 _geom->setVertexArray(_vertices);
115 _texCoords = new osg::Vec2Array(4);
116 _texCoords->setDataVariance(osg::Object::DYNAMIC);
117 _geom->setTexCoordArray(0, _texCoords);
119 _colors = new osg::Vec4Array(1);
120 _colors->setDataVariance(osg::Object::DYNAMIC);
121 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
122 _geom->setColorArray(_colors);
124 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
125 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
126 _prim->setDataVariance(osg::Object::DYNAMIC);
127 _geom->addPrimitiveSet(_prim);
131 if( !isInit<Image>() )
133 addStyle("fill", "color", &Image::setFill);
134 addStyle("slice", "", &Image::setSlice);
135 addStyle("slice-width", "", &Image::setSliceWidth);
136 addStyle("outset", "", &Image::setOutset);
139 setFill("#ffffff"); // TODO how should we handle default values?
144 //----------------------------------------------------------------------------
150 //----------------------------------------------------------------------------
151 void Image::update(double dt)
155 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
157 _geom->getOrCreateStateSet()
158 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
160 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
162 if( (_attributes_dirty & SRC_CANVAS)
163 // check if texture has changed (eg. due to resizing)
164 || (canvas && texture != canvas->getTexture()) )
166 _geom->getOrCreateStateSet()
167 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
169 if( !canvas || canvas->isInit() )
170 _attributes_dirty &= ~SRC_CANVAS;
173 if( !_attributes_dirty )
176 const SGRect<int>& tex_dim = getTextureDimensions();
178 // http://www.w3.org/TR/css3-background/#border-image-slice
180 // The ‘fill’ keyword, if present, causes the middle part of the image to be
181 // preserved. (By default it is discarded, i.e., treated as empty.)
182 bool fill = (_slice.getKeyword() == "fill");
184 if( _attributes_dirty & DEST_SIZE )
186 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
188 if( num_vertices != _prim->getNumPrimitives() )
190 _vertices->resize(num_vertices);
191 _texCoords->resize(num_vertices);
192 _prim->setCount(num_vertices);
195 _attributes_dirty |= SRC_RECT;
198 // http://www.w3.org/TR/css3-background/#border-image-outset
199 SGRect<float> region = _region;
200 if( _outset.isValid() )
202 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
203 region.t() -= outset.t;
204 region.r() += outset.r;
205 region.b() += outset.b;
206 region.l() -= outset.l;
209 if( !_slice.isValid() )
211 setQuad(0, region.getMin(), region.getMax());
216 Image slice, 9-scale, whatever it is called. The four corner images
217 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
218 fill the remaining space up to the specified size.
222 -------------------- - y[0]
224 -------------------- - y[1]
231 -------------------- - y[2]
233 -------------------- - y[3]
236 const CSSBorder::Offsets& slice =
237 (_slice_width.isValid() ? _slice_width : _slice)
238 .getAbsOffsets(tex_dim);
241 region.l() + slice.l,
242 region.r() - slice.r,
247 region.t() + slice.t,
248 region.b() - slice.b,
253 for(int ix = 0; ix < 3; ++ix)
254 for(int iy = 0; iy < 3; ++iy)
256 if( ix == 1 && iy == 1 && !fill )
257 // The ‘fill’ keyword, if present, causes the middle part of the
258 // image to be filled.
262 SGVec2f(x[ix ], y[iy ]),
263 SGVec2f(x[ix + 1], y[iy + 1]) );
268 _attributes_dirty &= ~DEST_SIZE;
270 setBoundingBox(_geom->getBound());
273 if( _attributes_dirty & SRC_RECT )
275 SGRect<float> src_rect = _src_rect;
276 if( !_node_src_rect->getBoolValue("normalized", true) )
278 src_rect.t() /= tex_dim.height();
279 src_rect.r() /= tex_dim.width();
280 src_rect.b() /= tex_dim.height();
281 src_rect.l() /= tex_dim.width();
284 // Image coordinate systems y-axis is flipped
285 std::swap(src_rect.t(), src_rect.b());
287 if( !_slice.isValid() )
289 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
293 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
296 src_rect.l() + slice.l,
297 src_rect.r() - slice.r,
302 src_rect.t() - slice.t,
303 src_rect.b() + slice.b,
308 for(int ix = 0; ix < 3; ++ix)
309 for(int iy = 0; iy < 3; ++iy)
311 if( ix == 1 && iy == 1 && !fill )
312 // The ‘fill’ keyword, if present, causes the middle part of the
313 // image to be filled.
317 SGVec2f(x[ix ], y[iy ]),
318 SGVec2f(x[ix + 1], y[iy + 1]) );
323 _attributes_dirty &= ~SRC_RECT;
327 //----------------------------------------------------------------------------
328 void Image::valueChanged(SGPropertyNode* child)
330 // If the image is switched from invisible to visible, and it shows a
331 // canvas, we need to delay showing it by one frame to ensure the canvas is
332 // updated before the image is displayed.
334 // As canvas::Element handles and filters changes to the "visible" property
335 // we can not check this in Image::childChanged but instead have to override
336 // Element::valueChanged.
338 && child->getParent() == _node
339 && child->getNameString() == "visible"
340 && child->getBoolValue() )
342 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
347 Element::valueChanged(child);
350 //----------------------------------------------------------------------------
351 void Image::setSrcCanvas(CanvasPtr canvas)
353 if( !_src_canvas.expired() )
354 _src_canvas.lock()->removeParentCanvas(_canvas);
355 if( !_canvas.expired() )
356 _canvas.lock()->removeChildCanvas(_src_canvas);
358 _src_canvas = canvas;
359 _attributes_dirty |= SRC_CANVAS;
360 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
362 if( !_src_canvas.expired() )
364 setupDefaultDimensions();
365 _src_canvas.lock()->addParentCanvas(_canvas);
367 if( !_canvas.expired() )
368 _canvas.lock()->addChildCanvas(_src_canvas);
372 //----------------------------------------------------------------------------
373 CanvasWeakPtr Image::getSrcCanvas() const
378 //----------------------------------------------------------------------------
379 void Image::setImage(osg::Image *img)
382 setSrcCanvas( CanvasPtr() );
384 _texture->setResizeNonPowerOfTwoHint(false);
385 _texture->setImage(img);
386 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
387 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
388 _geom->getOrCreateStateSet()
389 ->setTextureAttributeAndModes(0, _texture);
392 setupDefaultDimensions();
395 //----------------------------------------------------------------------------
396 void Image::setFill(const std::string& fill)
399 if( !parseColor(fill, color) )
402 _colors->front() = color;
406 //----------------------------------------------------------------------------
407 void Image::setSlice(const std::string& slice)
409 _slice = CSSBorder::parse(slice);
410 _attributes_dirty |= SRC_RECT | DEST_SIZE;
413 //----------------------------------------------------------------------------
414 void Image::setSliceWidth(const std::string& width)
416 _slice_width = CSSBorder::parse(width);
417 _attributes_dirty |= DEST_SIZE;
420 //----------------------------------------------------------------------------
421 void Image::setOutset(const std::string& outset)
423 _outset = CSSBorder::parse(outset);
424 _attributes_dirty |= DEST_SIZE;
427 //----------------------------------------------------------------------------
428 const SGRect<float>& Image::getRegion() const
433 //----------------------------------------------------------------------------
434 bool Image::handleEvent(EventPtr event)
436 bool handled = Element::handleEvent(event);
438 CanvasPtr src_canvas = _src_canvas.lock();
442 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
445 mouse_event.reset( new MouseEvent(*mouse_event) );
448 mouse_event->client_pos = mouse_event->local_pos
449 - toOsg(_region.getMin());
451 osg::Vec2f size(_region.width(), _region.height());
452 if( _outset.isValid() )
454 CSSBorder::Offsets outset =
455 _outset.getAbsOffsets(getTextureDimensions());
457 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
458 size.x() += outset.l + outset.r;
459 size.y() += outset.t + outset.b;
462 // Scale event pos according to canvas view size vs. displayed/screen size
463 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
464 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
465 mouse_event->local_pos = mouse_event->client_pos;
468 return handled || src_canvas->handleMouseEvent(mouse_event);
471 //----------------------------------------------------------------------------
472 void Image::childChanged(SGPropertyNode* child)
474 const std::string& name = child->getNameString();
476 if( child->getParent() == _node_src_rect )
478 _attributes_dirty |= SRC_RECT;
481 _src_rect.setLeft( child->getFloatValue() );
482 else if( name == "right" )
483 _src_rect.setRight( child->getFloatValue() );
484 else if( name == "top" )
485 _src_rect.setTop( child->getFloatValue() );
486 else if( name == "bottom" )
487 _src_rect.setBottom( child->getFloatValue() );
491 else if( child->getParent() != _node )
496 _region.setX( child->getFloatValue() );
497 _attributes_dirty |= DEST_SIZE;
499 else if( name == "y" )
501 _region.setY( child->getFloatValue() );
502 _attributes_dirty |= DEST_SIZE;
504 else if( name == "size" )
506 if( child->getIndex() == 0 )
507 _region.setWidth( child->getFloatValue() );
509 _region.setHeight( child->getFloatValue() );
511 _attributes_dirty |= DEST_SIZE;
513 else if( name == "file" )
515 static const std::string CANVAS_PROTOCOL = "canvas://";
516 const std::string& path = child->getStringValue();
518 CanvasPtr canvas = _canvas.lock();
521 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
525 if( boost::starts_with(path, CANVAS_PROTOCOL) )
527 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
530 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
534 const SGPropertyNode* canvas_node =
535 canvas_mgr->getPropertyRoot()
537 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
540 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
544 // TODO add support for other means of addressing canvases (eg. by
546 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
549 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
553 setSrcCanvas(src_canvas);
557 setImage( canvas->getSystemAdapter()->getImage(path) );
562 //----------------------------------------------------------------------------
563 void Image::setupDefaultDimensions()
565 if( !_src_rect.width() || !_src_rect.height() )
567 // Show whole image by default
568 _node_src_rect->setBoolValue("normalized", true);
569 _node_src_rect->setFloatValue("right", 1);
570 _node_src_rect->setFloatValue("bottom", 1);
573 if( !_region.width() || !_region.height() )
575 // Default to image size.
576 // TODO handle showing only part of image?
577 const SGRect<int>& dim = getTextureDimensions();
578 _node->setFloatValue("size[0]", dim.width());
579 _node->setFloatValue("size[1]", dim.height());
583 //----------------------------------------------------------------------------
584 SGRect<int> Image::getTextureDimensions() const
586 CanvasPtr canvas = _src_canvas.lock();
587 SGRect<int> dim(0,0);
589 // Use canvas/image dimensions rather than texture dimensions, as they could
590 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
593 dim.setRight( canvas->getViewWidth() );
594 dim.setBottom( canvas->getViewHeight() );
598 osg::Image* img = _texture->getImage();
602 dim.setRight( img->s() );
603 dim.setBottom( img->t() );
610 //----------------------------------------------------------------------------
611 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
614 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
615 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
616 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
617 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
620 //----------------------------------------------------------------------------
621 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
624 (*_texCoords)[i + 0].set(tl.x(), tl.y());
625 (*_texCoords)[i + 1].set(br.x(), tl.y());
626 (*_texCoords)[i + 2].set(br.x(), br.y());
627 (*_texCoords)[i + 3].set(tl.x(), br.y());
630 } // namespace canvas
631 } // namespace simgear