]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
Canvas: bug fixing and add some helpers.
[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 #include <boost/lexical_cast.hpp>
35 #include <boost/range.hpp>
36 #include <boost/tokenizer.hpp>
37
38 namespace simgear
39 {
40 namespace canvas
41 {
42   /**
43    * Callback to enable/disable rendering of canvas displayed inside windows or
44    * other canvases.
45    */
46   class CullCallback:
47     public osg::Drawable::CullCallback
48   {
49     public:
50       CullCallback(const CanvasWeakPtr& canvas);
51       void cullNextFrame();
52
53     private:
54       CanvasWeakPtr _canvas;
55       mutable bool  _cull_next_frame;
56
57       virtual bool cull( osg::NodeVisitor* nv,
58                          osg::Drawable* drawable,
59                          osg::RenderInfo* renderInfo ) const;
60   };
61
62   //----------------------------------------------------------------------------
63   CullCallback::CullCallback(const CanvasWeakPtr& canvas):
64     _canvas( canvas ),
65     _cull_next_frame( false )
66   {
67
68   }
69
70   //----------------------------------------------------------------------------
71   void CullCallback::cullNextFrame()
72   {
73     _cull_next_frame = true;
74   }
75
76   //----------------------------------------------------------------------------
77   bool CullCallback::cull( osg::NodeVisitor* nv,
78                            osg::Drawable* drawable,
79                            osg::RenderInfo* renderInfo ) const
80   {
81     if( !_canvas.expired() )
82       _canvas.lock()->enableRendering();
83
84     if( !_cull_next_frame )
85       // TODO check if window/image should be culled
86       return false;
87
88     _cull_next_frame = false;
89     return true;
90   }
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", "numeric", &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     if( !_attributes_dirty )
156       return;
157
158     const SGRect<int>& tex_dim = getTextureDimensions();
159
160     // http://www.w3.org/TR/css3-background/#border-image-slice
161
162     // The ‘fill’ keyword, if present, causes the middle part of the image to be
163     // preserved. (By default it is discarded, i.e., treated as empty.)
164     bool fill = (_slice.keyword == "fill");
165
166     if( _attributes_dirty & DEST_SIZE )
167     {
168       size_t num_vertices = (_slice.valid ? (fill ? 9 : 8) : 1) * 4;
169
170       if( num_vertices != _prim->getNumPrimitives() )
171       {
172         _vertices->resize(num_vertices);
173         _texCoords->resize(num_vertices);
174         _prim->setCount(num_vertices);
175         _prim->dirty();
176
177         _attributes_dirty |= SRC_RECT;
178       }
179
180       // http://www.w3.org/TR/css3-background/#border-image-outset
181       SGRect<float> region = _region;
182       if( _outset.valid )
183       {
184         const CSSOffsets& outset = _outset.getAbsOffsets(tex_dim);
185         region.t() -= outset.t;
186         region.r() += outset.r;
187         region.b() += outset.b;
188         region.l() -= outset.l;
189       }
190
191       if( !_slice.valid )
192       {
193         setQuad(0, region.getMin(), region.getMax());
194       }
195       else
196       {
197         /*
198         Image slice, 9-scale, whatever it is called. The four corner images
199         stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
200         fill the remaining space up to the specified size.
201
202         x[0] x[1]     x[2] x[3]
203           |    |        |    |
204           -------------------- - y[0]
205           | tl |   top  | tr |
206           -------------------- - y[1]
207           |    |        |    |
208           | l  |        |  r |
209           | e  | center |  i |
210           | f  |        |  g |
211           | t  |        |  h |
212           |    |        |  t |
213           -------------------- - y[2]
214           | bl | bottom | br |
215           -------------------- - y[3]
216          */
217
218         const CSSOffsets& slice =
219           (_slice_width.valid ? _slice_width : _slice).getAbsOffsets(tex_dim);
220         float x[4] = {
221           region.l(),
222           region.l() + slice.l,
223           region.r() - slice.r,
224           region.r()
225         };
226         float y[4] = {
227           region.t(),
228           region.t() + slice.t,
229           region.b() - slice.b,
230           region.b()
231         };
232
233         int i = 0;
234         for(int ix = 0; ix < 3; ++ix)
235           for(int iy = 0; iy < 3; ++iy)
236           {
237             if( ix == 1 && iy == 1 && !fill )
238               // The ‘fill’ keyword, if present, causes the middle part of the
239               // image to be filled.
240               continue;
241
242             setQuad( i++,
243                      SGVec2f(x[ix    ], y[iy    ]),
244                      SGVec2f(x[ix + 1], y[iy + 1]) );
245           }
246       }
247
248       _vertices->dirty();
249       _attributes_dirty &= ~DEST_SIZE;
250       _geom->dirtyBound();
251       setBoundingBox(_geom->getBound());
252     }
253
254     if( _attributes_dirty & SRC_RECT )
255     {
256       SGRect<float> src_rect = _src_rect;
257       if( !_node_src_rect->getBoolValue("normalized", true) )
258       {
259         src_rect.t() /= tex_dim.height();
260         src_rect.r() /= tex_dim.width();
261         src_rect.b() /= tex_dim.height();
262         src_rect.l() /= tex_dim.width();
263       }
264
265       // Image coordinate systems y-axis is flipped
266       std::swap(src_rect.t(), src_rect.b());
267
268       if( !_slice.valid )
269       {
270         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
271       }
272       else
273       {
274         const CSSOffsets& slice = _slice.getRelOffsets(tex_dim);
275         float x[4] = {
276           src_rect.l(),
277           src_rect.l() + slice.l,
278           src_rect.r() - slice.r,
279           src_rect.r()
280         };
281         float y[4] = {
282           src_rect.t(),
283           src_rect.t() - slice.t,
284           src_rect.b() + slice.b,
285           src_rect.b()
286         };
287
288         int i = 0;
289         for(int ix = 0; ix < 3; ++ix)
290           for(int iy = 0; iy < 3; ++iy)
291           {
292             if( ix == 1 && iy == 1 && !fill )
293               // The ‘fill’ keyword, if present, causes the middle part of the
294               // image to be filled.
295               continue;
296
297             setQuadUV( i++,
298                        SGVec2f(x[ix    ], y[iy    ]),
299                        SGVec2f(x[ix + 1], y[iy + 1]) );
300           }
301       }
302
303       _texCoords->dirty();
304       _attributes_dirty &= ~SRC_RECT;
305     }
306   }
307
308   //----------------------------------------------------------------------------
309   void Image::valueChanged(SGPropertyNode* child)
310   {
311     // If the image is switched from invisible to visible, and it shows a
312     // canvas, we need to delay showing it by one frame to ensure the canvas is
313     // updated before the image is displayed.
314     //
315     // As canvas::Element handles and filters changes to the "visible" property
316     // we can not check this in Image::childChanged but instead have to override
317     // Element::valueChanged.
318     if(    !isVisible()
319         && child->getParent() == _node
320         && child->getNameString() == "visible"
321         && child->getBoolValue() )
322     {
323       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
324       if( cb )
325         cb->cullNextFrame();
326     }
327
328     Element::valueChanged(child);
329   }
330
331   //----------------------------------------------------------------------------
332   void Image::setSrcCanvas(CanvasPtr canvas)
333   {
334     if( !_src_canvas.expired() )
335       _src_canvas.lock()->removeParentCanvas(_canvas);
336     if( !_canvas.expired() )
337       _canvas.lock()->removeChildCanvas(_src_canvas);
338
339     _src_canvas = canvas;
340     _geom->getOrCreateStateSet()
341          ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
342     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
343
344     if( !_src_canvas.expired() )
345     {
346       setupDefaultDimensions();
347       _src_canvas.lock()->addParentCanvas(_canvas);
348
349       if( !_canvas.expired() )
350         _canvas.lock()->addChildCanvas(_src_canvas);
351     }
352   }
353
354   //----------------------------------------------------------------------------
355   CanvasWeakPtr Image::getSrcCanvas() const
356   {
357     return _src_canvas;
358   }
359
360   //----------------------------------------------------------------------------
361   void Image::setImage(osg::Image *img)
362   {
363     // remove canvas...
364     setSrcCanvas( CanvasPtr() );
365
366     _texture->setImage(img);
367     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
368     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
369     _geom->getOrCreateStateSet()
370          ->setTextureAttributeAndModes(0, _texture);
371
372     if( img )
373       setupDefaultDimensions();
374   }
375
376   //----------------------------------------------------------------------------
377   void Image::setFill(const std::string& fill)
378   {
379     osg::Vec4 color;
380     if( !parseColor(fill, color) )
381       return;
382
383     _colors->front() = color;
384     _colors->dirty();
385   }
386
387   //----------------------------------------------------------------------------
388   void Image::setSlice(const std::string& slice)
389   {
390     _slice = parseSideOffsets(slice);
391     _attributes_dirty |= SRC_RECT | DEST_SIZE;
392   }
393
394   //----------------------------------------------------------------------------
395   void Image::setSliceWidth(const std::string& width)
396   {
397     _slice_width = parseSideOffsets(width);
398     _attributes_dirty |= DEST_SIZE;
399   }
400
401   //----------------------------------------------------------------------------
402   void Image::setOutset(const std::string& outset)
403   {
404     _outset = parseSideOffsets(outset);
405     _attributes_dirty |= DEST_SIZE;
406   }
407
408   //----------------------------------------------------------------------------
409   const SGRect<float>& Image::getRegion() const
410   {
411     return _region;
412   }
413
414   //----------------------------------------------------------------------------
415   bool Image::handleEvent(EventPtr event)
416   {
417     bool handled = Element::handleEvent(event);
418
419     CanvasPtr src_canvas = _src_canvas.lock();
420     if( !src_canvas )
421       return handled;
422
423     MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
424     if( mouse_event )
425     {
426       mouse_event.reset( new MouseEvent(*mouse_event) );
427       event = mouse_event;
428
429       mouse_event->client_pos = mouse_event->local_pos
430                               - toOsg(_region.getMin());
431
432       osg::Vec2f size(_region.width(), _region.height());
433       if( _outset.valid )
434       {
435         CSSOffsets outset = _outset.getAbsOffsets(getTextureDimensions());
436
437         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
438         size.x() += outset.l + outset.r;
439         size.y() += outset.t + outset.b;
440       }
441
442       // Scale event pos according to canvas view size vs. displayed/screen size
443       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
444       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
445       mouse_event->local_pos = mouse_event->client_pos;
446     }
447
448     return handled || src_canvas->handleMouseEvent(mouse_event);
449   }
450
451   //----------------------------------------------------------------------------
452   Image::CSSOffsets
453   Image::CSSBorder::getRelOffsets(const SGRect<int>& dim) const
454   {
455     CSSOffsets ret;
456     for(int i = 0; i < 4; ++i)
457     {
458       ret.val[i] = offsets.val[i];
459       if( !types.rel[i] )
460         ret.val[i] /= (i & 1) ? dim.height() : dim.width();
461     }
462     return ret;
463   }
464
465   //----------------------------------------------------------------------------
466   Image::CSSOffsets
467   Image::CSSBorder::getAbsOffsets(const SGRect<int>& dim) const
468   {
469     CSSOffsets ret;
470     for(int i = 0; i < 4; ++i)
471     {
472       ret.val[i] = offsets.val[i];
473       if( types.rel[i] )
474         ret.val[i] *= (i & 1) ? dim.height() : dim.width();
475     }
476     return ret;
477   }
478
479   //----------------------------------------------------------------------------
480   void Image::childChanged(SGPropertyNode* child)
481   {
482     const std::string& name = child->getNameString();
483
484     if( child->getParent() == _node_src_rect )
485     {
486       _attributes_dirty |= SRC_RECT;
487
488       if(      name == "left" )
489         _src_rect.setLeft( child->getFloatValue() );
490       else if( name == "right" )
491         _src_rect.setRight( child->getFloatValue() );
492       else if( name == "top" )
493         _src_rect.setTop( child->getFloatValue() );
494       else if( name == "bottom" )
495         _src_rect.setBottom( child->getFloatValue() );
496
497       return;
498     }
499     else if( child->getParent() != _node )
500       return;
501
502     if( name == "x" )
503     {
504       _region.setX( child->getFloatValue() );
505       _attributes_dirty |= DEST_SIZE;
506     }
507     else if( name == "y" )
508     {
509       _region.setY( child->getFloatValue() );
510       _attributes_dirty |= DEST_SIZE;
511     }
512     else if( name == "size" )
513     {
514       if( child->getIndex() == 0 )
515         _region.setWidth( child->getFloatValue() );
516       else
517         _region.setHeight( child->getFloatValue() );
518
519       _attributes_dirty |= DEST_SIZE;
520     }
521     else if( name == "file" )
522     {
523       static const std::string CANVAS_PROTOCOL = "canvas://";
524       const std::string& path = child->getStringValue();
525
526       CanvasPtr canvas = _canvas.lock();
527       if( !canvas )
528       {
529         SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
530         return;
531       }
532
533       if( boost::starts_with(path, CANVAS_PROTOCOL) )
534       {
535         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
536         if( !canvas_mgr )
537         {
538           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
539           return;
540         }
541
542         const SGPropertyNode* canvas_node =
543           canvas_mgr->getPropertyRoot()
544                     ->getParent()
545                     ->getNode( path.substr(CANVAS_PROTOCOL.size()) );
546         if( !canvas_node )
547         {
548           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
549           return;
550         }
551
552         // TODO add support for other means of addressing canvases (eg. by
553         // name)
554         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
555         if( !src_canvas )
556         {
557           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
558           return;
559         }
560
561         setSrcCanvas(src_canvas);
562       }
563       else
564       {
565         setImage( canvas->getSystemAdapter()->getImage(path) );
566       }
567     }
568   }
569
570   //----------------------------------------------------------------------------
571   void Image::setupDefaultDimensions()
572   {
573     if( !_src_rect.width() || !_src_rect.height() )
574     {
575       // Show whole image by default
576       _node_src_rect->setBoolValue("normalized", true);
577       _node_src_rect->setFloatValue("right", 1);
578       _node_src_rect->setFloatValue("bottom", 1);
579     }
580
581     if( !_region.width() || !_region.height() )
582     {
583       // Default to image size.
584       // TODO handle showing only part of image?
585       const SGRect<int>& dim = getTextureDimensions();
586       _node->setFloatValue("size[0]", dim.width());
587       _node->setFloatValue("size[1]", dim.height());
588     }
589   }
590
591   //----------------------------------------------------------------------------
592   SGRect<int> Image::getTextureDimensions() const
593   {
594     CanvasPtr canvas = _src_canvas.lock();
595     SGRect<int> dim(0,0);
596
597     // Use canvas/image dimensions rather than texture dimensions, as they could
598     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
599     if( canvas )
600     {
601       dim.setRight( canvas->getViewWidth() );
602       dim.setBottom( canvas->getViewHeight() );
603     }
604     else if( _texture )
605     {
606       osg::Image* img = _texture->getImage();
607
608       if( img )
609       {
610         dim.setRight( img->s() );
611         dim.setBottom( img->t() );
612       }
613     }
614
615     return dim;
616   }
617
618   //----------------------------------------------------------------------------
619   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
620   {
621     int i = index * 4;
622     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
623     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
624     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
625     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
626   }
627
628   //----------------------------------------------------------------------------
629   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
630   {
631     int i = index * 4;
632     (*_texCoords)[i + 0].set(tl.x(), tl.y());
633     (*_texCoords)[i + 1].set(br.x(), tl.y());
634     (*_texCoords)[i + 2].set(br.x(), br.y());
635     (*_texCoords)[i + 3].set(tl.x(), br.y());
636   }
637
638   //----------------------------------------------------------------------------
639   Image::CSSBorder Image::parseSideOffsets(const std::string& str) const
640   {
641     if( str.empty() )
642       return CSSBorder();
643
644     // [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
645     //
646     // Percentages are relative to the size of the image: the width of the
647     // image for the horizontal offsets, the height for vertical offsets.
648     // Numbers represent pixels in the image.
649     int c = 0;
650     CSSBorder ret;
651
652     typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
653     const boost::char_separator<char> del(" \t\n");
654
655     tokenizer tokens(str.begin(), str.end(), del);
656     for( tokenizer::const_iterator tok = tokens.begin();
657          tok != tokens.end() && c < 4;
658          ++tok )
659     {
660       if( isalpha(*tok->begin()) )
661         ret.keyword = *tok;
662       else
663       {
664         bool rel = ret.types.rel[c] = (*tok->rbegin() == '%');
665         ret.offsets.val[c] =
666           // Negative values are not allowed and values bigger than the size of
667           // the image are interpreted as ‘100%’. TODO check max
668           std::max
669           (
670             0.f,
671             boost::lexical_cast<float>
672             (
673               rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
674                   : *tok
675             )
676             /
677             (rel ? 100 : 1)
678           );
679         ++c;
680       }
681     }
682
683     // When four values are specified, they set the offsets on the top, right,
684     // bottom and left sides in that order.
685
686 #define CSS_COPY_VAL(dest, src)\
687   {\
688     ret.offsets.val[dest] = ret.offsets.val[src];\
689     ret.types.rel[dest] = ret.types.rel[src];\
690   }
691
692     if( c < 4 )
693     {
694       if( c < 3 )
695       {
696         if( c < 2 )
697           // if the right is missing, it is the same as the top.
698           CSS_COPY_VAL(1, 0);
699
700         // if the bottom is missing, it is the same as the top
701         CSS_COPY_VAL(2, 0);
702       }
703
704       // If the left is missing, it is the same as the right
705       CSS_COPY_VAL(3, 1);
706     }
707
708 #undef CSS_COPY_VAL
709
710     ret.valid = true;
711     return ret;
712   }
713
714 } // namespace canvas
715 } // namespace simgear