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 addStyle("fill", &Image::setFill, this);
131 addStyle("slice", &Image::setSlice, this);
132 addStyle("slice-width", &Image::setSliceWidth, this);
133 addStyle("outset", &Image::setOutset, this);
135 setFill("#ffffff"); // TODO how should we handle default values?
140 //----------------------------------------------------------------------------
146 //----------------------------------------------------------------------------
147 void Image::update(double dt)
151 if( !_attributes_dirty )
154 const SGRect<int>& tex_dim = getTextureDimensions();
156 // http://www.w3.org/TR/css3-background/#border-image-slice
158 // The ‘fill’ keyword, if present, causes the middle part of the image to be
159 // preserved. (By default it is discarded, i.e., treated as empty.)
160 bool fill = (_slice.keyword == "fill");
162 if( _attributes_dirty & DEST_SIZE )
164 size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
166 if( num_vertices != _prim->getNumPrimitives() )
168 _vertices->resize(num_vertices);
169 _texCoords->resize(num_vertices);
170 _prim->setCount(num_vertices);
173 _attributes_dirty |= SRC_RECT;
176 // http://www.w3.org/TR/css3-background/#border-image-outset
177 SGRect<float> region = _region;
180 const CSSOffsets& outset = _outset.getAbsOffsets(tex_dim);
181 region.t() -= outset.t;
182 region.r() += outset.r;
183 region.b() += outset.b;
184 region.l() -= outset.l;
189 setQuad(0, region.getMin(), region.getMax());
194 Image slice, 9-scale, whatever it is called. The four corner images
195 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
196 fill the remaining space up to the specified size.
200 -------------------- - y[0]
202 -------------------- - y[1]
209 -------------------- - y[2]
211 -------------------- - y[3]
214 const CSSOffsets& slice =
215 (_slice_width.valid ? _slice_width : _slice).getAbsOffsets(tex_dim);
218 region.l() + slice.l,
219 region.r() - slice.r,
224 region.t() + slice.t,
225 region.b() - slice.b,
230 for(int ix = 0; ix < 3; ++ix)
231 for(int iy = 0; iy < 3; ++iy)
233 if( ix == 1 && iy == 1 && !fill )
234 // The ‘fill’ keyword, if present, causes the middle part of the
235 // image to be filled.
239 SGVec2f(x[ix ], y[iy ]),
240 SGVec2f(x[ix + 1], y[iy + 1]) );
245 _attributes_dirty &= ~DEST_SIZE;
247 setBoundingBox(_geom->getBound());
250 if( _attributes_dirty & SRC_RECT )
252 SGRect<float> src_rect = _src_rect;
253 if( !_node_src_rect->getBoolValue("normalized", true) )
255 src_rect.t() /= tex_dim.height();
256 src_rect.r() /= tex_dim.width();
257 src_rect.b() /= tex_dim.height();
258 src_rect.l() /= tex_dim.width();
261 // Image coordinate systems y-axis is flipped
262 std::swap(src_rect.t(), src_rect.b());
266 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
270 const CSSOffsets& slice = _slice.getRelOffsets(tex_dim);
273 src_rect.l() + slice.l,
274 src_rect.r() - slice.r,
279 src_rect.t() - slice.t,
280 src_rect.b() + slice.b,
285 for(int ix = 0; ix < 3; ++ix)
286 for(int iy = 0; iy < 3; ++iy)
288 if( ix == 1 && iy == 1 && !fill )
289 // The ‘fill’ keyword, if present, causes the middle part of the
290 // image to be filled.
294 SGVec2f(x[ix ], y[iy ]),
295 SGVec2f(x[ix + 1], y[iy + 1]) );
300 _attributes_dirty &= ~SRC_RECT;
304 //----------------------------------------------------------------------------
305 void Image::valueChanged(SGPropertyNode* child)
307 // If the image is switched from invisible to visible, and it shows a
308 // canvas, we need to delay showing it by one frame to ensure the canvas is
309 // updated before the image is displayed.
311 // As canvas::Element handles and filters changes to the "visible" property
312 // we can not check this in Image::childChanged but instead have to override
313 // Element::valueChanged.
315 && child->getParent() == _node
316 && child->getNameString() == "visible"
317 && child->getBoolValue() )
319 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
324 Element::valueChanged(child);
327 //----------------------------------------------------------------------------
328 void Image::setSrcCanvas(CanvasPtr canvas)
330 if( !_src_canvas.expired() )
331 _src_canvas.lock()->removeDependentCanvas(_canvas);
333 _src_canvas = canvas;
334 _geom->getOrCreateStateSet()
335 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
336 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
338 if( !_src_canvas.expired() )
340 setupDefaultDimensions();
341 _src_canvas.lock()->addDependentCanvas(_canvas);
345 //----------------------------------------------------------------------------
346 CanvasWeakPtr Image::getSrcCanvas() const
351 //----------------------------------------------------------------------------
352 void Image::setImage(osg::Image *img)
355 setSrcCanvas( CanvasPtr() );
357 _texture->setImage(img);
358 _geom->getOrCreateStateSet()
359 ->setTextureAttributeAndModes(0, _texture);
362 setupDefaultDimensions();
365 //----------------------------------------------------------------------------
366 void Image::setFill(const std::string& fill)
369 if( !parseColor(fill, color) )
372 _colors->front() = color;
376 //----------------------------------------------------------------------------
377 void Image::setSlice(const std::string& slice)
379 _slice = parseSideOffsets(slice);
380 _attributes_dirty |= SRC_RECT | DEST_SIZE;
383 //----------------------------------------------------------------------------
384 void Image::setSliceWidth(const std::string& width)
386 _slice_width = parseSideOffsets(width);
387 _attributes_dirty |= DEST_SIZE;
390 //----------------------------------------------------------------------------
391 void Image::setOutset(const std::string& outset)
393 _outset = parseSideOffsets(outset);
394 _attributes_dirty |= DEST_SIZE;
397 //----------------------------------------------------------------------------
398 const SGRect<float>& Image::getRegion() const
403 //----------------------------------------------------------------------------
404 bool Image::handleMouseEvent(MouseEventPtr event)
406 CanvasPtr src_canvas = _src_canvas.lock();
413 CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
415 event.reset( new MouseEvent(*event) );
416 event->client_pos += osg::Vec2f(outset.l, outset.t);
417 event->client_pos.x() *= src_canvas->getViewWidth()
418 / (_region.width() + outset.l + outset.r);
419 event->client_pos.y() *= src_canvas->getViewHeight()
420 / (_region.height() + outset.t + outset.b);
423 return src_canvas->handleMouseEvent(event);
426 //----------------------------------------------------------------------------
428 Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
431 for(int i = 0; i < 4; ++i)
433 ret.val[i] = offsets.val[i];
435 ret.val[i] /= (i & 1) ? dim.height() : dim.width();
440 //----------------------------------------------------------------------------
442 Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
445 for(int i = 0; i < 4; ++i)
447 ret.val[i] = offsets.val[i];
449 ret.val[i] *= (i & 1) ? dim.height() : dim.width();
454 //----------------------------------------------------------------------------
455 void Image::childChanged(SGPropertyNode* child)
457 const std::string& name = child->getNameString();
459 if( child->getParent() == _node_src_rect )
461 _attributes_dirty |= SRC_RECT;
464 _src_rect.setLeft( child->getFloatValue() );
465 else if( name == "right" )
466 _src_rect.setRight( child->getFloatValue() );
467 else if( name == "top" )
468 _src_rect.setTop( child->getFloatValue() );
469 else if( name == "bottom" )
470 _src_rect.setBottom( child->getFloatValue() );
474 else if( child->getParent() != _node )
479 _region.setX( child->getFloatValue() );
480 _attributes_dirty |= DEST_SIZE;
482 else if( name == "y" )
484 _region.setY( child->getFloatValue() );
485 _attributes_dirty |= DEST_SIZE;
487 else if( name == "size" )
489 if( child->getIndex() == 0 )
490 _region.setWidth( child->getFloatValue() );
492 _region.setHeight( child->getFloatValue() );
494 _attributes_dirty |= DEST_SIZE;
496 else if( name == "file" )
498 static const std::string CANVAS_PROTOCOL = "canvas://";
499 const std::string& path = child->getStringValue();
501 CanvasPtr canvas = _canvas.lock();
504 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
508 if( boost::starts_with(path, CANVAS_PROTOCOL) )
510 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
513 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
517 const SGPropertyNode* canvas_node =
518 canvas_mgr->getPropertyRoot()
520 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
523 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
527 // TODO add support for other means of addressing canvases (eg. by
529 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
532 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
536 setSrcCanvas(src_canvas);
540 setImage( canvas->getSystemAdapter()->getImage(path) );
545 //----------------------------------------------------------------------------
546 void Image::setupDefaultDimensions()
548 if( !_src_rect.width() || !_src_rect.height() )
550 // Show whole image by default
551 _node_src_rect->setBoolValue("normalized", true);
552 _node_src_rect->setFloatValue("right", 1);
553 _node_src_rect->setFloatValue("bottom", 1);
556 if( !_region.width() || !_region.height() )
558 // Default to image size.
559 // TODO handle showing only part of image?
560 const SGRect<int>& dim = getTextureDimensions();
561 _node->setFloatValue("size[0]", dim.width());
562 _node->setFloatValue("size[1]", dim.height());
566 //----------------------------------------------------------------------------
567 SGRect<int> Image::getTextureDimensions() const
569 CanvasPtr canvas = _src_canvas.lock();
570 SGRect<int> dim(0,0);
572 // Use canvas/image dimensions rather than texture dimensions, as they could
573 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
576 dim.setRight( canvas->getViewWidth() );
577 dim.setBottom( canvas->getViewHeight() );
581 osg::Image* img = _texture->getImage();
585 dim.setRight( img->s() );
586 dim.setBottom( img->t() );
593 //----------------------------------------------------------------------------
594 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
597 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
598 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
599 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
600 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
603 //----------------------------------------------------------------------------
604 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
607 (*_texCoords)[i + 0].set(tl.x(), tl.y());
608 (*_texCoords)[i + 1].set(br.x(), tl.y());
609 (*_texCoords)[i + 2].set(br.x(), br.y());
610 (*_texCoords)[i + 3].set(tl.x(), br.y());
613 //----------------------------------------------------------------------------
614 Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
619 // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
621 // Percentages are relative to the size of the image: the width of the
622 // image for the horizontal offsets, the height for vertical offsets.
623 // Numbers represent pixels in the image.
627 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
628 const boost::char_separator<char> del(" \t\n");
630 tokenizer tokens(str.begin(), str.end(), del);
631 for( tokenizer::const_iterator tok = tokens.begin();
632 tok != tokens.end() && c < 4;
635 if( isalpha(*tok->begin()) )
639 bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
641 // Negative values are not allowed and values bigger than the size of
642 // the image are interpreted as ‘100%’. TODO check max
646 boost::lexical_cast<float>
648 rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
658 // When four values are specified, they set the offsets on the top, right,
659 // bottom and left sides in that order.
661 #define CSS_COPY_VAL(dest, src)\
663 ret.offsets.val[dest] = ret.offsets.val[src];\
664 ret.types.rel[dest] = ret.types.rel[src];\
672 // if the right is missing, it is the same as the top.
675 // if the bottom is missing, it is the same as the top
679 // If the left is missing, it is the same as the right
689 } // namespace canvas
690 } // namespace simgear