]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
Canvas Image: fix updating while showing nested canvas.
[simgear.git] / simgear / canvas / elements / CanvasImage.cxx
1 // An image on the Canvas
2 //
3 // Copyright (C) 2012  Thomas Geymayer <tomgey@gmail.com>
4 //
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.
9 //
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.
14 //
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
18
19 #include "CanvasImage.hxx"
20
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>
28
29 #include <osg/Array>
30 #include <osg/Geometry>
31 #include <osg/PrimitiveSet>
32
33 #include <boost/algorithm/string/predicate.hpp>
34
35 namespace simgear
36 {
37 namespace canvas
38 {
39   /**
40    * Callback to enable/disable rendering of canvas displayed inside windows or
41    * other canvases.
42    */
43   class CullCallback:
44     public osg::Drawable::CullCallback
45   {
46     public:
47       CullCallback(const CanvasWeakPtr& canvas);
48       void cullNextFrame();
49
50     private:
51       CanvasWeakPtr _canvas;
52       mutable bool  _cull_next_frame;
53
54       virtual bool cull( osg::NodeVisitor* nv,
55                          osg::Drawable* drawable,
56                          osg::RenderInfo* renderInfo ) const;
57   };
58
59   //----------------------------------------------------------------------------
60   CullCallback::CullCallback(const CanvasWeakPtr& canvas):
61     _canvas( canvas ),
62     _cull_next_frame( false )
63   {
64
65   }
66
67   //----------------------------------------------------------------------------
68   void CullCallback::cullNextFrame()
69   {
70     _cull_next_frame = true;
71   }
72
73   //----------------------------------------------------------------------------
74   bool CullCallback::cull( osg::NodeVisitor* nv,
75                            osg::Drawable* drawable,
76                            osg::RenderInfo* renderInfo ) const
77   {
78     if( !_canvas.expired() )
79       _canvas.lock()->enableRendering();
80
81     if( !_cull_next_frame )
82       // TODO check if window/image should be culled
83       return false;
84
85     _cull_next_frame = false;
86     return true;
87   }
88
89   //----------------------------------------------------------------------------
90   Image::Image( const CanvasWeakPtr& canvas,
91                 const SGPropertyNode_ptr& node,
92                 const Style& parent_style,
93                 Element* parent ):
94     Element(canvas, node, parent_style, parent),
95     _texture(new osg::Texture2D),
96     _node_src_rect( node->getNode("source", 0, true) ),
97     _src_rect(0,0),
98     _region(0,0)
99   {
100     _geom = new osg::Geometry;
101     _geom->setUseDisplayList(false);
102
103     osg::StateSet *stateSet = _geom->getOrCreateStateSet();
104     stateSet->setTextureAttributeAndModes(0, _texture.get());
105     stateSet->setDataVariance(osg::Object::STATIC);
106
107     // allocate arrays for the image
108     _vertices = new osg::Vec3Array(4);
109     _vertices->setDataVariance(osg::Object::DYNAMIC);
110     _geom->setVertexArray(_vertices);
111
112     _texCoords = new osg::Vec2Array(4);
113     _texCoords->setDataVariance(osg::Object::DYNAMIC);
114     _geom->setTexCoordArray(0, _texCoords);
115
116     _colors = new osg::Vec4Array(1);
117     _colors->setDataVariance(osg::Object::DYNAMIC);
118     _geom->setColorBinding(osg::Geometry::BIND_OVERALL);
119     _geom->setColorArray(_colors);
120
121     _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
122     _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
123     _prim->setDataVariance(osg::Object::DYNAMIC);
124     _geom->addPrimitiveSet(_prim);
125
126     setDrawable(_geom);
127
128     if( !isInit<Image>() )
129     {
130       addStyle("fill", "color", &Image::setFill);
131       addStyle("slice", "", &Image::setSlice);
132       addStyle("slice-width", "numeric", &Image::setSliceWidth);
133       addStyle("outset", "", &Image::setOutset);
134     }
135
136     setFill("#ffffff"); // TODO how should we handle default values?
137
138     setupStyle();
139   }
140
141   //----------------------------------------------------------------------------
142   Image::~Image()
143   {
144
145   }
146
147   //----------------------------------------------------------------------------
148   void Image::update(double dt)
149   {
150     Element::update(dt);
151
152     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
153     (
154       _geom->getOrCreateStateSet()
155            ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
156     );
157     simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
158
159     if(    (_attributes_dirty & SRC_CANVAS)
160            // check if texture has changed (eg. due to resizing)
161         || (canvas && texture != canvas->getTexture()) )
162     {
163       _geom->getOrCreateStateSet()
164            ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
165
166       if( !canvas || canvas->isInit() )
167         _attributes_dirty &= ~SRC_CANVAS;
168     }
169
170     if( !_attributes_dirty )
171       return;
172
173     const SGRect<int>& tex_dim = getTextureDimensions();
174
175     // http://www.w3.org/TR/css3-background/#border-image-slice
176
177     // The ‘fill’ keyword, if present, causes the middle part of the image to be
178     // preserved. (By default it is discarded, i.e., treated as empty.)
179     bool fill = (_slice.getKeyword() == "fill");
180
181     if( _attributes_dirty & DEST_SIZE )
182     {
183       size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
184
185       if( num_vertices != _prim->getNumPrimitives() )
186       {
187         _vertices->resize(num_vertices);
188         _texCoords->resize(num_vertices);
189         _prim->setCount(num_vertices);
190         _prim->dirty();
191
192         _attributes_dirty |= SRC_RECT;
193       }
194
195       // http://www.w3.org/TR/css3-background/#border-image-outset
196       SGRect<float> region = _region;
197       if( _outset.isValid() )
198       {
199         const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
200         region.t() -= outset.t;
201         region.r() += outset.r;
202         region.b() += outset.b;
203         region.l() -= outset.l;
204       }
205
206       if( !_slice.isValid() )
207       {
208         setQuad(0, region.getMin(), region.getMax());
209       }
210       else
211       {
212         /*
213         Image slice, 9-scale, whatever it is called. The four corner images
214         stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
215         fill the remaining space up to the specified size.
216
217         x[0] x[1]     x[2] x[3]
218           |    |        |    |
219           -------------------- - y[0]
220           | tl |   top  | tr |
221           -------------------- - y[1]
222           |    |        |    |
223           | l  |        |  r |
224           | e  | center |  i |
225           | f  |        |  g |
226           | t  |        |  h |
227           |    |        |  t |
228           -------------------- - y[2]
229           | bl | bottom | br |
230           -------------------- - y[3]
231          */
232
233         const CSSBorder::Offsets& slice =
234           (_slice_width.isValid() ? _slice_width : _slice)
235           .getAbsOffsets(tex_dim);
236         float x[4] = {
237           region.l(),
238           region.l() + slice.l,
239           region.r() - slice.r,
240           region.r()
241         };
242         float y[4] = {
243           region.t(),
244           region.t() + slice.t,
245           region.b() - slice.b,
246           region.b()
247         };
248
249         int i = 0;
250         for(int ix = 0; ix < 3; ++ix)
251           for(int iy = 0; iy < 3; ++iy)
252           {
253             if( ix == 1 && iy == 1 && !fill )
254               // The ‘fill’ keyword, if present, causes the middle part of the
255               // image to be filled.
256               continue;
257
258             setQuad( i++,
259                      SGVec2f(x[ix    ], y[iy    ]),
260                      SGVec2f(x[ix + 1], y[iy + 1]) );
261           }
262       }
263
264       _vertices->dirty();
265       _attributes_dirty &= ~DEST_SIZE;
266       _geom->dirtyBound();
267       setBoundingBox(_geom->getBound());
268     }
269
270     if( _attributes_dirty & SRC_RECT )
271     {
272       SGRect<float> src_rect = _src_rect;
273       if( !_node_src_rect->getBoolValue("normalized", true) )
274       {
275         src_rect.t() /= tex_dim.height();
276         src_rect.r() /= tex_dim.width();
277         src_rect.b() /= tex_dim.height();
278         src_rect.l() /= tex_dim.width();
279       }
280
281       // Image coordinate systems y-axis is flipped
282       std::swap(src_rect.t(), src_rect.b());
283
284       if( !_slice.isValid() )
285       {
286         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
287       }
288       else
289       {
290         const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
291         float x[4] = {
292           src_rect.l(),
293           src_rect.l() + slice.l,
294           src_rect.r() - slice.r,
295           src_rect.r()
296         };
297         float y[4] = {
298           src_rect.t(),
299           src_rect.t() - slice.t,
300           src_rect.b() + slice.b,
301           src_rect.b()
302         };
303
304         int i = 0;
305         for(int ix = 0; ix < 3; ++ix)
306           for(int iy = 0; iy < 3; ++iy)
307           {
308             if( ix == 1 && iy == 1 && !fill )
309               // The ‘fill’ keyword, if present, causes the middle part of the
310               // image to be filled.
311               continue;
312
313             setQuadUV( i++,
314                        SGVec2f(x[ix    ], y[iy    ]),
315                        SGVec2f(x[ix + 1], y[iy + 1]) );
316           }
317       }
318
319       _texCoords->dirty();
320       _attributes_dirty &= ~SRC_RECT;
321     }
322   }
323
324   //----------------------------------------------------------------------------
325   void Image::valueChanged(SGPropertyNode* child)
326   {
327     // If the image is switched from invisible to visible, and it shows a
328     // canvas, we need to delay showing it by one frame to ensure the canvas is
329     // updated before the image is displayed.
330     //
331     // As canvas::Element handles and filters changes to the "visible" property
332     // we can not check this in Image::childChanged but instead have to override
333     // Element::valueChanged.
334     if(    !isVisible()
335         && child->getParent() == _node
336         && child->getNameString() == "visible"
337         && child->getBoolValue() )
338     {
339       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
340       if( cb )
341         cb->cullNextFrame();
342     }
343
344     Element::valueChanged(child);
345   }
346
347   //----------------------------------------------------------------------------
348   void Image::setSrcCanvas(CanvasPtr canvas)
349   {
350     if( !_src_canvas.expired() )
351       _src_canvas.lock()->removeParentCanvas(_canvas);
352     if( !_canvas.expired() )
353       _canvas.lock()->removeChildCanvas(_src_canvas);
354
355     _src_canvas = canvas;
356     _attributes_dirty |= SRC_CANVAS;
357     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
358
359     if( !_src_canvas.expired() )
360     {
361       setupDefaultDimensions();
362       _src_canvas.lock()->addParentCanvas(_canvas);
363
364       if( !_canvas.expired() )
365         _canvas.lock()->addChildCanvas(_src_canvas);
366     }
367   }
368
369   //----------------------------------------------------------------------------
370   CanvasWeakPtr Image::getSrcCanvas() const
371   {
372     return _src_canvas;
373   }
374
375   //----------------------------------------------------------------------------
376   void Image::setImage(osg::Image *img)
377   {
378     // remove canvas...
379     setSrcCanvas( CanvasPtr() );
380
381     _texture->setImage(img);
382     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
383     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
384     _geom->getOrCreateStateSet()
385          ->setTextureAttributeAndModes(0, _texture);
386
387     if( img )
388       setupDefaultDimensions();
389   }
390
391   //----------------------------------------------------------------------------
392   void Image::setFill(const std::string& fill)
393   {
394     osg::Vec4 color;
395     if( !parseColor(fill, color) )
396       return;
397
398     _colors->front() = color;
399     _colors->dirty();
400   }
401
402   //----------------------------------------------------------------------------
403   void Image::setSlice(const std::string& slice)
404   {
405     _slice = CSSBorder::parse(slice);
406     _attributes_dirty |= SRC_RECT | DEST_SIZE;
407   }
408
409   //----------------------------------------------------------------------------
410   void Image::setSliceWidth(const std::string& width)
411   {
412     _slice_width = CSSBorder::parse(width);
413     _attributes_dirty |= DEST_SIZE;
414   }
415
416   //----------------------------------------------------------------------------
417   void Image::setOutset(const std::string& outset)
418   {
419     _outset = CSSBorder::parse(outset);
420     _attributes_dirty |= DEST_SIZE;
421   }
422
423   //----------------------------------------------------------------------------
424   const SGRect<float>& Image::getRegion() const
425   {
426     return _region;
427   }
428
429   //----------------------------------------------------------------------------
430   bool Image::handleEvent(EventPtr event)
431   {
432     bool handled = Element::handleEvent(event);
433
434     CanvasPtr src_canvas = _src_canvas.lock();
435     if( !src_canvas )
436       return handled;
437
438     MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
439     if( mouse_event )
440     {
441       mouse_event.reset( new MouseEvent(*mouse_event) );
442       event = mouse_event;
443
444       mouse_event->client_pos = mouse_event->local_pos
445                               - toOsg(_region.getMin());
446
447       osg::Vec2f size(_region.width(), _region.height());
448       if( _outset.isValid() )
449       {
450         CSSBorder::Offsets outset =
451           _outset.getAbsOffsets(getTextureDimensions());
452
453         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
454         size.x() += outset.l + outset.r;
455         size.y() += outset.t + outset.b;
456       }
457
458       // Scale event pos according to canvas view size vs. displayed/screen size
459       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
460       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
461       mouse_event->local_pos = mouse_event->client_pos;
462     }
463
464     return handled || src_canvas->handleMouseEvent(mouse_event);
465   }
466
467   //----------------------------------------------------------------------------
468   void Image::childChanged(SGPropertyNode* child)
469   {
470     const std::string& name = child->getNameString();
471
472     if( child->getParent() == _node_src_rect )
473     {
474       _attributes_dirty |= SRC_RECT;
475
476       if(      name == "left" )
477         _src_rect.setLeft( child->getFloatValue() );
478       else if( name == "right" )
479         _src_rect.setRight( child->getFloatValue() );
480       else if( name == "top" )
481         _src_rect.setTop( child->getFloatValue() );
482       else if( name == "bottom" )
483         _src_rect.setBottom( child->getFloatValue() );
484
485       return;
486     }
487     else if( child->getParent() != _node )
488       return;
489
490     if( name == "x" )
491     {
492       _region.setX( child->getFloatValue() );
493       _attributes_dirty |= DEST_SIZE;
494     }
495     else if( name == "y" )
496     {
497       _region.setY( child->getFloatValue() );
498       _attributes_dirty |= DEST_SIZE;
499     }
500     else if( name == "size" )
501     {
502       if( child->getIndex() == 0 )
503         _region.setWidth( child->getFloatValue() );
504       else
505         _region.setHeight( child->getFloatValue() );
506
507       _attributes_dirty |= DEST_SIZE;
508     }
509     else if( name == "file" )
510     {
511       static const std::string CANVAS_PROTOCOL = "canvas://";
512       const std::string& path = child->getStringValue();
513
514       CanvasPtr canvas = _canvas.lock();
515       if( !canvas )
516       {
517         SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
518         return;
519       }
520
521       if( boost::starts_with(path, CANVAS_PROTOCOL) )
522       {
523         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
524         if( !canvas_mgr )
525         {
526           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
527           return;
528         }
529
530         const SGPropertyNode* canvas_node =
531           canvas_mgr->getPropertyRoot()
532                     ->getParent()
533                     ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
534         if( !canvas_node )
535         {
536           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
537           return;
538         }
539
540         // TODO add support for other means of addressing canvases (eg. by
541         // name)
542         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
543         if( !src_canvas )
544         {
545           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
546           return;
547         }
548
549         setSrcCanvas(src_canvas);
550       }
551       else
552       {
553         setImage( canvas->getSystemAdapter()->getImage(path) );
554       }
555     }
556   }
557
558   //----------------------------------------------------------------------------
559   void Image::setupDefaultDimensions()
560   {
561     if( !_src_rect.width() || !_src_rect.height() )
562     {
563       // Show whole image by default
564       _node_src_rect->setBoolValue("normalized", true);
565       _node_src_rect->setFloatValue("right", 1);
566       _node_src_rect->setFloatValue("bottom", 1);
567     }
568
569     if( !_region.width() || !_region.height() )
570     {
571       // Default to image size.
572       // TODO handle showing only part of image?
573       const SGRect<int>& dim = getTextureDimensions();
574       _node->setFloatValue("size[0]", dim.width());
575       _node->setFloatValue("size[1]", dim.height());
576     }
577   }
578
579   //----------------------------------------------------------------------------
580   SGRect<int> Image::getTextureDimensions() const
581   {
582     CanvasPtr canvas = _src_canvas.lock();
583     SGRect<int> dim(0,0);
584
585     // Use canvas/image dimensions rather than texture dimensions, as they could
586     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
587     if( canvas )
588     {
589       dim.setRight( canvas->getViewWidth() );
590       dim.setBottom( canvas->getViewHeight() );
591     }
592     else if( _texture )
593     {
594       osg::Image* img = _texture->getImage();
595
596       if( img )
597       {
598         dim.setRight( img->s() );
599         dim.setBottom( img->t() );
600       }
601     }
602
603     return dim;
604   }
605
606   //----------------------------------------------------------------------------
607   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
608   {
609     int i = index * 4;
610     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
611     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
612     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
613     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
614   }
615
616   //----------------------------------------------------------------------------
617   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
618   {
619     int i = index * 4;
620     (*_texCoords)[i + 0].set(tl.x(), tl.y());
621     (*_texCoords)[i + 1].set(br.x(), tl.y());
622     (*_texCoords)[i + 2].set(br.x(), br.y());
623     (*_texCoords)[i + 3].set(tl.x(), br.y());
624   }
625
626 } // namespace canvas
627 } // namespace simgear