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>
34 #include <boost/lexical_cast.hpp>
35 #include <boost/range.hpp>
36 #include <boost/tokenizer.hpp>
43 * Callback to enable/disable rendering of canvas displayed inside windows or
47 public osg::Drawable::CullCallback
50 CullCallback(const CanvasWeakPtr& canvas);
54 CanvasWeakPtr _canvas;
55 mutable bool _cull_next_frame;
57 virtual bool cull( osg::NodeVisitor* nv,
58 osg::Drawable* drawable,
59 osg::RenderInfo* renderInfo ) const;
62 //----------------------------------------------------------------------------
63 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
65 _cull_next_frame( false )
70 //----------------------------------------------------------------------------
71 void CullCallback::cullNextFrame()
73 _cull_next_frame = true;
76 //----------------------------------------------------------------------------
77 bool CullCallback::cull( osg::NodeVisitor* nv,
78 osg::Drawable* drawable,
79 osg::RenderInfo* renderInfo ) const
81 if( !_canvas.expired() )
82 _canvas.lock()->enableRendering();
84 if( !_cull_next_frame )
85 // TODO check if window/image should be culled
88 _cull_next_frame = false;
92 //----------------------------------------------------------------------------
93 Image::Image( const CanvasWeakPtr& canvas,
94 const SGPropertyNode_ptr& node,
95 const Style& parent_style,
97 Element(canvas, node, parent_style, parent),
98 _texture(new osg::Texture2D),
99 _node_src_rect( node->getNode("source", 0, true) ),
103 _geom = new osg::Geometry;
104 _geom->setUseDisplayList(false);
106 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
107 stateSet->setTextureAttributeAndModes(0, _texture.get());
108 stateSet->setDataVariance(osg::Object::STATIC);
110 // allocate arrays for the image
111 _vertices = new osg::Vec3Array(4);
112 _vertices->setDataVariance(osg::Object::DYNAMIC);
113 _geom->setVertexArray(_vertices);
115 _texCoords = new osg::Vec2Array(4);
116 _texCoords->setDataVariance(osg::Object::DYNAMIC);
117 _geom->setTexCoordArray(0, _texCoords);
119 _colors = new osg::Vec4Array(1);
120 _colors->setDataVariance(osg::Object::DYNAMIC);
121 _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
122 _geom->setColorArray(_colors);
124 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
125 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
126 _prim->setDataVariance(osg::Object::DYNAMIC);
127 _geom->addPrimitiveSet(_prim);
131 if( !isInit<Image>() )
133 addStyle("fill", "color", &Image::setFill);
134 addStyle("slice", "", &Image::setSlice);
135 addStyle("slice-width", "numeric", &Image::setSliceWidth);
136 addStyle("outset", "", &Image::setOutset);
139 setFill("#ffffff"); // TODO how should we handle default values?
144 //----------------------------------------------------------------------------
150 //----------------------------------------------------------------------------
151 void Image::update(double dt)
155 if( !_attributes_dirty )
158 const SGRect<int>& tex_dim = getTextureDimensions();
160 // http://www.w3.org/TR/css3-background/#border-image-slice
162 // The ‘fill’ keyword, if present, causes the middle part of the image to be
163 // preserved. (By default it is discarded, i.e., treated as empty.)
164 bool fill = (_slice.keyword == "fill");
166 if( _attributes_dirty & DEST_SIZE )
168 size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
170 if( num_vertices != _prim->getNumPrimitives() )
172 _vertices->resize(num_vertices);
173 _texCoords->resize(num_vertices);
174 _prim->setCount(num_vertices);
177 _attributes_dirty |= SRC_RECT;
180 // http://www.w3.org/TR/css3-background/#border-image-outset
181 SGRect<float> region = _region;
184 const CSSOffsets& outset = _outset.getAbsOffsets(tex_dim);
185 region.t() -= outset.t;
186 region.r() += outset.r;
187 region.b() += outset.b;
188 region.l() -= outset.l;
193 setQuad(0, region.getMin(), region.getMax());
198 Image slice, 9-scale, whatever it is called. The four corner images
199 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
200 fill the remaining space up to the specified size.
204 -------------------- - y[0]
206 -------------------- - y[1]
213 -------------------- - y[2]
215 -------------------- - y[3]
218 const CSSOffsets& slice =
219 (_slice_width.valid ? _slice_width : _slice).getAbsOffsets(tex_dim);
222 region.l() + slice.l,
223 region.r() - slice.r,
228 region.t() + slice.t,
229 region.b() - slice.b,
234 for(int ix = 0; ix < 3; ++ix)
235 for(int iy = 0; iy < 3; ++iy)
237 if( ix == 1 && iy == 1 && !fill )
238 // The ‘fill’ keyword, if present, causes the middle part of the
239 // image to be filled.
243 SGVec2f(x[ix ], y[iy ]),
244 SGVec2f(x[ix + 1], y[iy + 1]) );
249 _attributes_dirty &= ~DEST_SIZE;
251 setBoundingBox(_geom->getBound());
254 if( _attributes_dirty & SRC_RECT )
256 SGRect<float> src_rect = _src_rect;
257 if( !_node_src_rect->getBoolValue("normalized", true) )
259 src_rect.t() /= tex_dim.height();
260 src_rect.r() /= tex_dim.width();
261 src_rect.b() /= tex_dim.height();
262 src_rect.l() /= tex_dim.width();
265 // Image coordinate systems y-axis is flipped
266 std::swap(src_rect.t(), src_rect.b());
270 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
274 const CSSOffsets& slice = _slice.getRelOffsets(tex_dim);
277 src_rect.l() + slice.l,
278 src_rect.r() - slice.r,
283 src_rect.t() - slice.t,
284 src_rect.b() + slice.b,
289 for(int ix = 0; ix < 3; ++ix)
290 for(int iy = 0; iy < 3; ++iy)
292 if( ix == 1 && iy == 1 && !fill )
293 // The ‘fill’ keyword, if present, causes the middle part of the
294 // image to be filled.
298 SGVec2f(x[ix ], y[iy ]),
299 SGVec2f(x[ix + 1], y[iy + 1]) );
304 _attributes_dirty &= ~SRC_RECT;
308 //----------------------------------------------------------------------------
309 void Image::valueChanged(SGPropertyNode* child)
311 // If the image is switched from invisible to visible, and it shows a
312 // canvas, we need to delay showing it by one frame to ensure the canvas is
313 // updated before the image is displayed.
315 // As canvas::Element handles and filters changes to the "visible" property
316 // we can not check this in Image::childChanged but instead have to override
317 // Element::valueChanged.
319 && child->getParent() == _node
320 && child->getNameString() == "visible"
321 && child->getBoolValue() )
323 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
328 Element::valueChanged(child);
331 //----------------------------------------------------------------------------
332 void Image::setSrcCanvas(CanvasPtr canvas)
334 if( !_src_canvas.expired() )
335 _src_canvas.lock()->removeParentCanvas(_canvas);
336 if( !_canvas.expired() )
337 _canvas.lock()->removeChildCanvas(_src_canvas);
339 _src_canvas = canvas;
340 _geom->getOrCreateStateSet()
341 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
342 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
344 if( !_src_canvas.expired() )
346 setupDefaultDimensions();
347 _src_canvas.lock()->addParentCanvas(_canvas);
349 if( !_canvas.expired() )
350 _canvas.lock()->addChildCanvas(_src_canvas);
354 //----------------------------------------------------------------------------
355 CanvasWeakPtr Image::getSrcCanvas() const
360 //----------------------------------------------------------------------------
361 void Image::setImage(osg::Image *img)
364 setSrcCanvas( CanvasPtr() );
366 _texture->setImage(img);
367 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
368 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
369 _geom->getOrCreateStateSet()
370 ->setTextureAttributeAndModes(0, _texture);
373 setupDefaultDimensions();
376 //----------------------------------------------------------------------------
377 void Image::setFill(const std::string& fill)
380 if( !parseColor(fill, color) )
383 _colors->front() = color;
387 //----------------------------------------------------------------------------
388 void Image::setSlice(const std::string& slice)
390 _slice = parseSideOffsets(slice);
391 _attributes_dirty |= SRC_RECT | DEST_SIZE;
394 //----------------------------------------------------------------------------
395 void Image::setSliceWidth(const std::string& width)
397 _slice_width = parseSideOffsets(width);
398 _attributes_dirty |= DEST_SIZE;
401 //----------------------------------------------------------------------------
402 void Image::setOutset(const std::string& outset)
404 _outset = parseSideOffsets(outset);
405 _attributes_dirty |= DEST_SIZE;
408 //----------------------------------------------------------------------------
409 const SGRect<float>& Image::getRegion() const
414 //----------------------------------------------------------------------------
415 bool Image::handleEvent(EventPtr event)
417 bool handled = Element::handleEvent(event);
419 CanvasPtr src_canvas = _src_canvas.lock();
423 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
426 mouse_event.reset( new MouseEvent(*mouse_event) );
429 mouse_event->client_pos = mouse_event->local_pos
430 - toOsg(_region.getMin());
432 osg::Vec2f size(_region.width(), _region.height());
435 CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
437 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
438 size.x() += outset.l + outset.r;
439 size.y() += outset.t + outset.b;
442 // Scale event pos according to canvas view size vs. displayed/screen size
443 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
444 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
445 mouse_event->local_pos = mouse_event->client_pos;
448 return handled || src_canvas->handleMouseEvent(mouse_event);
451 //----------------------------------------------------------------------------
453 Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
456 for(int i = 0; i < 4; ++i)
458 ret.val[i] = offsets.val[i];
460 ret.val[i] /= (i & 1) ? dim.height() : dim.width();
465 //----------------------------------------------------------------------------
467 Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
470 for(int i = 0; i < 4; ++i)
472 ret.val[i] = offsets.val[i];
474 ret.val[i] *= (i & 1) ? dim.height() : dim.width();
479 //----------------------------------------------------------------------------
480 void Image::childChanged(SGPropertyNode* child)
482 const std::string& name = child->getNameString();
484 if( child->getParent() == _node_src_rect )
486 _attributes_dirty |= SRC_RECT;
489 _src_rect.setLeft( child->getFloatValue() );
490 else if( name == "right" )
491 _src_rect.setRight( child->getFloatValue() );
492 else if( name == "top" )
493 _src_rect.setTop( child->getFloatValue() );
494 else if( name == "bottom" )
495 _src_rect.setBottom( child->getFloatValue() );
499 else if( child->getParent() != _node )
504 _region.setX( child->getFloatValue() );
505 _attributes_dirty |= DEST_SIZE;
507 else if( name == "y" )
509 _region.setY( child->getFloatValue() );
510 _attributes_dirty |= DEST_SIZE;
512 else if( name == "size" )
514 if( child->getIndex() == 0 )
515 _region.setWidth( child->getFloatValue() );
517 _region.setHeight( child->getFloatValue() );
519 _attributes_dirty |= DEST_SIZE;
521 else if( name == "file" )
523 static const std::string CANVAS_PROTOCOL = "canvas://";
524 const std::string& path = child->getStringValue();
526 CanvasPtr canvas = _canvas.lock();
529 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
533 if( boost::starts_with(path, CANVAS_PROTOCOL) )
535 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
538 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
542 const SGPropertyNode* canvas_node =
543 canvas_mgr->getPropertyRoot()
545 ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
548 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
552 // TODO add support for other means of addressing canvases (eg. by
554 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
557 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
561 setSrcCanvas(src_canvas);
565 setImage( canvas->getSystemAdapter()->getImage(path) );
570 //----------------------------------------------------------------------------
571 void Image::setupDefaultDimensions()
573 if( !_src_rect.width() || !_src_rect.height() )
575 // Show whole image by default
576 _node_src_rect->setBoolValue("normalized", true);
577 _node_src_rect->setFloatValue("right", 1);
578 _node_src_rect->setFloatValue("bottom", 1);
581 if( !_region.width() || !_region.height() )
583 // Default to image size.
584 // TODO handle showing only part of image?
585 const SGRect<int>& dim = getTextureDimensions();
586 _node->setFloatValue("size[0]", dim.width());
587 _node->setFloatValue("size[1]", dim.height());
591 //----------------------------------------------------------------------------
592 SGRect<int> Image::getTextureDimensions() const
594 CanvasPtr canvas = _src_canvas.lock();
595 SGRect<int> dim(0,0);
597 // Use canvas/image dimensions rather than texture dimensions, as they could
598 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
601 dim.setRight( canvas->getViewWidth() );
602 dim.setBottom( canvas->getViewHeight() );
606 osg::Image* img = _texture->getImage();
610 dim.setRight( img->s() );
611 dim.setBottom( img->t() );
618 //----------------------------------------------------------------------------
619 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
622 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
623 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
624 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
625 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
628 //----------------------------------------------------------------------------
629 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
632 (*_texCoords)[i + 0].set(tl.x(), tl.y());
633 (*_texCoords)[i + 1].set(br.x(), tl.y());
634 (*_texCoords)[i + 2].set(br.x(), br.y());
635 (*_texCoords)[i + 3].set(tl.x(), br.y());
638 //----------------------------------------------------------------------------
639 Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
644 // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
646 // Percentages are relative to the size of the image: the width of the
647 // image for the horizontal offsets, the height for vertical offsets.
648 // Numbers represent pixels in the image.
652 typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
653 const boost::char_separator<char> del(" \t\n");
655 tokenizer tokens(str.begin(), str.end(), del);
656 for( tokenizer::const_iterator tok = tokens.begin();
657 tok != tokens.end() && c < 4;
660 if( isalpha(*tok->begin()) )
664 bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
666 // Negative values are not allowed and values bigger than the size of
667 // the image are interpreted as ‘100%’. TODO check max
671 boost::lexical_cast<float>
673 rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
683 // When four values are specified, they set the offsets on the top, right,
684 // bottom and left sides in that order.
686 #define CSS_COPY_VAL(dest, src)\
688 ret.offsets.val[dest] = ret.offsets.val[src];\
689 ret.types.rel[dest] = ret.types.rel[src];\
697 // if the right is missing, it is the same as the top.
700 // if the bottom is missing, it is the same as the top
704 // If the left is missing, it is the same as the right
714 } // namespace canvas
715 } // namespace simgear