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