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