]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
CanvasImage: improve fetching from http and add mime detection.
[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       setBoundingBox(_geom->getBound());
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 = boost::dynamic_pointer_cast<MouseEvent>(event);
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
478     return handled | src_canvas->handleMouseEvent(mouse_event);
479   }
480
481   //----------------------------------------------------------------------------
482   void Image::childChanged(SGPropertyNode* child)
483   {
484     const std::string& name = child->getNameString();
485
486     if( child->getParent() == _node_src_rect )
487     {
488       _attributes_dirty |= SRC_RECT;
489
490       if(      name == "left" )
491         _src_rect.setLeft( child->getFloatValue() );
492       else if( name == "right" )
493         _src_rect.setRight( child->getFloatValue() );
494       else if( name == "top" )
495         _src_rect.setTop( child->getFloatValue() );
496       else if( name == "bottom" )
497         _src_rect.setBottom( child->getFloatValue() );
498
499       return;
500     }
501     else if( child->getParent() != _node )
502       return;
503
504     if( name == "x" )
505     {
506       _region.setX( child->getFloatValue() );
507       _attributes_dirty |= DEST_SIZE;
508     }
509     else if( name == "y" )
510     {
511       _region.setY( child->getFloatValue() );
512       _attributes_dirty |= DEST_SIZE;
513     }
514     else if( name == "size" )
515     {
516       if( child->getIndex() == 0 )
517         _region.setWidth( child->getFloatValue() );
518       else
519         _region.setHeight( child->getFloatValue() );
520
521       _attributes_dirty |= DEST_SIZE;
522     }
523     else if( name == "file" )
524     {
525       static const std::string PROTOCOL_SEP = "://";
526
527       std::string url = child->getStringValue(),
528                   protocol, path;
529
530       size_t sep_pos = url.find(PROTOCOL_SEP);
531       if( sep_pos != std::string::npos )
532       {
533         protocol = url.substr(0, sep_pos);
534         path = url.substr(sep_pos + PROTOCOL_SEP.length());
535       }
536       else
537         path = url;
538
539       if( protocol == "canvas" )
540       {
541         CanvasPtr canvas = _canvas.lock();
542         if( !canvas )
543         {
544           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
545           return;
546         }
547
548         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
549         if( !canvas_mgr )
550         {
551           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
552           return;
553         }
554
555         const SGPropertyNode* canvas_node =
556           canvas_mgr->getPropertyRoot()
557                     ->getParent()
558                     ->getNode( path );
559         if( !canvas_node )
560         {
561           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
562           return;
563         }
564
565         // TODO add support for other means of addressing canvases (eg. by
566         // name)
567         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
568         if( !src_canvas )
569         {
570           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
571           return;
572         }
573
574         setSrcCanvas(src_canvas);
575       }
576       else if( protocol == "http" || protocol == "https" )
577       // TODO check https
578       {
579         Canvas::getSystemAdapter()
580           ->getHTTPClient()
581           ->load(url)
582           ->done(this, &Image::handleImageLoadDone);
583       }
584       else
585       {
586         setImage( Canvas::getSystemAdapter()->getImage(path) );
587       }
588     }
589   }
590
591   //----------------------------------------------------------------------------
592   void Image::setupDefaultDimensions()
593   {
594     if( !_src_rect.width() || !_src_rect.height() )
595     {
596       // Show whole image by default
597       _node_src_rect->setBoolValue("normalized", true);
598       _node_src_rect->setFloatValue("right", 1);
599       _node_src_rect->setFloatValue("bottom", 1);
600     }
601
602     if( !_region.width() || !_region.height() )
603     {
604       // Default to image size.
605       // TODO handle showing only part of image?
606       const SGRect<int>& dim = getTextureDimensions();
607       _node->setFloatValue("size[0]", dim.width());
608       _node->setFloatValue("size[1]", dim.height());
609     }
610   }
611
612   //----------------------------------------------------------------------------
613   SGRect<int> Image::getTextureDimensions() const
614   {
615     CanvasPtr canvas = _src_canvas.lock();
616     SGRect<int> dim(0,0);
617
618     // Use canvas/image dimensions rather than texture dimensions, as they could
619     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
620     if( canvas )
621     {
622       dim.setRight( canvas->getViewWidth() );
623       dim.setBottom( canvas->getViewHeight() );
624     }
625     else if( _texture )
626     {
627       osg::Image* img = _texture->getImage();
628
629       if( img )
630       {
631         dim.setRight( img->s() );
632         dim.setBottom( img->t() );
633       }
634     }
635
636     return dim;
637   }
638
639   //----------------------------------------------------------------------------
640   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
641   {
642     int i = index * 4;
643     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
644     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
645     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
646     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
647   }
648
649   //----------------------------------------------------------------------------
650   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
651   {
652     int i = index * 4;
653     (*_texCoords)[i + 0].set(tl.x(), tl.y());
654     (*_texCoords)[i + 1].set(br.x(), tl.y());
655     (*_texCoords)[i + 2].set(br.x(), br.y());
656     (*_texCoords)[i + 3].set(tl.x(), br.y());
657   }
658
659   //----------------------------------------------------------------------------
660   void Image::handleImageLoadDone(HTTP::Request* req)
661   {
662     if( req->responseCode() != 200 )
663     {
664       SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
665                                                     << req->responseReason());
666       return;
667     }
668
669     const std::string ext = SGPath(req->path()).extension(),
670                       mime = req->responseMime();
671
672     SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
673                            " (ext=" << ext << ", MIME=" << mime << ")");
674
675     const std::string& img_data =
676       static_cast<HTTP::MemoryRequest*>(req)->responseBody();
677     osgDB::Registry* reg = osgDB::Registry::instance();
678
679     // First try to detect image type by extension
680     osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
681     if( rw && loadImage(*rw, img_data, *req, "extension") )
682       return;
683
684     // Now try with MIME type
685     rw = reg->getReaderWriterForMimeType(req->responseMime());
686     if( rw && loadImage(*rw, img_data, *req, "MIME type") )
687       return;
688
689     SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
690   }
691
692   //----------------------------------------------------------------------------
693   bool Image::loadImage( osgDB::ReaderWriter& reader,
694                          const std::string& data,
695                          HTTP::Request& request,
696                          const std::string& type )
697   {
698     SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
699
700     std::istringstream data_strm(data);
701     osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
702     if( result.success() )
703     {
704       setImage( result.takeImage() );
705       return true;
706     }
707
708     SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
709                                                     << result.message());
710
711     return false;
712   }
713
714 } // namespace canvas
715 } // namespace simgear