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/parse_color.hxx>
26 #include <simgear/misc/sg_path.hxx>
29 #include <osg/Geometry>
30 #include <osg/PrimitiveSet>
32 #include <boost/algorithm/string/predicate.hpp>
33 #include <boost/lexical_cast.hpp>
34 #include <boost/range.hpp>
35 #include <boost/tokenizer.hpp>
42 * Callback to enable/disable rendering of canvas displayed inside windows or
46 public osg::Drawable::CullCallback
49 CullCallback(const CanvasWeakPtr& canvas);
53 CanvasWeakPtr _canvas;
54 mutable bool _cull_next_frame;
56 virtual bool cull( osg::NodeVisitor* nv,
57 osg::Drawable* drawable,
58 osg::RenderInfo* renderInfo ) const;
61 //----------------------------------------------------------------------------
62 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
64 _cull_next_frame( false )
69 //----------------------------------------------------------------------------
70 void CullCallback::cullNextFrame()
72 _cull_next_frame = true;
75 //----------------------------------------------------------------------------
76 bool CullCallback::cull( osg::NodeVisitor* nv,
77 osg::Drawable* drawable,
78 osg::RenderInfo* renderInfo ) const
80 if( !_canvas.expired() )
81 _canvas.lock()->enableRendering();
83 if( !_cull_next_frame )
84 // TODO check if window/image should be culled
87 _cull_next_frame = false;
91 //----------------------------------------------------------------------------
92 Image::Image( const CanvasWeakPtr& canvas,
93 const SGPropertyNode_ptr& node,
94 const Style& parent_style,
96 Element(canvas, node, parent_style, parent),
97 _texture(new osg::Texture2D),
98 _node_src_rect( node->getNode("source", 0, true) ),
102 _geom = new osg::Geometry;
103 _geom->setUseDisplayList(false);
105 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
106 stateSet->setTextureAttributeAndModes(0, _texture.get());
107 stateSet->setDataVariance(osg::Object::STATIC);
109 // allocate arrays for the image
110 _vertices = new osg::Vec3Array(4);
111 _vertices->setDataVariance(osg::Object::DYNAMIC);
112 _geom->setVertexArray(_vertices);
114 _texCoords = new osg::Vec2Array(4);
115 _texCoords->setDataVariance(osg::Object::DYNAMIC);
116 _geom->setTexCoordArray(0, _texCoords);
118 _colors = new osg::Vec4Array(1);
119 _colors->setDataVariance(osg::Object::DYNAMIC);
120 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
121 _geom->setColorArray(_colors);
123 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
124 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
125 _prim->setDataVariance(osg::Object::DYNAMIC);
126 _geom->addPrimitiveSet(_prim);
130 if( !isInit<Image>() )
132 addStyle("fill", "color", &Image::setFill);
133 addStyle("slice", "", &Image::setSlice);
134 addStyle("slice-width", "numeric", &Image::setSliceWidth);
135 addStyle("outset", "", &Image::setOutset);
138 setFill("#ffffff"); // TODO how should we handle default values?
143 //----------------------------------------------------------------------------
149 //----------------------------------------------------------------------------
150 void Image::update(double dt)
154 if( !_attributes_dirty )
157 const SGRect<int>& tex_dim = getTextureDimensions();
159 // http://www.w3.org/TR/css3-background/#border-image-slice
161 // The ‘fill’ keyword, if present, causes the middle part of the image to be
162 // preserved. (By default it is discarded, i.e., treated as empty.)
163 bool fill = (_slice.keyword == "fill");
165 if( _attributes_dirty & DEST_SIZE )
167 size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
169 if( num_vertices != _prim->getNumPrimitives() )
171 _vertices->resize(num_vertices);
172 _texCoords->resize(num_vertices);
173 _prim->setCount(num_vertices);
176 _attributes_dirty |= SRC_RECT;
179 // http://www.w3.org/TR/css3-background/#border-image-outset
180 SGRect<float> region = _region;
183 const CSSOffsets& outset = _outset.getAbsOffsets(tex_dim);
184 region.t() -= outset.t;
185 region.r() += outset.r;
186 region.b() += outset.b;
187 region.l() -= outset.l;
192 setQuad(0, region.getMin(), region.getMax());
197 Image slice, 9-scale, whatever it is called. The four corner images
198 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
199 fill the remaining space up to the specified size.
203 -------------------- - y[0]
205 -------------------- - y[1]
212 -------------------- - y[2]
214 -------------------- - y[3]
217 const CSSOffsets& slice =
218 (_slice_width.valid ? _slice_width : _slice).getAbsOffsets(tex_dim);
221 region.l() + slice.l,
222 region.r() - slice.r,
227 region.t() + slice.t,
228 region.b() - slice.b,
233 for(int ix = 0; ix < 3; ++ix)
234 for(int iy = 0; iy < 3; ++iy)
236 if( ix == 1 && iy == 1 && !fill )
237 // The ‘fill’ keyword, if present, causes the middle part of the
238 // image to be filled.
242 SGVec2f(x[ix ], y[iy ]),
243 SGVec2f(x[ix + 1], y[iy + 1]) );
248 _attributes_dirty &= ~DEST_SIZE;
250 setBoundingBox(_geom->getBound());
253 if( _attributes_dirty & SRC_RECT )
255 SGRect<float> src_rect = _src_rect;
256 if( !_node_src_rect->getBoolValue("normalized", true) )
258 src_rect.t() /= tex_dim.height();
259 src_rect.r() /= tex_dim.width();
260 src_rect.b() /= tex_dim.height();
261 src_rect.l() /= tex_dim.width();
264 // Image coordinate systems y-axis is flipped
265 std::swap(src_rect.t(), src_rect.b());
269 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
273 const CSSOffsets& slice = _slice.getRelOffsets(tex_dim);
276 src_rect.l() + slice.l,
277 src_rect.r() - slice.r,
282 src_rect.t() - slice.t,
283 src_rect.b() + slice.b,
288 for(int ix = 0; ix < 3; ++ix)
289 for(int iy = 0; iy < 3; ++iy)
291 if( ix == 1 && iy == 1 && !fill )
292 // The ‘fill’ keyword, if present, causes the middle part of the
293 // image to be filled.
297 SGVec2f(x[ix ], y[iy ]),
298 SGVec2f(x[ix + 1], y[iy + 1]) );
303 _attributes_dirty &= ~SRC_RECT;
307 //----------------------------------------------------------------------------
308 void Image::valueChanged(SGPropertyNode* child)
310 // If the image is switched from invisible to visible, and it shows a
311 // canvas, we need to delay showing it by one frame to ensure the canvas is
312 // updated before the image is displayed.
314 // As canvas::Element handles and filters changes to the "visible" property
315 // we can not check this in Image::childChanged but instead have to override
316 // Element::valueChanged.
318 && child->getParent() == _node
319 && child->getNameString() == "visible"
320 && child->getBoolValue() )
322 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
327 Element::valueChanged(child);
330 //----------------------------------------------------------------------------
331 void Image::setSrcCanvas(CanvasPtr canvas)
333 if( !_src_canvas.expired() )
334 _src_canvas.lock()->removeParentCanvas(_canvas);
335 if( !_canvas.expired() )
336 _canvas.lock()->removeChildCanvas(_src_canvas);
338 _src_canvas = canvas;
339 _geom->getOrCreateStateSet()
340 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
341 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
343 if( !_src_canvas.expired() )
345 setupDefaultDimensions();
346 _src_canvas.lock()->addParentCanvas(_canvas);
348 if( !_canvas.expired() )
349 _canvas.lock()->addChildCanvas(_src_canvas);
353 //----------------------------------------------------------------------------
354 CanvasWeakPtr Image::getSrcCanvas() const
359 //----------------------------------------------------------------------------
360 void Image::setImage(osg::Image *img)
363 setSrcCanvas( CanvasPtr() );
365 _texture->setImage(img);
366 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
367 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
368 _geom->getOrCreateStateSet()
369 ->setTextureAttributeAndModes(0, _texture);
372 setupDefaultDimensions();
375 //----------------------------------------------------------------------------
376 void Image::setFill(const std::string& fill)
379 if( !parseColor(fill, color) )
382 _colors->front() = color;
386 //----------------------------------------------------------------------------
387 void Image::setSlice(const std::string& slice)
389 _slice = parseSideOffsets(slice);
390 _attributes_dirty |= SRC_RECT | DEST_SIZE;
393 //----------------------------------------------------------------------------
394 void Image::setSliceWidth(const std::string& width)
396 _slice_width = parseSideOffsets(width);
397 _attributes_dirty |= DEST_SIZE;
400 //----------------------------------------------------------------------------
401 void Image::setOutset(const std::string& outset)
403 _outset = parseSideOffsets(outset);
404 _attributes_dirty |= DEST_SIZE;
407 //----------------------------------------------------------------------------
408 const SGRect<float>& Image::getRegion() const
413 //----------------------------------------------------------------------------
414 bool Image::handleMouseEvent(MouseEventPtr event)
416 CanvasPtr src_canvas = _src_canvas.lock();
423 CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
425 event.reset( new MouseEvent(*event) );
426 event->client_pos += osg::Vec2f(outset.l, outset.t);
427 event->client_pos.x() *= src_canvas->getViewWidth()
428 / (_region.width() + outset.l + outset.r);
429 event->client_pos.y() *= src_canvas->getViewHeight()
430 / (_region.height() + outset.t + outset.b);
433 return src_canvas->handleMouseEvent(event);
436 //----------------------------------------------------------------------------
438 Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
441 for(int i = 0; i < 4; ++i)
443 ret.val[i] = offsets.val[i];
445 ret.val[i] /= (i & 1) ? dim.height() : dim.width();
450 //----------------------------------------------------------------------------
452 Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
455 for(int i = 0; i < 4; ++i)
457 ret.val[i] = offsets.val[i];
459 ret.val[i] *= (i & 1) ? dim.height() : dim.width();
464 //----------------------------------------------------------------------------
465 void Image::childChanged(SGPropertyNode* child)
467 const std::string& name = child->getNameString();
469 if( child->getParent() == _node_src_rect )
471 _attributes_dirty |= SRC_RECT;
474 _src_rect.setLeft( child->getFloatValue() );
475 else if( name == "right" )
476 _src_rect.setRight( child->getFloatValue() );
477 else if( name == "top" )
478 _src_rect.setTop( child->getFloatValue() );
479 else if( name == "bottom" )
480 _src_rect.setBottom( child->getFloatValue() );
484 else if( child->getParent() != _node )
489 _region.setX( child->getFloatValue() );
490 _attributes_dirty |= DEST_SIZE;
492 else if( name == "y" )
494 _region.setY( child->getFloatValue() );
495 _attributes_dirty |= DEST_SIZE;
497 else if( name == "size" )
499 if( child->getIndex() == 0 )
500 _region.setWidth( child->getFloatValue() );
502 _region.setHeight( child->getFloatValue() );
504 _attributes_dirty |= DEST_SIZE;
506 else if( name == "file" )
508 static const std::string CANVAS_PROTOCOL = "canvas://";
509 const std::string& path = child->getStringValue();
511 CanvasPtr canvas = _canvas.lock();
514 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
518 if( boost::starts_with(path, CANVAS_PROTOCOL) )
520 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
523 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
527 const SGPropertyNode* canvas_node =
528 canvas_mgr->getPropertyRoot()
530 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
533 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
537 // TODO add support for other means of addressing canvases (eg. by
539 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
542 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
546 setSrcCanvas(src_canvas);
550 setImage( canvas->getSystemAdapter()->getImage(path) );
555 //----------------------------------------------------------------------------
556 void Image::setupDefaultDimensions()
558 if( !_src_rect.width() || !_src_rect.height() )
560 // Show whole image by default
561 _node_src_rect->setBoolValue("normalized", true);
562 _node_src_rect->setFloatValue("right", 1);
563 _node_src_rect->setFloatValue("bottom", 1);
566 if( !_region.width() || !_region.height() )
568 // Default to image size.
569 // TODO handle showing only part of image?
570 const SGRect<int>& dim = getTextureDimensions();
571 _node->setFloatValue("size[0]", dim.width());
572 _node->setFloatValue("size[1]", dim.height());
576 //----------------------------------------------------------------------------
577 SGRect<int> Image::getTextureDimensions() const
579 CanvasPtr canvas = _src_canvas.lock();
580 SGRect<int> dim(0,0);
582 // Use canvas/image dimensions rather than texture dimensions, as they could
583 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
586 dim.setRight( canvas->getViewWidth() );
587 dim.setBottom( canvas->getViewHeight() );
591 osg::Image* img = _texture->getImage();
595 dim.setRight( img->s() );
596 dim.setBottom( img->t() );
603 //----------------------------------------------------------------------------
604 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
607 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
608 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
609 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
610 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
613 //----------------------------------------------------------------------------
614 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
617 (*_texCoords)[i + 0].set(tl.x(), tl.y());
618 (*_texCoords)[i + 1].set(br.x(), tl.y());
619 (*_texCoords)[i + 2].set(br.x(), br.y());
620 (*_texCoords)[i + 3].set(tl.x(), br.y());
623 //----------------------------------------------------------------------------
624 Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
629 // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
631 // Percentages are relative to the size of the image: the width of the
632 // image for the horizontal offsets, the height for vertical offsets.
633 // Numbers represent pixels in the image.
637 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
638 const boost::char_separator<char> del(" \t\n");
640 tokenizer tokens(str.begin(), str.end(), del);
641 for( tokenizer::const_iterator tok = tokens.begin();
642 tok != tokens.end() && c < 4;
645 if( isalpha(*tok->begin()) )
649 bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
651 // Negative values are not allowed and values bigger than the size of
652 // the image are interpreted as ‘100%’. TODO check max
656 boost::lexical_cast<float>
658 rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
668 // When four values are specified, they set the offsets on the top, right,
669 // bottom and left sides in that order.
671 #define CSS_COPY_VAL(dest, src)\
673 ret.offsets.val[dest] = ret.offsets.val[src];\
674 ret.types.rel[dest] = ret.types.rel[src];\
682 // if the right is missing, it is the same as the top.
685 // if the bottom is missing, it is the same as the top
689 // If the left is missing, it is the same as the right
699 } // namespace canvas
700 } // namespace simgear