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->setColorArray(_colors);
122 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
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)
403 osg::Vec4 color(1,1,1,1);
404 if( !fill.empty() // If no color is given default to white
405 && !parseColor(fill, color) )
408 _colors->front() = color;
412 //----------------------------------------------------------------------------
413 void Image::setSlice(const std::string& slice)
415 _slice = CSSBorder::parse(slice);
416 _attributes_dirty |= SRC_RECT | DEST_SIZE;
419 //----------------------------------------------------------------------------
420 void Image::setSliceWidth(const std::string& width)
422 _slice_width = CSSBorder::parse(width);
423 _attributes_dirty |= DEST_SIZE;
426 //----------------------------------------------------------------------------
427 void Image::setOutset(const std::string& outset)
429 _outset = CSSBorder::parse(outset);
430 _attributes_dirty |= DEST_SIZE;
433 //----------------------------------------------------------------------------
434 const SGRect<float>& Image::getRegion() const
439 //----------------------------------------------------------------------------
440 bool Image::handleEvent(EventPtr event)
442 bool handled = Element::handleEvent(event);
444 CanvasPtr src_canvas = _src_canvas.lock();
448 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
451 mouse_event.reset( new MouseEvent(*mouse_event) );
454 mouse_event->client_pos = mouse_event->local_pos
455 - toOsg(_region.getMin());
457 osg::Vec2f size(_region.width(), _region.height());
458 if( _outset.isValid() )
460 CSSBorder::Offsets outset =
461 _outset.getAbsOffsets(getTextureDimensions());
463 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
464 size.x() += outset.l + outset.r;
465 size.y() += outset.t + outset.b;
468 // Scale event pos according to canvas view size vs. displayed/screen size
469 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
470 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
471 mouse_event->local_pos = mouse_event->client_pos;
474 return handled | src_canvas->handleMouseEvent(mouse_event);
477 //----------------------------------------------------------------------------
478 void Image::childChanged(SGPropertyNode* child)
480 const std::string& name = child->getNameString();
482 if( child->getParent() == _node_src_rect )
484 _attributes_dirty |= SRC_RECT;
487 _src_rect.setLeft( child->getFloatValue() );
488 else if( name == "right" )
489 _src_rect.setRight( child->getFloatValue() );
490 else if( name == "top" )
491 _src_rect.setTop( child->getFloatValue() );
492 else if( name == "bottom" )
493 _src_rect.setBottom( child->getFloatValue() );
497 else if( child->getParent() != _node )
502 _region.setX( child->getFloatValue() );
503 _attributes_dirty |= DEST_SIZE;
505 else if( name == "y" )
507 _region.setY( child->getFloatValue() );
508 _attributes_dirty |= DEST_SIZE;
510 else if( name == "size" )
512 if( child->getIndex() == 0 )
513 _region.setWidth( child->getFloatValue() );
515 _region.setHeight( child->getFloatValue() );
517 _attributes_dirty |= DEST_SIZE;
519 else if( name == "file" )
521 static const std::string CANVAS_PROTOCOL = "canvas://";
522 const std::string& path = child->getStringValue();
524 CanvasPtr canvas = _canvas.lock();
527 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
531 if( boost::starts_with(path, CANVAS_PROTOCOL) )
533 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
536 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
540 const SGPropertyNode* canvas_node =
541 canvas_mgr->getPropertyRoot()
543 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
546 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
550 // TODO add support for other means of addressing canvases (eg. by
552 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
555 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
559 setSrcCanvas(src_canvas);
563 setImage( canvas->getSystemAdapter()->getImage(path) );
568 //----------------------------------------------------------------------------
569 void Image::setupDefaultDimensions()
571 if( !_src_rect.width() || !_src_rect.height() )
573 // Show whole image by default
574 _node_src_rect->setBoolValue("normalized", true);
575 _node_src_rect->setFloatValue("right", 1);
576 _node_src_rect->setFloatValue("bottom", 1);
579 if( !_region.width() || !_region.height() )
581 // Default to image size.
582 // TODO handle showing only part of image?
583 const SGRect<int>& dim = getTextureDimensions();
584 _node->setFloatValue("size[0]", dim.width());
585 _node->setFloatValue("size[1]", dim.height());
589 //----------------------------------------------------------------------------
590 SGRect<int> Image::getTextureDimensions() const
592 CanvasPtr canvas = _src_canvas.lock();
593 SGRect<int> dim(0,0);
595 // Use canvas/image dimensions rather than texture dimensions, as they could
596 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
599 dim.setRight( canvas->getViewWidth() );
600 dim.setBottom( canvas->getViewHeight() );
604 osg::Image* img = _texture->getImage();
608 dim.setRight( img->s() );
609 dim.setBottom( img->t() );
616 //----------------------------------------------------------------------------
617 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
620 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
621 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
622 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
623 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
626 //----------------------------------------------------------------------------
627 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
630 (*_texCoords)[i + 0].set(tl.x(), tl.y());
631 (*_texCoords)[i + 1].set(br.x(), tl.y());
632 (*_texCoords)[i + 2].set(br.x(), br.y());
633 (*_texCoords)[i + 3].set(tl.x(), br.y());
636 } // namespace canvas
637 } // namespace simgear