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 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
154 _geom->getOrCreateStateSet()
155 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
157 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
159 if( (_attributes_dirty & SRC_CANVAS)
160 // check if texture has changed (eg. due to resizing)
161 || (canvas && texture != canvas->getTexture()) )
163 _geom->getOrCreateStateSet()
164 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
166 if( !canvas || canvas->isInit() )
167 _attributes_dirty &= ~SRC_CANVAS;
170 if( !_attributes_dirty )
173 const SGRect<int>& tex_dim = getTextureDimensions();
175 // http://www.w3.org/TR/css3-background/#border-image-slice
177 // The ‘fill’ keyword, if present, causes the middle part of the image to be
178 // preserved. (By default it is discarded, i.e., treated as empty.)
179 bool fill = (_slice.getKeyword() == "fill");
181 if( _attributes_dirty & DEST_SIZE )
183 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
185 if( num_vertices != _prim->getNumPrimitives() )
187 _vertices->resize(num_vertices);
188 _texCoords->resize(num_vertices);
189 _prim->setCount(num_vertices);
192 _attributes_dirty |= SRC_RECT;
195 // http://www.w3.org/TR/css3-background/#border-image-outset
196 SGRect<float> region = _region;
197 if( _outset.isValid() )
199 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
200 region.t() -= outset.t;
201 region.r() += outset.r;
202 region.b() += outset.b;
203 region.l() -= outset.l;
206 if( !_slice.isValid() )
208 setQuad(0, region.getMin(), region.getMax());
213 Image slice, 9-scale, whatever it is called. The four corner images
214 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
215 fill the remaining space up to the specified size.
219 -------------------- - y[0]
221 -------------------- - y[1]
228 -------------------- - y[2]
230 -------------------- - y[3]
233 const CSSBorder::Offsets& slice =
234 (_slice_width.isValid() ? _slice_width : _slice)
235 .getAbsOffsets(tex_dim);
238 region.l() + slice.l,
239 region.r() - slice.r,
244 region.t() + slice.t,
245 region.b() - slice.b,
250 for(int ix = 0; ix < 3; ++ix)
251 for(int iy = 0; iy < 3; ++iy)
253 if( ix == 1 && iy == 1 && !fill )
254 // The ‘fill’ keyword, if present, causes the middle part of the
255 // image to be filled.
259 SGVec2f(x[ix ], y[iy ]),
260 SGVec2f(x[ix + 1], y[iy + 1]) );
265 _attributes_dirty &= ~DEST_SIZE;
267 setBoundingBox(_geom->getBound());
270 if( _attributes_dirty & SRC_RECT )
272 SGRect<float> src_rect = _src_rect;
273 if( !_node_src_rect->getBoolValue("normalized", true) )
275 src_rect.t() /= tex_dim.height();
276 src_rect.r() /= tex_dim.width();
277 src_rect.b() /= tex_dim.height();
278 src_rect.l() /= tex_dim.width();
281 // Image coordinate systems y-axis is flipped
282 std::swap(src_rect.t(), src_rect.b());
284 if( !_slice.isValid() )
286 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
290 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
293 src_rect.l() + slice.l,
294 src_rect.r() - slice.r,
299 src_rect.t() - slice.t,
300 src_rect.b() + slice.b,
305 for(int ix = 0; ix < 3; ++ix)
306 for(int iy = 0; iy < 3; ++iy)
308 if( ix == 1 && iy == 1 && !fill )
309 // The ‘fill’ keyword, if present, causes the middle part of the
310 // image to be filled.
314 SGVec2f(x[ix ], y[iy ]),
315 SGVec2f(x[ix + 1], y[iy + 1]) );
320 _attributes_dirty &= ~SRC_RECT;
324 //----------------------------------------------------------------------------
325 void Image::valueChanged(SGPropertyNode* child)
327 // If the image is switched from invisible to visible, and it shows a
328 // canvas, we need to delay showing it by one frame to ensure the canvas is
329 // updated before the image is displayed.
331 // As canvas::Element handles and filters changes to the "visible" property
332 // we can not check this in Image::childChanged but instead have to override
333 // Element::valueChanged.
335 && child->getParent() == _node
336 && child->getNameString() == "visible"
337 && child->getBoolValue() )
339 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
344 Element::valueChanged(child);
347 //----------------------------------------------------------------------------
348 void Image::setSrcCanvas(CanvasPtr canvas)
350 if( !_src_canvas.expired() )
351 _src_canvas.lock()->removeParentCanvas(_canvas);
352 if( !_canvas.expired() )
353 _canvas.lock()->removeChildCanvas(_src_canvas);
355 _src_canvas = canvas;
356 _attributes_dirty |= SRC_CANVAS;
357 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
359 if( !_src_canvas.expired() )
361 setupDefaultDimensions();
362 _src_canvas.lock()->addParentCanvas(_canvas);
364 if( !_canvas.expired() )
365 _canvas.lock()->addChildCanvas(_src_canvas);
369 //----------------------------------------------------------------------------
370 CanvasWeakPtr Image::getSrcCanvas() const
375 //----------------------------------------------------------------------------
376 void Image::setImage(osg::Image *img)
379 setSrcCanvas( CanvasPtr() );
381 _texture->setResizeNonPowerOfTwoHint(false);
382 _texture->setImage(img);
383 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
384 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
385 _geom->getOrCreateStateSet()
386 ->setTextureAttributeAndModes(0, _texture);
389 setupDefaultDimensions();
392 //----------------------------------------------------------------------------
393 void Image::setFill(const std::string& fill)
396 if( !parseColor(fill, color) )
399 _colors->front() = color;
403 //----------------------------------------------------------------------------
404 void Image::setSlice(const std::string& slice)
406 _slice = CSSBorder::parse(slice);
407 _attributes_dirty |= SRC_RECT | DEST_SIZE;
410 //----------------------------------------------------------------------------
411 void Image::setSliceWidth(const std::string& width)
413 _slice_width = CSSBorder::parse(width);
414 _attributes_dirty |= DEST_SIZE;
417 //----------------------------------------------------------------------------
418 void Image::setOutset(const std::string& outset)
420 _outset = CSSBorder::parse(outset);
421 _attributes_dirty |= DEST_SIZE;
424 //----------------------------------------------------------------------------
425 const SGRect<float>& Image::getRegion() const
430 //----------------------------------------------------------------------------
431 bool Image::handleEvent(EventPtr event)
433 bool handled = Element::handleEvent(event);
435 CanvasPtr src_canvas = _src_canvas.lock();
439 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
442 mouse_event.reset( new MouseEvent(*mouse_event) );
445 mouse_event->client_pos = mouse_event->local_pos
446 - toOsg(_region.getMin());
448 osg::Vec2f size(_region.width(), _region.height());
449 if( _outset.isValid() )
451 CSSBorder::Offsets outset =
452 _outset.getAbsOffsets(getTextureDimensions());
454 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
455 size.x() += outset.l + outset.r;
456 size.y() += outset.t + outset.b;
459 // Scale event pos according to canvas view size vs. displayed/screen size
460 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
461 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
462 mouse_event->local_pos = mouse_event->client_pos;
465 return handled || src_canvas->handleMouseEvent(mouse_event);
468 //----------------------------------------------------------------------------
469 void Image::childChanged(SGPropertyNode* child)
471 const std::string& name = child->getNameString();
473 if( child->getParent() == _node_src_rect )
475 _attributes_dirty |= SRC_RECT;
478 _src_rect.setLeft( child->getFloatValue() );
479 else if( name == "right" )
480 _src_rect.setRight( child->getFloatValue() );
481 else if( name == "top" )
482 _src_rect.setTop( child->getFloatValue() );
483 else if( name == "bottom" )
484 _src_rect.setBottom( child->getFloatValue() );
488 else if( child->getParent() != _node )
493 _region.setX( child->getFloatValue() );
494 _attributes_dirty |= DEST_SIZE;
496 else if( name == "y" )
498 _region.setY( child->getFloatValue() );
499 _attributes_dirty |= DEST_SIZE;
501 else if( name == "size" )
503 if( child->getIndex() == 0 )
504 _region.setWidth( child->getFloatValue() );
506 _region.setHeight( child->getFloatValue() );
508 _attributes_dirty |= DEST_SIZE;
510 else if( name == "file" )
512 static const std::string CANVAS_PROTOCOL = "canvas://";
513 const std::string& path = child->getStringValue();
515 CanvasPtr canvas = _canvas.lock();
518 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
522 if( boost::starts_with(path, CANVAS_PROTOCOL) )
524 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
527 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
531 const SGPropertyNode* canvas_node =
532 canvas_mgr->getPropertyRoot()
534 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
537 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
541 // TODO add support for other means of addressing canvases (eg. by
543 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
546 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
550 setSrcCanvas(src_canvas);
554 setImage( canvas->getSystemAdapter()->getImage(path) );
559 //----------------------------------------------------------------------------
560 void Image::setupDefaultDimensions()
562 if( !_src_rect.width() || !_src_rect.height() )
564 // Show whole image by default
565 _node_src_rect->setBoolValue("normalized", true);
566 _node_src_rect->setFloatValue("right", 1);
567 _node_src_rect->setFloatValue("bottom", 1);
570 if( !_region.width() || !_region.height() )
572 // Default to image size.
573 // TODO handle showing only part of image?
574 const SGRect<int>& dim = getTextureDimensions();
575 _node->setFloatValue("size[0]", dim.width());
576 _node->setFloatValue("size[1]", dim.height());
580 //----------------------------------------------------------------------------
581 SGRect<int> Image::getTextureDimensions() const
583 CanvasPtr canvas = _src_canvas.lock();
584 SGRect<int> dim(0,0);
586 // Use canvas/image dimensions rather than texture dimensions, as they could
587 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
590 dim.setRight( canvas->getViewWidth() );
591 dim.setBottom( canvas->getViewHeight() );
595 osg::Image* img = _texture->getImage();
599 dim.setRight( img->s() );
600 dim.setBottom( img->t() );
607 //----------------------------------------------------------------------------
608 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
611 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
612 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
613 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
614 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
617 //----------------------------------------------------------------------------
618 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
621 (*_texCoords)[i + 0].set(tl.x(), tl.y());
622 (*_texCoords)[i + 1].set(br.x(), tl.y());
623 (*_texCoords)[i + 2].set(br.x(), br.y());
624 (*_texCoords)[i + 3].set(tl.x(), br.y());
627 } // namespace canvas
628 } // namespace simgear