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