]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
Fix uninitialized member.
[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     addStyle("fill", &Image::setFill, this);
131     addStyle("slice", &Image::setSlice, this);
132     addStyle("slice-width", &Image::setSliceWidth, this);
133     addStyle("outset", &Image::setOutset, this);
134
135     setFill("#ffffff"); // TODO how should we handle default values?
136
137     setupStyle();
138   }
139
140   //----------------------------------------------------------------------------
141   Image::~Image()
142   {
143
144   }
145
146   //----------------------------------------------------------------------------
147   void Image::update(double dt)
148   {
149     Element::update(dt);
150
151     if( !_attributes_dirty )
152       return;
153
154     const SGRect<int>& tex_dim = getTextureDimensions();
155
156     // http://www.w3.org/TR/css3-background/#border-image-slice
157
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");
161
162     if( _attributes_dirty & DEST_SIZE )
163     {
164       size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
165
166       if( num_vertices != _prim->getNumPrimitives() )
167       {
168         _vertices->resize(num_vertices);
169         _texCoords->resize(num_vertices);
170         _prim->setCount(num_vertices);
171         _prim->dirty();
172
173         _attributes_dirty |= SRC_RECT;
174       }
175
176       // http://www.w3.org/TR/css3-background/#border-image-outset
177       SGRect<float> region = _region;
178       if( _outset.valid )
179       {
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;
185       }
186
187       if( !_slice.valid )
188       {
189         setQuad(0, region.getMin(), region.getMax());
190       }
191       else
192       {
193         /*
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.
197
198         x[0] x[1]     x[2] x[3]
199           |    |        |    |
200           -------------------- - y[0]
201           | tl |   top  | tr |
202           -------------------- - y[1]
203           |    |        |    |
204           | l  |        |  r |
205           | e  | center |  i |
206           | f  |        |  g |
207           | t  |        |  h |
208           |    |        |  t |
209           -------------------- - y[2]
210           | bl | bottom | br |
211           -------------------- - y[3]
212          */
213
214         const CSSOffsets& slice =
215           (_slice_width.valid ? _slice_width : _slice).getAbsOffsets(tex_dim);
216         float x[4] = {
217           region.l(),
218           region.l() + slice.l,
219           region.r() - slice.r,
220           region.r()
221         };
222         float y[4] = {
223           region.t(),
224           region.t() + slice.t,
225           region.b() - slice.b,
226           region.b()
227         };
228
229         int i = 0;
230         for(int ix = 0; ix < 3; ++ix)
231           for(int iy = 0; iy < 3; ++iy)
232           {
233             if( ix == 1 && iy == 1 && !fill )
234               // The ‘fill’ keyword, if present, causes the middle part of the
235               // image to be filled.
236               continue;
237
238             setQuad( i++,
239                      SGVec2f(x[ix    ], y[iy    ]),
240                      SGVec2f(x[ix + 1], y[iy + 1]) );
241           }
242       }
243
244       _vertices->dirty();
245       _attributes_dirty &= ~DEST_SIZE;
246       _geom->dirtyBound();
247       setBoundingBox(_geom->getBound());
248     }
249
250     if( _attributes_dirty & SRC_RECT )
251     {
252       SGRect<float> src_rect = _src_rect;
253       if( !_node_src_rect->getBoolValue("normalized", true) )
254       {
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();
259       }
260
261       // Image coordinate systems y-axis is flipped
262       std::swap(src_rect.t(), src_rect.b());
263
264       if( !_slice.valid )
265       {
266         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
267       }
268       else
269       {
270         const CSSOffsets& slice = _slice.getRelOffsets(tex_dim);
271         float x[4] = {
272           src_rect.l(),
273           src_rect.l() + slice.l,
274           src_rect.r() - slice.r,
275           src_rect.r()
276         };
277         float y[4] = {
278           src_rect.t(),
279           src_rect.t() - slice.t,
280           src_rect.b() + slice.b,
281           src_rect.b()
282         };
283
284         int i = 0;
285         for(int ix = 0; ix < 3; ++ix)
286           for(int iy = 0; iy < 3; ++iy)
287           {
288             if( ix == 1 && iy == 1 && !fill )
289               // The ‘fill’ keyword, if present, causes the middle part of the
290               // image to be filled.
291               continue;
292
293             setQuadUV( i++,
294                        SGVec2f(x[ix    ], y[iy    ]),
295                        SGVec2f(x[ix + 1], y[iy + 1]) );
296           }
297       }
298
299       _texCoords->dirty();
300       _attributes_dirty &= ~SRC_RECT;
301     }
302   }
303
304   //----------------------------------------------------------------------------
305   void Image::valueChanged(SGPropertyNode* child)
306   {
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.
310     //
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.
314     if(    !isVisible()
315         && child->getParent() == _node
316         && child->getNameString() == "visible"
317         && child->getBoolValue() )
318     {
319       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
320       if( cb )
321         cb->cullNextFrame();
322     }
323
324     Element::valueChanged(child);
325   }
326
327   //----------------------------------------------------------------------------
328   void Image::setSrcCanvas(CanvasPtr canvas)
329   {
330     if( !_src_canvas.expired() )
331       _src_canvas.lock()->removeDependentCanvas(_canvas);
332
333     _src_canvas = canvas;
334     _geom->getOrCreateStateSet()
335          ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
336     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
337
338     if( !_src_canvas.expired() )
339     {
340       setupDefaultDimensions();
341       _src_canvas.lock()->addDependentCanvas(_canvas);
342     }
343   }
344
345   //----------------------------------------------------------------------------
346   CanvasWeakPtr Image::getSrcCanvas() const
347   {
348     return _src_canvas;
349   }
350
351   //----------------------------------------------------------------------------
352   void Image::setImage(osg::Image *img)
353   {
354     // remove canvas...
355     setSrcCanvas( CanvasPtr() );
356
357     _texture->setImage(img);
358     _geom->getOrCreateStateSet()
359          ->setTextureAttributeAndModes(0, _texture);
360
361     if( img )
362       setupDefaultDimensions();
363   }
364
365   //----------------------------------------------------------------------------
366   void Image::setFill(const std::string& fill)
367   {
368     osg::Vec4 color;
369     if( !parseColor(fill, color) )
370       return;
371
372     _colors->front() = color;
373     _colors->dirty();
374   }
375
376   //----------------------------------------------------------------------------
377   void Image::setSlice(const std::string& slice)
378   {
379     _slice = parseSideOffsets(slice);
380     _attributes_dirty |= SRC_RECT | DEST_SIZE;
381   }
382
383   //----------------------------------------------------------------------------
384   void Image::setSliceWidth(const std::string& width)
385   {
386     _slice_width = parseSideOffsets(width);
387     _attributes_dirty |= DEST_SIZE;
388   }
389
390   //----------------------------------------------------------------------------
391   void Image::setOutset(const std::string& outset)
392   {
393     _outset = parseSideOffsets(outset);
394     _attributes_dirty |= DEST_SIZE;
395   }
396
397   //----------------------------------------------------------------------------
398   const SGRect<float>& Image::getRegion() const
399   {
400     return _region;
401   }
402
403   //----------------------------------------------------------------------------
404   bool Image::handleMouseEvent(MouseEventPtr event)
405   {
406     CanvasPtr src_canvas = _src_canvas.lock();
407
408     if( !src_canvas )
409       return false;
410
411     if( _outset.valid )
412     {
413       CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
414
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);
421     }
422
423     return src_canvas->handleMouseEvent(event);
424   }
425
426   //----------------------------------------------------------------------------
427   Image::CSSOffsets
428   Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
429   {
430     CSSOffsets ret;
431     for(int i = 0; i < 4; ++i)
432     {
433       ret.val[i] = offsets.val[i];
434       if( !types.rel[i] )
435         ret.val[i] /= (i & 1) ? dim.height() : dim.width();
436     }
437     return ret;
438   }
439
440   //----------------------------------------------------------------------------
441   Image::CSSOffsets
442   Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
443   {
444     CSSOffsets ret;
445     for(int i = 0; i < 4; ++i)
446     {
447       ret.val[i] = offsets.val[i];
448       if( types.rel[i] )
449         ret.val[i] *= (i & 1) ? dim.height() : dim.width();
450     }
451     return ret;
452   }
453
454   //----------------------------------------------------------------------------
455   void Image::childChanged(SGPropertyNode* child)
456   {
457     const std::string& name = child->getNameString();
458
459     if( child->getParent() == _node_src_rect )
460     {
461       _attributes_dirty |= SRC_RECT;
462
463       if(      name == "left" )
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() );
471
472       return;
473     }
474     else if( child->getParent() != _node )
475       return;
476
477     if( name == "x" )
478     {
479       _region.setX( child->getFloatValue() );
480       _attributes_dirty |= DEST_SIZE;
481     }
482     else if( name == "y" )
483     {
484       _region.setY( child->getFloatValue() );
485       _attributes_dirty |= DEST_SIZE;
486     }
487     else if( name == "size" )
488     {
489       if( child->getIndex() == 0 )
490         _region.setWidth( child->getFloatValue() );
491       else
492         _region.setHeight( child->getFloatValue() );
493
494       _attributes_dirty |= DEST_SIZE;
495     }
496     else if( name == "file" )
497     {
498       static const std::string CANVAS_PROTOCOL = "canvas://";
499       const std::string& path = child->getStringValue();
500
501       CanvasPtr canvas = _canvas.lock();
502       if( !canvas )
503       {
504         SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
505         return;
506       }
507
508       if( boost::starts_with(path, CANVAS_PROTOCOL) )
509       {
510         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
511         if( !canvas_mgr )
512         {
513           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
514           return;
515         }
516
517         const SGPropertyNode* canvas_node =
518           canvas_mgr->getPropertyRoot()
519                     ->getParent()
520                     ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
521         if( !canvas_node )
522         {
523           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
524           return;
525         }
526
527         // TODO add support for other means of addressing canvases (eg. by
528         // name)
529         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
530         if( !src_canvas )
531         {
532           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
533           return;
534         }
535
536         setSrcCanvas(src_canvas);
537       }
538       else
539       {
540         setImage( canvas->getSystemAdapter()->getImage(path) );
541       }
542     }
543   }
544
545   //----------------------------------------------------------------------------
546   void Image::setupDefaultDimensions()
547   {
548     if( !_src_rect.width() || !_src_rect.height() )
549     {
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);
554     }
555
556     if( !_region.width() || !_region.height() )
557     {
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());
563     }
564   }
565
566   //----------------------------------------------------------------------------
567   SGRect<int> Image::getTextureDimensions() const
568   {
569     CanvasPtr canvas = _src_canvas.lock();
570     SGRect<int> dim(0,0);
571
572     // Use canvas/image dimensions rather than texture dimensions, as they could
573     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
574     if( canvas )
575     {
576       dim.setRight( canvas->getViewWidth() );
577       dim.setBottom( canvas->getViewHeight() );
578     }
579     else if( _texture )
580     {
581       osg::Image* img = _texture->getImage();
582
583       if( img )
584       {
585         dim.setRight( img->s() );
586         dim.setBottom( img->t() );
587       }
588     }
589
590     return dim;
591   }
592
593   //----------------------------------------------------------------------------
594   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
595   {
596     int i = index * 4;
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);
601   }
602
603   //----------------------------------------------------------------------------
604   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
605   {
606     int i = index * 4;
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());
611   }
612
613   //----------------------------------------------------------------------------
614   Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
615   {
616     if( str.empty() )
617       return CSSBorder();
618
619     // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
620     //
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.
624     int c = 0;
625     CSSBorder ret;
626
627     typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
628     const boost::char_separator<char> del(" \t\n");
629
630     tokenizer tokens(str.begin(), str.end(), del);
631     for( tokenizer::const_iterator tok = tokens.begin();
632          tok != tokens.end() && c < 4;
633          ++tok )
634     {
635       if( isalpha(*tok->begin()) )
636         ret.keyword = *tok;
637       else
638       {
639         bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
640         ret.offsets.val[c] =
641           // Negative values are not allowed and values bigger than the size of
642           // the image are interpreted as ‘100%’. TODO check max
643           std::max
644           (
645             0.f,
646             boost::lexical_cast<float>
647             (
648               rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
649                   : *tok
650             )
651             /
652             (rel ? 100 : 1)
653           );
654         ++c;
655       }
656     }
657
658     // When four values are specified, they set the offsets on the top, right,
659     // bottom and left sides in that order.
660
661 #define CSS_COPY_VAL(dest, src)\
662   {\
663     ret.offsets.val[dest] = ret.offsets.val[src];\
664     ret.types.rel[dest] = ret.types.rel[src];\
665   }
666
667     if( c < 4 )
668     {
669       if( c < 3 )
670       {
671         if( c < 2 )
672           // if the right is missing, it is the same as the top.
673           CSS_COPY_VAL(1, 0);
674
675         // if the bottom is missing, it is the same as the top
676         CSS_COPY_VAL(2, 0);
677       }
678
679       // If the left is missing, it is the same as the right
680       CSS_COPY_VAL(3, 1);
681     }
682
683 #undef CSS_COPY_VAL
684
685     ret.valid = true;
686     return ret;
687   }
688
689 } // namespace canvas
690 } // namespace simgear