]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
canvas: exclude data-* properties from triggering an update.
[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/events/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 #include <simgear/io/HTTPClient.hxx>
29
30 #include <osg/Array>
31 #include <osg/Geometry>
32 #include <osg/PrimitiveSet>
33 #include <osgDB/Registry>
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     CanvasPtr canvas = _canvas.lock();
79     if( canvas )
80       canvas->enableRendering();
81
82     if( !_cull_next_frame )
83       // TODO check if window/image should be culled
84       return false;
85
86     _cull_next_frame = false;
87     return true;
88   }
89
90   //----------------------------------------------------------------------------
91   const std::string Image::TYPE_NAME = "image";
92
93   //----------------------------------------------------------------------------
94   void Image::staticInit()
95   {
96     if( isInit<Image>() )
97       return;
98
99     addStyle("fill", "color", &Image::setFill);
100     addStyle("slice", "", &Image::setSlice);
101     addStyle("slice-width", "", &Image::setSliceWidth);
102     addStyle("outset", "", &Image::setOutset);
103   }
104
105   //----------------------------------------------------------------------------
106   Image::Image( const CanvasWeakPtr& canvas,
107                 const SGPropertyNode_ptr& node,
108                 const Style& parent_style,
109                 Element* parent ):
110     Element(canvas, node, parent_style, parent),
111     _texture(new osg::Texture2D),
112     _node_src_rect( node->getNode("source", 0, true) ),
113     _src_rect(0,0),
114     _region(0,0)
115   {
116     staticInit();
117
118     _geom = new osg::Geometry;
119     _geom->setUseDisplayList(false);
120
121     osg::StateSet *stateSet = _geom->getOrCreateStateSet();
122     stateSet->setTextureAttributeAndModes(0, _texture.get());
123     stateSet->setDataVariance(osg::Object::STATIC);
124
125     // allocate arrays for the image
126     _vertices = new osg::Vec3Array(4);
127     _vertices->setDataVariance(osg::Object::DYNAMIC);
128     _geom->setVertexArray(_vertices);
129
130     _texCoords = new osg::Vec2Array(4);
131     _texCoords->setDataVariance(osg::Object::DYNAMIC);
132     _geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
133
134     _colors = new osg::Vec4Array(1);
135     _colors->setDataVariance(osg::Object::DYNAMIC);
136     _geom->setColorArray(_colors, osg::Array::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     }
276
277     if( _attributes_dirty & SRC_RECT )
278     {
279       SGRect<float> src_rect = _src_rect;
280       if( !_node_src_rect->getBoolValue("normalized", true) )
281       {
282         src_rect.t() /= tex_dim.height();
283         src_rect.r() /= tex_dim.width();
284         src_rect.b() /= tex_dim.height();
285         src_rect.l() /= tex_dim.width();
286       }
287
288       // Image coordinate systems y-axis is flipped
289       std::swap(src_rect.t(), src_rect.b());
290
291       if( !_slice.isValid() )
292       {
293         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
294       }
295       else
296       {
297         const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
298         float x[4] = {
299           src_rect.l(),
300           src_rect.l() + slice.l,
301           src_rect.r() - slice.r,
302           src_rect.r()
303         };
304         float y[4] = {
305           src_rect.t(),
306           src_rect.t() - slice.t,
307           src_rect.b() + slice.b,
308           src_rect.b()
309         };
310
311         int i = 0;
312         for(int ix = 0; ix < 3; ++ix)
313           for(int iy = 0; iy < 3; ++iy)
314           {
315             if( ix == 1 && iy == 1 && !fill )
316               // The ‘fill’ keyword, if present, causes the middle part of the
317               // image to be filled.
318               continue;
319
320             setQuadUV( i++,
321                        SGVec2f(x[ix    ], y[iy    ]),
322                        SGVec2f(x[ix + 1], y[iy + 1]) );
323           }
324       }
325
326       _texCoords->dirty();
327       _attributes_dirty &= ~SRC_RECT;
328     }
329   }
330
331   //----------------------------------------------------------------------------
332   void Image::valueChanged(SGPropertyNode* child)
333   {
334     // If the image is switched from invisible to visible, and it shows a
335     // canvas, we need to delay showing it by one frame to ensure the canvas is
336     // updated before the image is displayed.
337     //
338     // As canvas::Element handles and filters changes to the "visible" property
339     // we can not check this in Image::childChanged but instead have to override
340     // Element::valueChanged.
341     if(    !isVisible()
342         && child->getParent() == _node
343         && child->getNameString() == "visible"
344         && child->getBoolValue() )
345     {
346       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
347       if( cb )
348         cb->cullNextFrame();
349     }
350
351     Element::valueChanged(child);
352   }
353
354   //----------------------------------------------------------------------------
355   void Image::setSrcCanvas(CanvasPtr canvas)
356   {
357     CanvasPtr src_canvas = _src_canvas.lock(),
358               self_canvas = _canvas.lock();
359
360     if( src_canvas )
361       src_canvas->removeParentCanvas(self_canvas);
362     if( self_canvas )
363       self_canvas->removeChildCanvas(src_canvas);
364
365     _src_canvas = src_canvas = canvas;
366     _attributes_dirty |= SRC_CANVAS;
367     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
368
369     if( src_canvas )
370     {
371       setupDefaultDimensions();
372
373       if( self_canvas )
374       {
375         self_canvas->addChildCanvas(src_canvas);
376         src_canvas->addParentCanvas(self_canvas);
377       }
378     }
379   }
380
381   //----------------------------------------------------------------------------
382   CanvasWeakPtr Image::getSrcCanvas() const
383   {
384     return _src_canvas;
385   }
386
387   //----------------------------------------------------------------------------
388   void Image::setImage(osg::Image *img)
389   {
390     // remove canvas...
391     setSrcCanvas( CanvasPtr() );
392
393     _texture->setResizeNonPowerOfTwoHint(false);
394     _texture->setImage(img);
395     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
396     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
397     _geom->getOrCreateStateSet()
398          ->setTextureAttributeAndModes(0, _texture);
399
400     if( img )
401       setupDefaultDimensions();
402   }
403
404   //----------------------------------------------------------------------------
405   void Image::setFill(const std::string& fill)
406   {
407     osg::Vec4 color(1,1,1,1);
408     if(    !fill.empty() // If no color is given default to white
409         && !parseColor(fill, color) )
410       return;
411
412     _colors->front() = color;
413     _colors->dirty();
414   }
415
416   //----------------------------------------------------------------------------
417   void Image::setSlice(const std::string& slice)
418   {
419     _slice = CSSBorder::parse(slice);
420     _attributes_dirty |= SRC_RECT | DEST_SIZE;
421   }
422
423   //----------------------------------------------------------------------------
424   void Image::setSliceWidth(const std::string& width)
425   {
426     _slice_width = CSSBorder::parse(width);
427     _attributes_dirty |= DEST_SIZE;
428   }
429
430   //----------------------------------------------------------------------------
431   void Image::setOutset(const std::string& outset)
432   {
433     _outset = CSSBorder::parse(outset);
434     _attributes_dirty |= DEST_SIZE;
435   }
436
437   //----------------------------------------------------------------------------
438   const SGRect<float>& Image::getRegion() const
439   {
440     return _region;
441   }
442
443   //----------------------------------------------------------------------------
444   bool Image::handleEvent(EventPtr event)
445   {
446     bool handled = Element::handleEvent(event);
447
448     CanvasPtr src_canvas = _src_canvas.lock();
449     if( !src_canvas )
450       return handled;
451
452     MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
453     if( mouse_event )
454     {
455       mouse_event.reset( new MouseEvent(*mouse_event) );
456       event = mouse_event;
457
458       mouse_event->client_pos = mouse_event->local_pos
459                               - toOsg(_region.getMin());
460
461       osg::Vec2f size(_region.width(), _region.height());
462       if( _outset.isValid() )
463       {
464         CSSBorder::Offsets outset =
465           _outset.getAbsOffsets(getTextureDimensions());
466
467         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
468         size.x() += outset.l + outset.r;
469         size.y() += outset.t + outset.b;
470       }
471
472       // Scale event pos according to canvas view size vs. displayed/screen size
473       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
474       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
475       mouse_event->local_pos = mouse_event->client_pos;
476
477       handled |= src_canvas->handleMouseEvent(mouse_event);
478     }
479
480     return handled;
481   }
482
483   //----------------------------------------------------------------------------
484   void Image::childChanged(SGPropertyNode* child)
485   {
486     const std::string& name = child->getNameString();
487
488     if( child->getParent() == _node_src_rect )
489     {
490       _attributes_dirty |= SRC_RECT;
491
492       if(      name == "left" )
493         _src_rect.setLeft( child->getFloatValue() );
494       else if( name == "right" )
495         _src_rect.setRight( child->getFloatValue() );
496       else if( name == "top" )
497         _src_rect.setTop( child->getFloatValue() );
498       else if( name == "bottom" )
499         _src_rect.setBottom( child->getFloatValue() );
500
501       return;
502     }
503     else if( child->getParent() != _node )
504       return;
505
506     if( name == "x" )
507     {
508       _region.setX( child->getFloatValue() );
509       _attributes_dirty |= DEST_SIZE;
510     }
511     else if( name == "y" )
512     {
513       _region.setY( child->getFloatValue() );
514       _attributes_dirty |= DEST_SIZE;
515     }
516     else if( name == "size" )
517     {
518       if( child->getIndex() == 0 )
519         _region.setWidth( child->getFloatValue() );
520       else
521         _region.setHeight( child->getFloatValue() );
522
523       _attributes_dirty |= DEST_SIZE;
524     }
525     else if( name == "src" || name == "file" )
526     {
527       if( name == "file" )
528         SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
529
530       static const std::string PROTOCOL_SEP = "://";
531
532       std::string url = child->getStringValue(),
533                   protocol, path;
534
535       size_t sep_pos = url.find(PROTOCOL_SEP);
536       if( sep_pos != std::string::npos )
537       {
538         protocol = url.substr(0, sep_pos);
539         path = url.substr(sep_pos + PROTOCOL_SEP.length());
540       }
541       else
542         path = url;
543
544       if( protocol == "canvas" )
545       {
546         CanvasPtr canvas = _canvas.lock();
547         if( !canvas )
548         {
549           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
550           return;
551         }
552
553         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
554         if( !canvas_mgr )
555         {
556           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
557           return;
558         }
559
560         const SGPropertyNode* canvas_node =
561           canvas_mgr->getPropertyRoot()
562                     ->getParent()
563                     ->getNode( path );
564         if( !canvas_node )
565         {
566           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
567           return;
568         }
569
570         // TODO add support for other means of addressing canvases (eg. by
571         // name)
572         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
573         if( !src_canvas )
574         {
575           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
576           return;
577         }
578
579         setSrcCanvas(src_canvas);
580       }
581       else if( protocol == "http" || protocol == "https" )
582       // TODO check https
583       {
584         Canvas::getSystemAdapter()
585           ->getHTTPClient()
586           ->load(url)
587           ->done(this, &Image::handleImageLoadDone);
588       }
589       else
590       {
591         setImage( Canvas::getSystemAdapter()->getImage(path) );
592       }
593     }
594   }
595
596   //----------------------------------------------------------------------------
597   void Image::setupDefaultDimensions()
598   {
599     if( !_src_rect.width() || !_src_rect.height() )
600     {
601       // Show whole image by default
602       _node_src_rect->setBoolValue("normalized", true);
603       _node_src_rect->setFloatValue("right", 1);
604       _node_src_rect->setFloatValue("bottom", 1);
605     }
606
607     if( !_region.width() || !_region.height() )
608     {
609       // Default to image size.
610       // TODO handle showing only part of image?
611       const SGRect<int>& dim = getTextureDimensions();
612       _node->setFloatValue("size[0]", dim.width());
613       _node->setFloatValue("size[1]", dim.height());
614     }
615   }
616
617   //----------------------------------------------------------------------------
618   SGRect<int> Image::getTextureDimensions() const
619   {
620     CanvasPtr canvas = _src_canvas.lock();
621     SGRect<int> dim(0,0);
622
623     // Use canvas/image dimensions rather than texture dimensions, as they could
624     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
625     if( canvas )
626     {
627       dim.setRight( canvas->getViewWidth() );
628       dim.setBottom( canvas->getViewHeight() );
629     }
630     else if( _texture )
631     {
632       osg::Image* img = _texture->getImage();
633
634       if( img )
635       {
636         dim.setRight( img->s() );
637         dim.setBottom( img->t() );
638       }
639     }
640
641     return dim;
642   }
643
644   //----------------------------------------------------------------------------
645   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
646   {
647     int i = index * 4;
648     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
649     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
650     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
651     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
652   }
653
654   //----------------------------------------------------------------------------
655   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
656   {
657     int i = index * 4;
658     (*_texCoords)[i + 0].set(tl.x(), tl.y());
659     (*_texCoords)[i + 1].set(br.x(), tl.y());
660     (*_texCoords)[i + 2].set(br.x(), br.y());
661     (*_texCoords)[i + 3].set(tl.x(), br.y());
662   }
663
664   //----------------------------------------------------------------------------
665   void Image::handleImageLoadDone(HTTP::Request* req)
666   {
667     if( req->responseCode() != 200 )
668     {
669       SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
670                                                     << req->responseReason());
671       return;
672     }
673
674     const std::string ext = SGPath(req->path()).extension(),
675                       mime = req->responseMime();
676
677     SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
678                            " (ext=" << ext << ", MIME=" << mime << ")");
679
680     const std::string& img_data =
681       static_cast<HTTP::MemoryRequest*>(req)->responseBody();
682     osgDB::Registry* reg = osgDB::Registry::instance();
683
684     // First try to detect image type by extension
685     osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
686     if( rw && loadImage(*rw, img_data, *req, "extension") )
687       return;
688
689     // Now try with MIME type
690     rw = reg->getReaderWriterForMimeType(mime);
691     if( rw && loadImage(*rw, img_data, *req, "MIME type") )
692       return;
693
694     SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
695   }
696
697   //----------------------------------------------------------------------------
698   bool Image::loadImage( osgDB::ReaderWriter& reader,
699                          const std::string& data,
700                          HTTP::Request& request,
701                          const std::string& type )
702   {
703     SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
704
705     std::istringstream data_strm(data);
706     osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
707     if( result.success() )
708     {
709       setImage( result.takeImage() );
710       return true;
711     }
712
713     SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
714                                                     << result.message());
715
716     return false;
717   }
718
719 } // namespace canvas
720 } // namespace simgear