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/scene/util/parse_color.hxx>
25 #include <simgear/misc/sg_path.hxx>
28 #include <osg/Geometry>
29 #include <osg/PrimitiveSet>
31 #include <boost/algorithm/string/predicate.hpp>
32 #include <boost/lexical_cast.hpp>
33 #include <boost/range.hpp>
34 #include <boost/tokenizer.hpp>
41 * Callback to enable/disable rendering of canvas displayed inside windows or
45 public osg::Drawable::CullCallback
48 CullCallback(const CanvasWeakPtr& canvas);
51 CanvasWeakPtr _canvas;
53 virtual bool cull( osg::NodeVisitor* nv,
54 osg::Drawable* drawable,
55 osg::RenderInfo* renderInfo ) const;
58 //----------------------------------------------------------------------------
59 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
65 //----------------------------------------------------------------------------
66 bool CullCallback::cull( osg::NodeVisitor* nv,
67 osg::Drawable* drawable,
68 osg::RenderInfo* renderInfo ) const
70 if( !_canvas.expired() )
71 _canvas.lock()->enableRendering();
73 // TODO check if window/image should be culled
77 //----------------------------------------------------------------------------
78 Image::Image( const CanvasWeakPtr& canvas,
79 const SGPropertyNode_ptr& node,
80 const Style& parent_style,
82 Element(canvas, node, parent_style, parent),
83 _texture(new osg::Texture2D),
84 _node_src_rect( node->getNode("source", 0, true) ),
88 _geom = new osg::Geometry;
89 _geom->setUseDisplayList(false);
91 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
92 stateSet->setTextureAttributeAndModes(0, _texture.get());
93 stateSet->setDataVariance(osg::Object::STATIC);
95 // allocate arrays for the image
96 _vertices = new osg::Vec3Array(4);
97 _vertices->setDataVariance(osg::Object::DYNAMIC);
98 _geom->setVertexArray(_vertices);
100 _texCoords = new osg::Vec2Array(4);
101 _texCoords->setDataVariance(osg::Object::DYNAMIC);
102 _geom->setTexCoordArray(0, _texCoords);
104 _colors = new osg::Vec4Array(1);
105 _colors->setDataVariance(osg::Object::DYNAMIC);
106 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
107 _geom->setColorArray(_colors);
109 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
110 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
111 _prim->setDataVariance(osg::Object::DYNAMIC);
112 _geom->addPrimitiveSet(_prim);
116 addStyle("fill", &Image::setFill, this);
117 addStyle("slice", &Image::setSlice, this);
118 addStyle("outset", &Image::setOutset, this);
120 setFill("#ffffff"); // TODO how should we handle default values?
125 //----------------------------------------------------------------------------
131 //----------------------------------------------------------------------------
132 void Image::update(double dt)
136 if( !_attributes_dirty )
139 const SGRect<int>& tex_dim = getTextureDimensions();
141 // http://www.w3.org/TR/css3-background/#border-image-slice
143 // The ‘fill’ keyword, if present, causes the middle part of the image to be
144 // preserved. (By default it is discarded, i.e., treated as empty.)
145 bool fill = (_slice.keyword == "fill");
147 if( _attributes_dirty & DEST_SIZE )
149 size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
151 if( num_vertices != _prim->getNumPrimitives() )
153 _vertices->resize(num_vertices);
154 _texCoords->resize(num_vertices);
155 _prim->setCount(num_vertices);
158 _attributes_dirty |= SRC_RECT;
161 // http://www.w3.org/TR/css3-background/#border-image-outset
162 SGRect<float> region = _region;
165 region.t() -= _outset.t;
166 region.r() += _outset.r;
167 region.b() += _outset.b;
168 region.l() -= _outset.l;
173 setQuad(0, region.getMin(), region.getMax());
178 Image slice, 9-scale, whatever it is called. The four corner images
179 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
180 fill the remaining space up to the specified size.
184 -------------------- - y[0]
186 -------------------- - y[1]
193 -------------------- - y[2]
195 -------------------- - y[3]
200 region.l() + _slice.l * tex_dim.width(),
201 region.r() - _slice.r * tex_dim.width(),
206 region.t() + _slice.t * tex_dim.height(),
207 region.b() - _slice.b * tex_dim.height(),
212 for(int ix = 0; ix < 3; ++ix)
213 for(int iy = 0; iy < 3; ++iy)
215 if( ix == 1 && iy == 1 && !fill )
216 // The ‘fill’ keyword, if present, causes the middle part of the
217 // image to be filled.
221 SGVec2f(x[ix ], y[iy ]),
222 SGVec2f(x[ix + 1], y[iy + 1]) );
227 _attributes_dirty &= ~DEST_SIZE;
229 setBoundingBox(_geom->getBound());
232 if( _attributes_dirty & SRC_RECT )
234 SGRect<float> src_rect = _src_rect;
235 if( !_node_src_rect->getBoolValue("normalized", true) )
237 src_rect.t() /= tex_dim.height();
238 src_rect.r() /= tex_dim.width();
239 src_rect.b() /= tex_dim.height();
240 src_rect.l() /= tex_dim.width();
243 // Image coordinate systems y-axis is flipped
244 std::swap(src_rect.t(), src_rect.b());
248 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
254 src_rect.l() + _slice.l,
255 src_rect.r() - _slice.r,
260 src_rect.t() - _slice.t,
261 src_rect.b() + _slice.b,
266 for(int ix = 0; ix < 3; ++ix)
267 for(int iy = 0; iy < 3; ++iy)
269 if( ix == 1 && iy == 1 && !fill )
270 // The ‘fill’ keyword, if present, causes the middle part of the
271 // image to be filled.
275 SGVec2f(x[ix ], y[iy ]),
276 SGVec2f(x[ix + 1], y[iy + 1]) );
281 _attributes_dirty &= ~SRC_RECT;
285 //----------------------------------------------------------------------------
286 void Image::setSrcCanvas(CanvasPtr canvas)
288 if( !_src_canvas.expired() )
289 _src_canvas.lock()->removeDependentCanvas(_canvas);
291 _src_canvas = canvas;
292 _geom->getOrCreateStateSet()
293 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
294 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
296 if( !_src_canvas.expired() )
298 setupDefaultDimensions();
299 _src_canvas.lock()->addDependentCanvas(_canvas);
303 //----------------------------------------------------------------------------
304 CanvasWeakPtr Image::getSrcCanvas() const
309 //----------------------------------------------------------------------------
310 void Image::setImage(osg::Image *img)
313 setSrcCanvas( CanvasPtr() );
315 _texture->setImage(img);
316 _geom->getOrCreateStateSet()
317 ->setTextureAttributeAndModes(0, _texture);
320 setupDefaultDimensions();
323 //----------------------------------------------------------------------------
324 void Image::setFill(const std::string& fill)
327 if( !parseColor(fill, color) )
330 _colors->front() = color;
334 //----------------------------------------------------------------------------
335 void Image::setSlice(const std::string& slice)
337 _slice = parseSideOffsets(slice);
338 _attributes_dirty |= SRC_RECT | DEST_SIZE;
341 //----------------------------------------------------------------------------
342 void Image::setOutset(const std::string& outset)
344 _outset = parseSideOffsets(outset);
345 _attributes_dirty |= DEST_SIZE;
348 //----------------------------------------------------------------------------
349 const SGRect<float>& Image::getRegion() const
354 //----------------------------------------------------------------------------
355 void Image::childChanged(SGPropertyNode* child)
357 const std::string& name = child->getNameString();
359 if( child->getParent() == _node_src_rect )
361 _attributes_dirty |= SRC_RECT;
364 _src_rect.setLeft( child->getFloatValue() );
365 else if( name == "right" )
366 _src_rect.setRight( child->getFloatValue() );
367 else if( name == "top" )
368 _src_rect.setTop( child->getFloatValue() );
369 else if( name == "bottom" )
370 _src_rect.setBottom( child->getFloatValue() );
374 else if( child->getParent() != _node )
379 _region.setX( child->getFloatValue() );
380 _attributes_dirty |= DEST_SIZE;
382 else if( name == "y" )
384 _region.setY( child->getFloatValue() );
385 _attributes_dirty |= DEST_SIZE;
387 else if( name == "size" )
389 if( child->getIndex() == 0 )
390 _region.setWidth( child->getFloatValue() );
392 _region.setHeight( child->getFloatValue() );
394 _attributes_dirty |= DEST_SIZE;
396 else if( name == "file" )
398 static const std::string CANVAS_PROTOCOL = "canvas://";
399 const std::string& path = child->getStringValue();
401 CanvasPtr canvas = _canvas.lock();
404 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
408 if( boost::starts_with(path, CANVAS_PROTOCOL) )
410 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
413 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
417 const SGPropertyNode* canvas_node =
418 canvas_mgr->getPropertyRoot()
420 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
423 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
427 // TODO add support for other means of addressing canvases (eg. by
429 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
432 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
436 setSrcCanvas(src_canvas);
440 setImage( canvas->getSystemAdapter()->getImage(path) );
445 //----------------------------------------------------------------------------
446 void Image::setupDefaultDimensions()
448 if( !_src_rect.width() || !_src_rect.height() )
450 // Show whole image by default
451 _node_src_rect->setBoolValue("normalized", true);
452 _node_src_rect->setFloatValue("right", 1);
453 _node_src_rect->setFloatValue("bottom", 1);
456 if( !_region.width() || !_region.height() )
458 // Default to image size.
459 // TODO handle showing only part of image?
460 const SGRect<int>& dim = getTextureDimensions();
461 _node->setFloatValue("size[0]", dim.width());
462 _node->setFloatValue("size[1]", dim.height());
466 //----------------------------------------------------------------------------
467 SGRect<int> Image::getTextureDimensions() const
469 CanvasPtr canvas = _src_canvas.lock();
470 SGRect<int> dim(0,0);
472 // Use canvas/image dimensions rather than texture dimensions, as they could
473 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
476 dim.setRight( canvas->getViewWidth() );
477 dim.setBottom( canvas->getViewHeight() );
481 osg::Image* img = _texture->getImage();
485 dim.setRight( img->s() );
486 dim.setBottom( img->t() );
493 //----------------------------------------------------------------------------
494 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
497 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
498 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
499 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
500 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
503 //----------------------------------------------------------------------------
504 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
507 (*_texCoords)[i + 0].set(tl.x(), tl.y());
508 (*_texCoords)[i + 1].set(br.x(), tl.y());
509 (*_texCoords)[i + 2].set(br.x(), br.y());
510 (*_texCoords)[i + 3].set(tl.x(), br.y());
513 //----------------------------------------------------------------------------
514 Image::CSSOffsets Image::parseSideOffsets(const std::string& str) const
519 const SGRect<int>& dim = getTextureDimensions();
521 // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
523 // Percentages are relative to the size of the image: the width of the
524 // image for the horizontal offsets, the height for vertical offsets.
525 // Numbers represent pixels in the image.
529 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
530 const boost::char_separator<char> del(" \t\n");
532 tokenizer tokens(str.begin(), str.end(), del);
533 for( tokenizer::const_iterator tok = tokens.begin();
534 tok != tokens.end() && c < 4;
537 if( isalpha(*tok->begin()) )
541 bool rel = (*tok->rbegin() == '%');
543 // Negative values are not allowed and values bigger than the size of
544 // the image are interpreted as ‘100%’.
547 boost::lexical_cast<float>
549 rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
555 : (c & 1) ? dim.width() : dim.height()
563 // When four values are specified, they set the offsets on the top, right,
564 // bottom and left sides in that order.
570 // if the right is missing, it is the same as the top.
571 ret.offsets[1] = ret.offsets[0];
573 // if the bottom is missing, it is the same as the top
574 ret.offsets[2] = ret.offsets[0];
577 // If the left is missing, it is the same as the right
578 ret.offsets[3] = ret.offsets[1];
585 } // namespace canvas
586 } // namespace simgear