]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
Canvas: More helper functions and cleanup.
[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->setColorBinding(osg::Geometry::BIND_OVERALL);
122     _geom->setColorArray(_colors);
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     if( !_src_canvas.expired() )
354       _src_canvas.lock()->removeParentCanvas(_canvas);
355     if( !_canvas.expired() )
356       _canvas.lock()->removeChildCanvas(_src_canvas);
357
358     _src_canvas = canvas;
359     _attributes_dirty |= SRC_CANVAS;
360     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
361
362     if( !_src_canvas.expired() )
363     {
364       setupDefaultDimensions();
365       _src_canvas.lock()->addParentCanvas(_canvas);
366
367       if( !_canvas.expired() )
368         _canvas.lock()->addChildCanvas(_src_canvas);
369     }
370   }
371
372   //----------------------------------------------------------------------------
373   CanvasWeakPtr Image::getSrcCanvas() const
374   {
375     return _src_canvas;
376   }
377
378   //----------------------------------------------------------------------------
379   void Image::setImage(osg::Image *img)
380   {
381     // remove canvas...
382     setSrcCanvas( CanvasPtr() );
383
384     _texture->setResizeNonPowerOfTwoHint(false);
385     _texture->setImage(img);
386     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
387     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
388     _geom->getOrCreateStateSet()
389          ->setTextureAttributeAndModes(0, _texture);
390
391     if( img )
392       setupDefaultDimensions();
393   }
394
395   //----------------------------------------------------------------------------
396   void Image::setFill(const std::string& fill)
397   {
398     osg::Vec4 color;
399     if( !parseColor(fill, color) )
400       return;
401
402     _colors->front() = color;
403     _colors->dirty();
404   }
405
406   //----------------------------------------------------------------------------
407   void Image::setSlice(const std::string& slice)
408   {
409     _slice = CSSBorder::parse(slice);
410     _attributes_dirty |= SRC_RECT | DEST_SIZE;
411   }
412
413   //----------------------------------------------------------------------------
414   void Image::setSliceWidth(const std::string& width)
415   {
416     _slice_width = CSSBorder::parse(width);
417     _attributes_dirty |= DEST_SIZE;
418   }
419
420   //----------------------------------------------------------------------------
421   void Image::setOutset(const std::string& outset)
422   {
423     _outset = CSSBorder::parse(outset);
424     _attributes_dirty |= DEST_SIZE;
425   }
426
427   //----------------------------------------------------------------------------
428   const SGRect<float>& Image::getRegion() const
429   {
430     return _region;
431   }
432
433   //----------------------------------------------------------------------------
434   bool Image::handleEvent(EventPtr event)
435   {
436     bool handled = Element::handleEvent(event);
437
438     CanvasPtr src_canvas = _src_canvas.lock();
439     if( !src_canvas )
440       return handled;
441
442     MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
443     if( mouse_event )
444     {
445       mouse_event.reset( new MouseEvent(*mouse_event) );
446       event = mouse_event;
447
448       mouse_event->client_pos = mouse_event->local_pos
449                               - toOsg(_region.getMin());
450
451       osg::Vec2f size(_region.width(), _region.height());
452       if( _outset.isValid() )
453       {
454         CSSBorder::Offsets outset =
455           _outset.getAbsOffsets(getTextureDimensions());
456
457         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
458         size.x() += outset.l + outset.r;
459         size.y() += outset.t + outset.b;
460       }
461
462       // Scale event pos according to canvas view size vs. displayed/screen size
463       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
464       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
465       mouse_event->local_pos = mouse_event->client_pos;
466     }
467
468     return handled || src_canvas->handleMouseEvent(mouse_event);
469   }
470
471   //----------------------------------------------------------------------------
472   void Image::childChanged(SGPropertyNode* child)
473   {
474     const std::string& name = child->getNameString();
475
476     if( child->getParent() == _node_src_rect )
477     {
478       _attributes_dirty |= SRC_RECT;
479
480       if(      name == "left" )
481         _src_rect.setLeft( child->getFloatValue() );
482       else if( name == "right" )
483         _src_rect.setRight( child->getFloatValue() );
484       else if( name == "top" )
485         _src_rect.setTop( child->getFloatValue() );
486       else if( name == "bottom" )
487         _src_rect.setBottom( child->getFloatValue() );
488
489       return;
490     }
491     else if( child->getParent() != _node )
492       return;
493
494     if( name == "x" )
495     {
496       _region.setX( child->getFloatValue() );
497       _attributes_dirty |= DEST_SIZE;
498     }
499     else if( name == "y" )
500     {
501       _region.setY( child->getFloatValue() );
502       _attributes_dirty |= DEST_SIZE;
503     }
504     else if( name == "size" )
505     {
506       if( child->getIndex() == 0 )
507         _region.setWidth( child->getFloatValue() );
508       else
509         _region.setHeight( child->getFloatValue() );
510
511       _attributes_dirty |= DEST_SIZE;
512     }
513     else if( name == "file" )
514     {
515       static const std::string CANVAS_PROTOCOL = "canvas://";
516       const std::string& path = child->getStringValue();
517
518       CanvasPtr canvas = _canvas.lock();
519       if( !canvas )
520       {
521         SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
522         return;
523       }
524
525       if( boost::starts_with(path, CANVAS_PROTOCOL) )
526       {
527         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
528         if( !canvas_mgr )
529         {
530           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
531           return;
532         }
533
534         const SGPropertyNode* canvas_node =
535           canvas_mgr->getPropertyRoot()
536                     ->getParent()
537                     ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
538         if( !canvas_node )
539         {
540           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
541           return;
542         }
543
544         // TODO add support for other means of addressing canvases (eg. by
545         // name)
546         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
547         if( !src_canvas )
548         {
549           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
550           return;
551         }
552
553         setSrcCanvas(src_canvas);
554       }
555       else
556       {
557         setImage( canvas->getSystemAdapter()->getImage(path) );
558       }
559     }
560   }
561
562   //----------------------------------------------------------------------------
563   void Image::setupDefaultDimensions()
564   {
565     if( !_src_rect.width() || !_src_rect.height() )
566     {
567       // Show whole image by default
568       _node_src_rect->setBoolValue("normalized", true);
569       _node_src_rect->setFloatValue("right", 1);
570       _node_src_rect->setFloatValue("bottom", 1);
571     }
572
573     if( !_region.width() || !_region.height() )
574     {
575       // Default to image size.
576       // TODO handle showing only part of image?
577       const SGRect<int>& dim = getTextureDimensions();
578       _node->setFloatValue("size[0]", dim.width());
579       _node->setFloatValue("size[1]", dim.height());
580     }
581   }
582
583   //----------------------------------------------------------------------------
584   SGRect<int> Image::getTextureDimensions() const
585   {
586     CanvasPtr canvas = _src_canvas.lock();
587     SGRect<int> dim(0,0);
588
589     // Use canvas/image dimensions rather than texture dimensions, as they could
590     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
591     if( canvas )
592     {
593       dim.setRight( canvas->getViewWidth() );
594       dim.setBottom( canvas->getViewHeight() );
595     }
596     else if( _texture )
597     {
598       osg::Image* img = _texture->getImage();
599
600       if( img )
601       {
602         dim.setRight( img->s() );
603         dim.setBottom( img->t() );
604       }
605     }
606
607     return dim;
608   }
609
610   //----------------------------------------------------------------------------
611   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
612   {
613     int i = index * 4;
614     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
615     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
616     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
617     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
618   }
619
620   //----------------------------------------------------------------------------
621   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
622   {
623     int i = index * 4;
624     (*_texCoords)[i + 0].set(tl.x(), tl.y());
625     (*_texCoords)[i + 1].set(br.x(), tl.y());
626     (*_texCoords)[i + 2].set(br.x(), br.y());
627     (*_texCoords)[i + 3].set(tl.x(), br.y());
628   }
629
630 } // namespace canvas
631 } // namespace simgear