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