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->setImage(img);
382 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
383 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
384 _geom->getOrCreateStateSet()
385 ->setTextureAttributeAndModes(0, _texture);
388 setupDefaultDimensions();
391 //----------------------------------------------------------------------------
392 void Image::setFill(const std::string& fill)
395 if( !parseColor(fill, color) )
398 _colors->front() = color;
402 //----------------------------------------------------------------------------
403 void Image::setSlice(const std::string& slice)
405 _slice = CSSBorder::parse(slice);
406 _attributes_dirty |= SRC_RECT | DEST_SIZE;
409 //----------------------------------------------------------------------------
410 void Image::setSliceWidth(const std::string& width)
412 _slice_width = CSSBorder::parse(width);
413 _attributes_dirty |= DEST_SIZE;
416 //----------------------------------------------------------------------------
417 void Image::setOutset(const std::string& outset)
419 _outset = CSSBorder::parse(outset);
420 _attributes_dirty |= DEST_SIZE;
423 //----------------------------------------------------------------------------
424 const SGRect<float>& Image::getRegion() const
429 //----------------------------------------------------------------------------
430 bool Image::handleEvent(EventPtr event)
432 bool handled = Element::handleEvent(event);
434 CanvasPtr src_canvas = _src_canvas.lock();
438 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
441 mouse_event.reset( new MouseEvent(*mouse_event) );
444 mouse_event->client_pos = mouse_event->local_pos
445 - toOsg(_region.getMin());
447 osg::Vec2f size(_region.width(), _region.height());
448 if( _outset.isValid() )
450 CSSBorder::Offsets outset =
451 _outset.getAbsOffsets(getTextureDimensions());
453 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
454 size.x() += outset.l + outset.r;
455 size.y() += outset.t + outset.b;
458 // Scale event pos according to canvas view size vs. displayed/screen size
459 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
460 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
461 mouse_event->local_pos = mouse_event->client_pos;
464 return handled || src_canvas->handleMouseEvent(mouse_event);
467 //----------------------------------------------------------------------------
468 void Image::childChanged(SGPropertyNode* child)
470 const std::string& name = child->getNameString();
472 if( child->getParent() == _node_src_rect )
474 _attributes_dirty |= SRC_RECT;
477 _src_rect.setLeft( child->getFloatValue() );
478 else if( name == "right" )
479 _src_rect.setRight( child->getFloatValue() );
480 else if( name == "top" )
481 _src_rect.setTop( child->getFloatValue() );
482 else if( name == "bottom" )
483 _src_rect.setBottom( child->getFloatValue() );
487 else if( child->getParent() != _node )
492 _region.setX( child->getFloatValue() );
493 _attributes_dirty |= DEST_SIZE;
495 else if( name == "y" )
497 _region.setY( child->getFloatValue() );
498 _attributes_dirty |= DEST_SIZE;
500 else if( name == "size" )
502 if( child->getIndex() == 0 )
503 _region.setWidth( child->getFloatValue() );
505 _region.setHeight( child->getFloatValue() );
507 _attributes_dirty |= DEST_SIZE;
509 else if( name == "file" )
511 static const std::string CANVAS_PROTOCOL = "canvas://";
512 const std::string& path = child->getStringValue();
514 CanvasPtr canvas = _canvas.lock();
517 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
521 if( boost::starts_with(path, CANVAS_PROTOCOL) )
523 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
526 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
530 const SGPropertyNode* canvas_node =
531 canvas_mgr->getPropertyRoot()
533 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
536 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
540 // TODO add support for other means of addressing canvases (eg. by
542 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
545 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
549 setSrcCanvas(src_canvas);
553 setImage( canvas->getSystemAdapter()->getImage(path) );
558 //----------------------------------------------------------------------------
559 void Image::setupDefaultDimensions()
561 if( !_src_rect.width() || !_src_rect.height() )
563 // Show whole image by default
564 _node_src_rect->setBoolValue("normalized", true);
565 _node_src_rect->setFloatValue("right", 1);
566 _node_src_rect->setFloatValue("bottom", 1);
569 if( !_region.width() || !_region.height() )
571 // Default to image size.
572 // TODO handle showing only part of image?
573 const SGRect<int>& dim = getTextureDimensions();
574 _node->setFloatValue("size[0]", dim.width());
575 _node->setFloatValue("size[1]", dim.height());
579 //----------------------------------------------------------------------------
580 SGRect<int> Image::getTextureDimensions() const
582 CanvasPtr canvas = _src_canvas.lock();
583 SGRect<int> dim(0,0);
585 // Use canvas/image dimensions rather than texture dimensions, as they could
586 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
589 dim.setRight( canvas->getViewWidth() );
590 dim.setBottom( canvas->getViewHeight() );
594 osg::Image* img = _texture->getImage();
598 dim.setRight( img->s() );
599 dim.setBottom( img->t() );
606 //----------------------------------------------------------------------------
607 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
610 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
611 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
612 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
613 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
616 //----------------------------------------------------------------------------
617 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
620 (*_texCoords)[i + 0].set(tl.x(), tl.y());
621 (*_texCoords)[i + 1].set(br.x(), tl.y());
622 (*_texCoords)[i + 2].set(br.x(), br.y());
623 (*_texCoords)[i + 3].set(tl.x(), br.y());
626 } // namespace canvas
627 } // namespace simgear