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