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