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 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
359 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
360 _geom->getOrCreateStateSet()
361 ->setTextureAttributeAndModes(0, _texture);
364 setupDefaultDimensions();
367 //----------------------------------------------------------------------------
368 void Image::setFill(const std::string& fill)
371 if( !parseColor(fill, color) )
374 _colors->front() = color;
378 //----------------------------------------------------------------------------
379 void Image::setSlice(const std::string& slice)
381 _slice = parseSideOffsets(slice);
382 _attributes_dirty |= SRC_RECT | DEST_SIZE;
385 //----------------------------------------------------------------------------
386 void Image::setSliceWidth(const std::string& width)
388 _slice_width = parseSideOffsets(width);
389 _attributes_dirty |= DEST_SIZE;
392 //----------------------------------------------------------------------------
393 void Image::setOutset(const std::string& outset)
395 _outset = parseSideOffsets(outset);
396 _attributes_dirty |= DEST_SIZE;
399 //----------------------------------------------------------------------------
400 const SGRect<float>& Image::getRegion() const
405 //----------------------------------------------------------------------------
406 bool Image::handleMouseEvent(MouseEventPtr event)
408 CanvasPtr src_canvas = _src_canvas.lock();
415 CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
417 event.reset( new MouseEvent(*event) );
418 event->client_pos += osg::Vec2f(outset.l, outset.t);
419 event->client_pos.x() *= src_canvas->getViewWidth()
420 / (_region.width() + outset.l + outset.r);
421 event->client_pos.y() *= src_canvas->getViewHeight()
422 / (_region.height() + outset.t + outset.b);
425 return src_canvas->handleMouseEvent(event);
428 //----------------------------------------------------------------------------
430 Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
433 for(int i = 0; i < 4; ++i)
435 ret.val[i] = offsets.val[i];
437 ret.val[i] /= (i & 1) ? dim.height() : dim.width();
442 //----------------------------------------------------------------------------
444 Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
447 for(int i = 0; i < 4; ++i)
449 ret.val[i] = offsets.val[i];
451 ret.val[i] *= (i & 1) ? dim.height() : dim.width();
456 //----------------------------------------------------------------------------
457 void Image::childChanged(SGPropertyNode* child)
459 const std::string& name = child->getNameString();
461 if( child->getParent() == _node_src_rect )
463 _attributes_dirty |= SRC_RECT;
466 _src_rect.setLeft( child->getFloatValue() );
467 else if( name == "right" )
468 _src_rect.setRight( child->getFloatValue() );
469 else if( name == "top" )
470 _src_rect.setTop( child->getFloatValue() );
471 else if( name == "bottom" )
472 _src_rect.setBottom( child->getFloatValue() );
476 else if( child->getParent() != _node )
481 _region.setX( child->getFloatValue() );
482 _attributes_dirty |= DEST_SIZE;
484 else if( name == "y" )
486 _region.setY( child->getFloatValue() );
487 _attributes_dirty |= DEST_SIZE;
489 else if( name == "size" )
491 if( child->getIndex() == 0 )
492 _region.setWidth( child->getFloatValue() );
494 _region.setHeight( child->getFloatValue() );
496 _attributes_dirty |= DEST_SIZE;
498 else if( name == "file" )
500 static const std::string CANVAS_PROTOCOL = "canvas://";
501 const std::string& path = child->getStringValue();
503 CanvasPtr canvas = _canvas.lock();
506 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
510 if( boost::starts_with(path, CANVAS_PROTOCOL) )
512 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
515 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
519 const SGPropertyNode* canvas_node =
520 canvas_mgr->getPropertyRoot()
522 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
525 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
529 // TODO add support for other means of addressing canvases (eg. by
531 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
534 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
538 setSrcCanvas(src_canvas);
542 setImage( canvas->getSystemAdapter()->getImage(path) );
547 //----------------------------------------------------------------------------
548 void Image::setupDefaultDimensions()
550 if( !_src_rect.width() || !_src_rect.height() )
552 // Show whole image by default
553 _node_src_rect->setBoolValue("normalized", true);
554 _node_src_rect->setFloatValue("right", 1);
555 _node_src_rect->setFloatValue("bottom", 1);
558 if( !_region.width() || !_region.height() )
560 // Default to image size.
561 // TODO handle showing only part of image?
562 const SGRect<int>& dim = getTextureDimensions();
563 _node->setFloatValue("size[0]", dim.width());
564 _node->setFloatValue("size[1]", dim.height());
568 //----------------------------------------------------------------------------
569 SGRect<int> Image::getTextureDimensions() const
571 CanvasPtr canvas = _src_canvas.lock();
572 SGRect<int> dim(0,0);
574 // Use canvas/image dimensions rather than texture dimensions, as they could
575 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
578 dim.setRight( canvas->getViewWidth() );
579 dim.setBottom( canvas->getViewHeight() );
583 osg::Image* img = _texture->getImage();
587 dim.setRight( img->s() );
588 dim.setBottom( img->t() );
595 //----------------------------------------------------------------------------
596 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
599 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
600 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
601 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
602 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
605 //----------------------------------------------------------------------------
606 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
609 (*_texCoords)[i + 0].set(tl.x(), tl.y());
610 (*_texCoords)[i + 1].set(br.x(), tl.y());
611 (*_texCoords)[i + 2].set(br.x(), br.y());
612 (*_texCoords)[i + 3].set(tl.x(), br.y());
615 //----------------------------------------------------------------------------
616 Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
621 // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
623 // Percentages are relative to the size of the image: the width of the
624 // image for the horizontal offsets, the height for vertical offsets.
625 // Numbers represent pixels in the image.
629 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
630 const boost::char_separator<char> del(" \t\n");
632 tokenizer tokens(str.begin(), str.end(), del);
633 for( tokenizer::const_iterator tok = tokens.begin();
634 tok != tokens.end() && c < 4;
637 if( isalpha(*tok->begin()) )
641 bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
643 // Negative values are not allowed and values bigger than the size of
644 // the image are interpreted as ‘100%’. TODO check max
648 boost::lexical_cast<float>
650 rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
660 // When four values are specified, they set the offsets on the top, right,
661 // bottom and left sides in that order.
663 #define CSS_COPY_VAL(dest, src)\
665 ret.offsets.val[dest] = ret.offsets.val[src];\
666 ret.types.rel[dest] = ret.types.rel[src];\
674 // if the right is missing, it is the same as the top.
677 // if the bottom is missing, it is the same as the top
681 // If the left is missing, it is the same as the right
691 } // namespace canvas
692 } // namespace simgear