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 void Image::staticInit()
98 addStyle("fill", "color", &Image::setFill);
99 addStyle("slice", "", &Image::setSlice);
100 addStyle("slice-width", "", &Image::setSliceWidth);
101 addStyle("outset", "", &Image::setOutset);
104 //----------------------------------------------------------------------------
105 Image::Image( const CanvasWeakPtr& canvas,
106 const SGPropertyNode_ptr& node,
107 const Style& parent_style,
109 Element(canvas, node, parent_style, parent),
110 _texture(new osg::Texture2D),
111 _node_src_rect( node->getNode("source", 0, true) ),
117 _geom = new osg::Geometry;
118 _geom->setUseDisplayList(false);
120 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
121 stateSet->setTextureAttributeAndModes(0, _texture.get());
122 stateSet->setDataVariance(osg::Object::STATIC);
124 // allocate arrays for the image
125 _vertices = new osg::Vec3Array(4);
126 _vertices->setDataVariance(osg::Object::DYNAMIC);
127 _geom->setVertexArray(_vertices);
129 _texCoords = new osg::Vec2Array(4);
130 _texCoords->setDataVariance(osg::Object::DYNAMIC);
131 _geom->setTexCoordArray(0, _texCoords);
133 _colors = new osg::Vec4Array(1);
134 _colors->setDataVariance(osg::Object::DYNAMIC);
135 _geom->setColorArray(_colors);
136 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
138 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
139 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
140 _prim->setDataVariance(osg::Object::DYNAMIC);
141 _geom->addPrimitiveSet(_prim);
145 setFill("#ffffff"); // TODO how should we handle default values?
149 //----------------------------------------------------------------------------
155 //----------------------------------------------------------------------------
156 void Image::update(double dt)
160 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
162 _geom->getOrCreateStateSet()
163 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
165 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
167 if( (_attributes_dirty & SRC_CANVAS)
168 // check if texture has changed (eg. due to resizing)
169 || (canvas && texture != canvas->getTexture()) )
171 _geom->getOrCreateStateSet()
172 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
174 if( !canvas || canvas->isInit() )
175 _attributes_dirty &= ~SRC_CANVAS;
178 if( !_attributes_dirty )
181 const SGRect<int>& tex_dim = getTextureDimensions();
183 // http://www.w3.org/TR/css3-background/#border-image-slice
185 // The ‘fill’ keyword, if present, causes the middle part of the image to be
186 // preserved. (By default it is discarded, i.e., treated as empty.)
187 bool fill = (_slice.getKeyword() == "fill");
189 if( _attributes_dirty & DEST_SIZE )
191 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
193 if( num_vertices != _prim->getNumPrimitives() )
195 _vertices->resize(num_vertices);
196 _texCoords->resize(num_vertices);
197 _prim->setCount(num_vertices);
200 _attributes_dirty |= SRC_RECT;
203 // http://www.w3.org/TR/css3-background/#border-image-outset
204 SGRect<float> region = _region;
205 if( _outset.isValid() )
207 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
208 region.t() -= outset.t;
209 region.r() += outset.r;
210 region.b() += outset.b;
211 region.l() -= outset.l;
214 if( !_slice.isValid() )
216 setQuad(0, region.getMin(), region.getMax());
221 Image slice, 9-scale, whatever it is called. The four corner images
222 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
223 fill the remaining space up to the specified size.
227 -------------------- - y[0]
229 -------------------- - y[1]
236 -------------------- - y[2]
238 -------------------- - y[3]
241 const CSSBorder::Offsets& slice =
242 (_slice_width.isValid() ? _slice_width : _slice)
243 .getAbsOffsets(tex_dim);
246 region.l() + slice.l,
247 region.r() - slice.r,
252 region.t() + slice.t,
253 region.b() - slice.b,
258 for(int ix = 0; ix < 3; ++ix)
259 for(int iy = 0; iy < 3; ++iy)
261 if( ix == 1 && iy == 1 && !fill )
262 // The ‘fill’ keyword, if present, causes the middle part of the
263 // image to be filled.
267 SGVec2f(x[ix ], y[iy ]),
268 SGVec2f(x[ix + 1], y[iy + 1]) );
273 _attributes_dirty &= ~DEST_SIZE;
275 setBoundingBox(_geom->getBound());
278 if( _attributes_dirty & SRC_RECT )
280 SGRect<float> src_rect = _src_rect;
281 if( !_node_src_rect->getBoolValue("normalized", true) )
283 src_rect.t() /= tex_dim.height();
284 src_rect.r() /= tex_dim.width();
285 src_rect.b() /= tex_dim.height();
286 src_rect.l() /= tex_dim.width();
289 // Image coordinate systems y-axis is flipped
290 std::swap(src_rect.t(), src_rect.b());
292 if( !_slice.isValid() )
294 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
298 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
301 src_rect.l() + slice.l,
302 src_rect.r() - slice.r,
307 src_rect.t() - slice.t,
308 src_rect.b() + slice.b,
313 for(int ix = 0; ix < 3; ++ix)
314 for(int iy = 0; iy < 3; ++iy)
316 if( ix == 1 && iy == 1 && !fill )
317 // The ‘fill’ keyword, if present, causes the middle part of the
318 // image to be filled.
322 SGVec2f(x[ix ], y[iy ]),
323 SGVec2f(x[ix + 1], y[iy + 1]) );
328 _attributes_dirty &= ~SRC_RECT;
332 //----------------------------------------------------------------------------
333 void Image::valueChanged(SGPropertyNode* child)
335 // If the image is switched from invisible to visible, and it shows a
336 // canvas, we need to delay showing it by one frame to ensure the canvas is
337 // updated before the image is displayed.
339 // As canvas::Element handles and filters changes to the "visible" property
340 // we can not check this in Image::childChanged but instead have to override
341 // Element::valueChanged.
343 && child->getParent() == _node
344 && child->getNameString() == "visible"
345 && child->getBoolValue() )
347 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
352 Element::valueChanged(child);
355 //----------------------------------------------------------------------------
356 void Image::setSrcCanvas(CanvasPtr canvas)
358 CanvasPtr src_canvas = _src_canvas.lock(),
359 self_canvas = _canvas.lock();
362 src_canvas->removeParentCanvas(self_canvas);
364 self_canvas->removeChildCanvas(src_canvas);
366 _src_canvas = src_canvas = canvas;
367 _attributes_dirty |= SRC_CANVAS;
368 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
372 setupDefaultDimensions();
376 self_canvas->addChildCanvas(src_canvas);
377 src_canvas->addParentCanvas(self_canvas);
382 //----------------------------------------------------------------------------
383 CanvasWeakPtr Image::getSrcCanvas() const
388 //----------------------------------------------------------------------------
389 void Image::setImage(osg::Image *img)
392 setSrcCanvas( CanvasPtr() );
394 _texture->setResizeNonPowerOfTwoHint(false);
395 _texture->setImage(img);
396 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
397 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
398 _geom->getOrCreateStateSet()
399 ->setTextureAttributeAndModes(0, _texture);
402 setupDefaultDimensions();
405 //----------------------------------------------------------------------------
406 void Image::setFill(const std::string& fill)
408 osg::Vec4 color(1,1,1,1);
409 if( !fill.empty() // If no color is given default to white
410 && !parseColor(fill, color) )
413 _colors->front() = color;
417 //----------------------------------------------------------------------------
418 void Image::setSlice(const std::string& slice)
420 _slice = CSSBorder::parse(slice);
421 _attributes_dirty |= SRC_RECT | DEST_SIZE;
424 //----------------------------------------------------------------------------
425 void Image::setSliceWidth(const std::string& width)
427 _slice_width = CSSBorder::parse(width);
428 _attributes_dirty |= DEST_SIZE;
431 //----------------------------------------------------------------------------
432 void Image::setOutset(const std::string& outset)
434 _outset = CSSBorder::parse(outset);
435 _attributes_dirty |= DEST_SIZE;
438 //----------------------------------------------------------------------------
439 const SGRect<float>& Image::getRegion() const
444 //----------------------------------------------------------------------------
445 bool Image::handleEvent(EventPtr event)
447 bool handled = Element::handleEvent(event);
449 CanvasPtr src_canvas = _src_canvas.lock();
453 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
456 mouse_event.reset( new MouseEvent(*mouse_event) );
459 mouse_event->client_pos = mouse_event->local_pos
460 - toOsg(_region.getMin());
462 osg::Vec2f size(_region.width(), _region.height());
463 if( _outset.isValid() )
465 CSSBorder::Offsets outset =
466 _outset.getAbsOffsets(getTextureDimensions());
468 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
469 size.x() += outset.l + outset.r;
470 size.y() += outset.t + outset.b;
473 // Scale event pos according to canvas view size vs. displayed/screen size
474 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
475 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
476 mouse_event->local_pos = mouse_event->client_pos;
479 return handled | src_canvas->handleMouseEvent(mouse_event);
482 //----------------------------------------------------------------------------
483 void Image::childChanged(SGPropertyNode* child)
485 const std::string& name = child->getNameString();
487 if( child->getParent() == _node_src_rect )
489 _attributes_dirty |= SRC_RECT;
492 _src_rect.setLeft( child->getFloatValue() );
493 else if( name == "right" )
494 _src_rect.setRight( child->getFloatValue() );
495 else if( name == "top" )
496 _src_rect.setTop( child->getFloatValue() );
497 else if( name == "bottom" )
498 _src_rect.setBottom( child->getFloatValue() );
502 else if( child->getParent() != _node )
507 _region.setX( child->getFloatValue() );
508 _attributes_dirty |= DEST_SIZE;
510 else if( name == "y" )
512 _region.setY( child->getFloatValue() );
513 _attributes_dirty |= DEST_SIZE;
515 else if( name == "size" )
517 if( child->getIndex() == 0 )
518 _region.setWidth( child->getFloatValue() );
520 _region.setHeight( child->getFloatValue() );
522 _attributes_dirty |= DEST_SIZE;
524 else if( name == "file" )
526 static const std::string CANVAS_PROTOCOL = "canvas://";
527 const std::string& path = child->getStringValue();
529 CanvasPtr canvas = _canvas.lock();
532 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
536 if( boost::starts_with(path, CANVAS_PROTOCOL) )
538 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
541 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
545 const SGPropertyNode* canvas_node =
546 canvas_mgr->getPropertyRoot()
548 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
551 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
555 // TODO add support for other means of addressing canvases (eg. by
557 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
560 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
564 setSrcCanvas(src_canvas);
568 setImage( Canvas::getSystemAdapter()->getImage(path) );
573 //----------------------------------------------------------------------------
574 void Image::setupDefaultDimensions()
576 if( !_src_rect.width() || !_src_rect.height() )
578 // Show whole image by default
579 _node_src_rect->setBoolValue("normalized", true);
580 _node_src_rect->setFloatValue("right", 1);
581 _node_src_rect->setFloatValue("bottom", 1);
584 if( !_region.width() || !_region.height() )
586 // Default to image size.
587 // TODO handle showing only part of image?
588 const SGRect<int>& dim = getTextureDimensions();
589 _node->setFloatValue("size[0]", dim.width());
590 _node->setFloatValue("size[1]", dim.height());
594 //----------------------------------------------------------------------------
595 SGRect<int> Image::getTextureDimensions() const
597 CanvasPtr canvas = _src_canvas.lock();
598 SGRect<int> dim(0,0);
600 // Use canvas/image dimensions rather than texture dimensions, as they could
601 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
604 dim.setRight( canvas->getViewWidth() );
605 dim.setBottom( canvas->getViewHeight() );
609 osg::Image* img = _texture->getImage();
613 dim.setRight( img->s() );
614 dim.setBottom( img->t() );
621 //----------------------------------------------------------------------------
622 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
625 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
626 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
627 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
628 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
631 //----------------------------------------------------------------------------
632 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
635 (*_texCoords)[i + 0].set(tl.x(), tl.y());
636 (*_texCoords)[i + 1].set(br.x(), tl.y());
637 (*_texCoords)[i + 2].set(br.x(), br.y());
638 (*_texCoords)[i + 3].set(tl.x(), br.y());
641 } // namespace canvas
642 } // namespace simgear