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