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 CanvasPtr src_canvas = _src_canvas.lock(),
354 self_canvas = _canvas.lock();
357 src_canvas->removeParentCanvas(self_canvas);
359 self_canvas->removeChildCanvas(src_canvas);
361 _src_canvas = src_canvas = canvas;
362 _attributes_dirty |= SRC_CANVAS;
363 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
367 setupDefaultDimensions();
371 self_canvas->addChildCanvas(src_canvas);
372 src_canvas->addParentCanvas(self_canvas);
377 //----------------------------------------------------------------------------
378 CanvasWeakPtr Image::getSrcCanvas() const
383 //----------------------------------------------------------------------------
384 void Image::setImage(osg::Image *img)
387 setSrcCanvas( CanvasPtr() );
389 _texture->setResizeNonPowerOfTwoHint(false);
390 _texture->setImage(img);
391 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
392 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
393 _geom->getOrCreateStateSet()
394 ->setTextureAttributeAndModes(0, _texture);
397 setupDefaultDimensions();
400 //----------------------------------------------------------------------------
401 void Image::setFill(const std::string& fill)
404 if( !parseColor(fill, color) )
407 _colors->front() = color;
411 //----------------------------------------------------------------------------
412 void Image::setSlice(const std::string& slice)
414 _slice = CSSBorder::parse(slice);
415 _attributes_dirty |= SRC_RECT | DEST_SIZE;
418 //----------------------------------------------------------------------------
419 void Image::setSliceWidth(const std::string& width)
421 _slice_width = CSSBorder::parse(width);
422 _attributes_dirty |= DEST_SIZE;
425 //----------------------------------------------------------------------------
426 void Image::setOutset(const std::string& outset)
428 _outset = CSSBorder::parse(outset);
429 _attributes_dirty |= DEST_SIZE;
432 //----------------------------------------------------------------------------
433 const SGRect<float>& Image::getRegion() const
438 //----------------------------------------------------------------------------
439 bool Image::handleEvent(EventPtr event)
441 bool handled = Element::handleEvent(event);
443 CanvasPtr src_canvas = _src_canvas.lock();
447 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
450 mouse_event.reset( new MouseEvent(*mouse_event) );
453 mouse_event->client_pos = mouse_event->local_pos
454 - toOsg(_region.getMin());
456 osg::Vec2f size(_region.width(), _region.height());
457 if( _outset.isValid() )
459 CSSBorder::Offsets outset =
460 _outset.getAbsOffsets(getTextureDimensions());
462 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
463 size.x() += outset.l + outset.r;
464 size.y() += outset.t + outset.b;
467 // Scale event pos according to canvas view size vs. displayed/screen size
468 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
469 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
470 mouse_event->local_pos = mouse_event->client_pos;
473 return handled || src_canvas->handleMouseEvent(mouse_event);
476 //----------------------------------------------------------------------------
477 void Image::childChanged(SGPropertyNode* child)
479 const std::string& name = child->getNameString();
481 if( child->getParent() == _node_src_rect )
483 _attributes_dirty |= SRC_RECT;
486 _src_rect.setLeft( child->getFloatValue() );
487 else if( name == "right" )
488 _src_rect.setRight( child->getFloatValue() );
489 else if( name == "top" )
490 _src_rect.setTop( child->getFloatValue() );
491 else if( name == "bottom" )
492 _src_rect.setBottom( child->getFloatValue() );
496 else if( child->getParent() != _node )
501 _region.setX( child->getFloatValue() );
502 _attributes_dirty |= DEST_SIZE;
504 else if( name == "y" )
506 _region.setY( child->getFloatValue() );
507 _attributes_dirty |= DEST_SIZE;
509 else if( name == "size" )
511 if( child->getIndex() == 0 )
512 _region.setWidth( child->getFloatValue() );
514 _region.setHeight( child->getFloatValue() );
516 _attributes_dirty |= DEST_SIZE;
518 else if( name == "file" )
520 static const std::string CANVAS_PROTOCOL = "canvas://";
521 const std::string& path = child->getStringValue();
523 CanvasPtr canvas = _canvas.lock();
526 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
530 if( boost::starts_with(path, CANVAS_PROTOCOL) )
532 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
535 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
539 const SGPropertyNode* canvas_node =
540 canvas_mgr->getPropertyRoot()
542 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
545 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
549 // TODO add support for other means of addressing canvases (eg. by
551 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
554 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
558 setSrcCanvas(src_canvas);
562 setImage( canvas->getSystemAdapter()->getImage(path) );
567 //----------------------------------------------------------------------------
568 void Image::setupDefaultDimensions()
570 if( !_src_rect.width() || !_src_rect.height() )
572 // Show whole image by default
573 _node_src_rect->setBoolValue("normalized", true);
574 _node_src_rect->setFloatValue("right", 1);
575 _node_src_rect->setFloatValue("bottom", 1);
578 if( !_region.width() || !_region.height() )
580 // Default to image size.
581 // TODO handle showing only part of image?
582 const SGRect<int>& dim = getTextureDimensions();
583 _node->setFloatValue("size[0]", dim.width());
584 _node->setFloatValue("size[1]", dim.height());
588 //----------------------------------------------------------------------------
589 SGRect<int> Image::getTextureDimensions() const
591 CanvasPtr canvas = _src_canvas.lock();
592 SGRect<int> dim(0,0);
594 // Use canvas/image dimensions rather than texture dimensions, as they could
595 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
598 dim.setRight( canvas->getViewWidth() );
599 dim.setBottom( canvas->getViewHeight() );
603 osg::Image* img = _texture->getImage();
607 dim.setRight( img->s() );
608 dim.setBottom( img->t() );
615 //----------------------------------------------------------------------------
616 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
619 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
620 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
621 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
622 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
625 //----------------------------------------------------------------------------
626 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
629 (*_texCoords)[i + 0].set(tl.x(), tl.y());
630 (*_texCoords)[i + 1].set(br.x(), tl.y());
631 (*_texCoords)[i + 2].set(br.x(), br.y());
632 (*_texCoords)[i + 3].set(tl.x(), br.y());
635 } // namespace canvas
636 } // namespace simgear