]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
canvas::Layout: support for contents margins.
[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
29 #include <osg/Array>
30 #include <osg/Geometry>
31 #include <osg/PrimitiveSet>
32 #include <osgDB/Registry>
33
34 namespace simgear
35 {
36 namespace canvas
37 {
38   /**
39    * Callback to enable/disable rendering of canvas displayed inside windows or
40    * other canvases.
41    */
42   class CullCallback:
43     public osg::Drawable::CullCallback
44   {
45     public:
46       CullCallback(const CanvasWeakPtr& canvas);
47       void cullNextFrame();
48
49     private:
50       CanvasWeakPtr _canvas;
51       mutable bool  _cull_next_frame;
52
53       virtual bool cull( osg::NodeVisitor* nv,
54                          osg::Drawable* drawable,
55                          osg::RenderInfo* renderInfo ) const;
56   };
57
58   //----------------------------------------------------------------------------
59   CullCallback::CullCallback(const CanvasWeakPtr& canvas):
60     _canvas( canvas ),
61     _cull_next_frame( false )
62   {
63
64   }
65
66   //----------------------------------------------------------------------------
67   void CullCallback::cullNextFrame()
68   {
69     _cull_next_frame = true;
70   }
71
72   //----------------------------------------------------------------------------
73   bool CullCallback::cull( osg::NodeVisitor* nv,
74                            osg::Drawable* drawable,
75                            osg::RenderInfo* renderInfo ) const
76   {
77     CanvasPtr canvas = _canvas.lock();
78     if( canvas )
79       canvas->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("outset", "", &Image::setOutset);
100     addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
101     addStyle("slice", "", &Image::setSlice);
102     addStyle("slice-width", "", &Image::setSliceWidth);
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     if( _http_request )
153       _http_request->abort("image destroyed");
154   }
155
156   //----------------------------------------------------------------------------
157   void Image::update(double dt)
158   {
159     Element::update(dt);
160
161     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
162     (
163       _geom->getOrCreateStateSet()
164            ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
165     );
166     simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
167
168     if(    (_attributes_dirty & SRC_CANVAS)
169            // check if texture has changed (eg. due to resizing)
170         || (canvas && texture != canvas->getTexture()) )
171     {
172       _geom->getOrCreateStateSet()
173            ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
174
175       if( !canvas || canvas->isInit() )
176         _attributes_dirty &= ~SRC_CANVAS;
177     }
178
179     if( !_attributes_dirty )
180       return;
181
182     const SGRect<int>& tex_dim = getTextureDimensions();
183
184     // http://www.w3.org/TR/css3-background/#border-image-slice
185
186     // The ‘fill’ keyword, if present, causes the middle part of the image to be
187     // preserved. (By default it is discarded, i.e., treated as empty.)
188     bool fill = (_slice.getKeyword() == "fill");
189
190     if( _attributes_dirty & DEST_SIZE )
191     {
192       size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
193
194       if( num_vertices != _prim->getNumPrimitives() )
195       {
196         _vertices->resize(num_vertices);
197         _texCoords->resize(num_vertices);
198         _prim->setCount(num_vertices);
199         _prim->dirty();
200
201         _attributes_dirty |= SRC_RECT;
202       }
203
204       // http://www.w3.org/TR/css3-background/#border-image-outset
205       SGRect<float> region = _region;
206       if( _outset.isValid() )
207       {
208         const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
209         region.t() -= outset.t;
210         region.r() += outset.r;
211         region.b() += outset.b;
212         region.l() -= outset.l;
213       }
214
215       if( !_slice.isValid() )
216       {
217         setQuad(0, region.getMin(), region.getMax());
218
219         if( !_preserve_aspect_ratio.scaleToFill() )
220           // We need to update texture coordinates to keep the aspect ratio
221           _attributes_dirty |= SRC_RECT;
222       }
223       else
224       {
225         /*
226         Image slice, 9-scale, whatever it is called. The four corner images
227         stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
228         fill the remaining space up to the specified size.
229
230         x[0] x[1]     x[2] x[3]
231           |    |        |    |
232           -------------------- - y[0]
233           | tl |   top  | tr |
234           -------------------- - y[1]
235           |    |        |    |
236           | l  |        |  r |
237           | e  | center |  i |
238           | f  |        |  g |
239           | t  |        |  h |
240           |    |        |  t |
241           -------------------- - y[2]
242           | bl | bottom | br |
243           -------------------- - y[3]
244          */
245
246         const CSSBorder::Offsets& slice =
247           (_slice_width.isValid() ? _slice_width : _slice)
248           .getAbsOffsets(tex_dim);
249         float x[4] = {
250           region.l(),
251           region.l() + slice.l,
252           region.r() - slice.r,
253           region.r()
254         };
255         float y[4] = {
256           region.t(),
257           region.t() + slice.t,
258           region.b() - slice.b,
259           region.b()
260         };
261
262         int i = 0;
263         for(int ix = 0; ix < 3; ++ix)
264           for(int iy = 0; iy < 3; ++iy)
265           {
266             if( ix == 1 && iy == 1 && !fill )
267               // The ‘fill’ keyword, if present, causes the middle part of the
268               // image to be filled.
269               continue;
270
271             setQuad( i++,
272                      SGVec2f(x[ix    ], y[iy    ]),
273                      SGVec2f(x[ix + 1], y[iy + 1]) );
274           }
275       }
276
277       _vertices->dirty();
278       _attributes_dirty &= ~DEST_SIZE;
279       _geom->dirtyBound();
280     }
281
282     if( _attributes_dirty & SRC_RECT )
283     {
284       SGRect<float> src_rect = _src_rect;
285       if( !_node_src_rect->getBoolValue("normalized", true) )
286       {
287         src_rect.t() /= tex_dim.height();
288         src_rect.r() /= tex_dim.width();
289         src_rect.b() /= tex_dim.height();
290         src_rect.l() /= tex_dim.width();
291       }
292
293       // Image coordinate systems y-axis is flipped
294       std::swap(src_rect.t(), src_rect.b());
295
296       if( !_slice.isValid() )
297       {
298         // Image scaling preserving aspect ratio. Change texture coordinates to
299         // scale image accordingly.
300         //
301         // TODO allow to specify what happens to not filled space (eg. color,
302         //      or texture repeat/mirror)
303         //
304         // http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
305         if( !_preserve_aspect_ratio.scaleToFill() )
306         {
307           osg::BoundingBox const& bb = getBoundingBox();
308           float dst_width = bb._max.x() - bb._min.x(),
309                 dst_height = bb._max.y() - bb._min.y();
310           float scale_x = dst_width / tex_dim.width(),
311                 scale_y = dst_height / tex_dim.height();
312
313           float scale = _preserve_aspect_ratio.scaleToFit()
314                       ? std::min(scale_x, scale_y)
315                       : std::max(scale_x, scale_y);
316
317           if( scale_x != scale )
318           {
319             float d = scale_x / scale - 1;
320             if(  _preserve_aspect_ratio.alignX()
321               == SVGpreserveAspectRatio::ALIGN_MIN )
322             {
323               src_rect.r() += d;
324             }
325             else if(  _preserve_aspect_ratio.alignX()
326                    == SVGpreserveAspectRatio::ALIGN_MAX )
327             {
328               src_rect.l() -= d;
329             }
330             else
331             {
332               src_rect.l() -= d / 2;
333               src_rect.r() += d / 2;
334             }
335           }
336
337           if( scale_y != scale )
338           {
339             float d = scale_y / scale - 1;
340             if(  _preserve_aspect_ratio.alignY()
341               == SVGpreserveAspectRatio::ALIGN_MIN )
342             {
343               src_rect.b() -= d;
344             }
345             else if(  _preserve_aspect_ratio.alignY()
346                    == SVGpreserveAspectRatio::ALIGN_MAX )
347             {
348               src_rect.t() += d;
349             }
350             else
351             {
352               src_rect.t() += d / 2;
353               src_rect.b() -= d / 2;
354             }
355           }
356         }
357
358         setQuadUV(0, src_rect.getMin(), src_rect.getMax());
359       }
360       else
361       {
362         const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
363         float x[4] = {
364           src_rect.l(),
365           src_rect.l() + slice.l,
366           src_rect.r() - slice.r,
367           src_rect.r()
368         };
369         float y[4] = {
370           src_rect.t(),
371           src_rect.t() - slice.t,
372           src_rect.b() + slice.b,
373           src_rect.b()
374         };
375
376         int i = 0;
377         for(int ix = 0; ix < 3; ++ix)
378           for(int iy = 0; iy < 3; ++iy)
379           {
380             if( ix == 1 && iy == 1 && !fill )
381               // The ‘fill’ keyword, if present, causes the middle part of the
382               // image to be filled.
383               continue;
384
385             setQuadUV( i++,
386                        SGVec2f(x[ix    ], y[iy    ]),
387                        SGVec2f(x[ix + 1], y[iy + 1]) );
388           }
389       }
390
391       _texCoords->dirty();
392       _attributes_dirty &= ~SRC_RECT;
393     }
394   }
395
396   //----------------------------------------------------------------------------
397   void Image::valueChanged(SGPropertyNode* child)
398   {
399     // If the image is switched from invisible to visible, and it shows a
400     // canvas, we need to delay showing it by one frame to ensure the canvas is
401     // updated before the image is displayed.
402     //
403     // As canvas::Element handles and filters changes to the "visible" property
404     // we can not check this in Image::childChanged but instead have to override
405     // Element::valueChanged.
406     if(    !isVisible()
407         && child->getParent() == _node
408         && child->getNameString() == "visible"
409         && child->getBoolValue() )
410     {
411       CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
412       if( cb )
413         cb->cullNextFrame();
414     }
415
416     Element::valueChanged(child);
417   }
418
419   //----------------------------------------------------------------------------
420   void Image::setSrcCanvas(CanvasPtr canvas)
421   {
422     CanvasPtr src_canvas = _src_canvas.lock(),
423               self_canvas = _canvas.lock();
424
425     if( src_canvas )
426       src_canvas->removeParentCanvas(self_canvas);
427     if( self_canvas )
428       self_canvas->removeChildCanvas(src_canvas);
429
430     _src_canvas = src_canvas = canvas;
431     _attributes_dirty |= SRC_CANVAS;
432     _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
433
434     if( src_canvas )
435     {
436       setupDefaultDimensions();
437
438       if( self_canvas )
439       {
440         self_canvas->addChildCanvas(src_canvas);
441         src_canvas->addParentCanvas(self_canvas);
442       }
443     }
444   }
445
446   //----------------------------------------------------------------------------
447   CanvasWeakPtr Image::getSrcCanvas() const
448   {
449     return _src_canvas;
450   }
451
452   //----------------------------------------------------------------------------
453   void Image::setImage(osg::Image *img)
454   {
455     // remove canvas...
456     setSrcCanvas( CanvasPtr() );
457
458     _texture->setResizeNonPowerOfTwoHint(false);
459     _texture->setImage(img);
460     _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
461     _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
462     _geom->getOrCreateStateSet()
463          ->setTextureAttributeAndModes(0, _texture);
464
465     if( img )
466       setupDefaultDimensions();
467   }
468
469   //----------------------------------------------------------------------------
470   void Image::setFill(const std::string& fill)
471   {
472     osg::Vec4 color(1,1,1,1);
473     if(    !fill.empty() // If no color is given default to white
474         && !parseColor(fill, color) )
475       return;
476
477     _colors->front() = color;
478     _colors->dirty();
479   }
480
481   //----------------------------------------------------------------------------
482   void Image::setOutset(const std::string& outset)
483   {
484     _outset = CSSBorder::parse(outset);
485     _attributes_dirty |= DEST_SIZE;
486   }
487
488   //----------------------------------------------------------------------------
489   void Image::setPreserveAspectRatio(const std::string& scale)
490   {
491     _preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
492     _attributes_dirty |= SRC_RECT;
493   }
494
495   //----------------------------------------------------------------------------
496   void Image::setSlice(const std::string& slice)
497   {
498     _slice = CSSBorder::parse(slice);
499     _attributes_dirty |= SRC_RECT | DEST_SIZE;
500   }
501
502   //----------------------------------------------------------------------------
503   void Image::setSliceWidth(const std::string& width)
504   {
505     _slice_width = CSSBorder::parse(width);
506     _attributes_dirty |= DEST_SIZE;
507   }
508
509   //----------------------------------------------------------------------------
510   const SGRect<float>& Image::getRegion() const
511   {
512     return _region;
513   }
514
515   //----------------------------------------------------------------------------
516   bool Image::handleEvent(const EventPtr& event)
517   {
518     bool handled = Element::handleEvent(event);
519
520     CanvasPtr src_canvas = _src_canvas.lock();
521     if( !src_canvas )
522       return handled;
523
524     MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
525     if( mouse_event )
526     {
527       mouse_event.reset( new MouseEvent(*mouse_event) );
528
529       mouse_event->client_pos = mouse_event->local_pos
530                               - toOsg(_region.getMin());
531
532       osg::Vec2f size(_region.width(), _region.height());
533       if( _outset.isValid() )
534       {
535         CSSBorder::Offsets outset =
536           _outset.getAbsOffsets(getTextureDimensions());
537
538         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
539         size.x() += outset.l + outset.r;
540         size.y() += outset.t + outset.b;
541       }
542
543       // Scale event pos according to canvas view size vs. displayed/screen size
544       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
545       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
546       mouse_event->local_pos = mouse_event->client_pos;
547
548       handled |= src_canvas->handleMouseEvent(mouse_event);
549     }
550
551     return handled;
552   }
553
554   //----------------------------------------------------------------------------
555   void Image::childChanged(SGPropertyNode* child)
556   {
557     const std::string& name = child->getNameString();
558
559     if( child->getParent() == _node_src_rect )
560     {
561       _attributes_dirty |= SRC_RECT;
562
563       if(      name == "left" )
564         _src_rect.setLeft( child->getFloatValue() );
565       else if( name == "right" )
566         _src_rect.setRight( child->getFloatValue() );
567       else if( name == "top" )
568         _src_rect.setTop( child->getFloatValue() );
569       else if( name == "bottom" )
570         _src_rect.setBottom( child->getFloatValue() );
571
572       return;
573     }
574     else if( child->getParent() != _node )
575       return;
576
577     if( name == "x" )
578     {
579       _region.setX( child->getFloatValue() );
580       _attributes_dirty |= DEST_SIZE;
581     }
582     else if( name == "y" )
583     {
584       _region.setY( child->getFloatValue() );
585       _attributes_dirty |= DEST_SIZE;
586     }
587     else if( name == "size" )
588     {
589       if( child->getIndex() == 0 )
590         _region.setWidth( child->getFloatValue() );
591       else
592         _region.setHeight( child->getFloatValue() );
593
594       _attributes_dirty |= DEST_SIZE;
595     }
596     else if( name == "src" || name == "file" )
597     {
598       if( name == "file" )
599         SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
600
601       // Abort pending request
602       if( _http_request )
603       {
604         _http_request->abort("setting new image");
605         _http_request.reset();
606       }
607
608       static const std::string PROTOCOL_SEP = "://";
609
610       std::string url = child->getStringValue(),
611                   protocol, path;
612
613       size_t sep_pos = url.find(PROTOCOL_SEP);
614       if( sep_pos != std::string::npos )
615       {
616         protocol = url.substr(0, sep_pos);
617         path = url.substr(sep_pos + PROTOCOL_SEP.length());
618       }
619       else
620         path = url;
621
622       if( protocol == "canvas" )
623       {
624         CanvasPtr canvas = _canvas.lock();
625         if( !canvas )
626         {
627           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
628           return;
629         }
630
631         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
632         if( !canvas_mgr )
633         {
634           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
635           return;
636         }
637
638         const SGPropertyNode* canvas_node =
639           canvas_mgr->getPropertyRoot()
640                     ->getParent()
641                     ->getNode( path );
642         if( !canvas_node )
643         {
644           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
645           return;
646         }
647
648         // TODO add support for other means of addressing canvases (eg. by
649         // name)
650         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
651         if( !src_canvas )
652         {
653           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
654           return;
655         }
656
657         setSrcCanvas(src_canvas);
658       }
659       else if( protocol == "http" || protocol == "https" )
660       // TODO check https
661       {
662         _http_request =
663           Canvas::getSystemAdapter()
664           ->getHTTPClient()
665           ->load(url)
666           // TODO handle capture of 'this'
667           ->done(this, &Image::handleImageLoadDone);
668       }
669       else
670       {
671         setImage( Canvas::getSystemAdapter()->getImage(path) );
672       }
673     }
674   }
675
676   //----------------------------------------------------------------------------
677   void Image::setupDefaultDimensions()
678   {
679     if( !_src_rect.width() || !_src_rect.height() )
680     {
681       // Show whole image by default
682       _node_src_rect->setBoolValue("normalized", true);
683       _node_src_rect->setFloatValue("right", 1);
684       _node_src_rect->setFloatValue("bottom", 1);
685     }
686
687     if( !_region.width() || !_region.height() )
688     {
689       // Default to image size.
690       // TODO handle showing only part of image?
691       const SGRect<int>& dim = getTextureDimensions();
692       _node->setFloatValue("size[0]", dim.width());
693       _node->setFloatValue("size[1]", dim.height());
694     }
695   }
696
697   //----------------------------------------------------------------------------
698   SGRect<int> Image::getTextureDimensions() const
699   {
700     CanvasPtr canvas = _src_canvas.lock();
701     SGRect<int> dim(0,0);
702
703     // Use canvas/image dimensions rather than texture dimensions, as they could
704     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
705     if( canvas )
706     {
707       dim.setRight( canvas->getViewWidth() );
708       dim.setBottom( canvas->getViewHeight() );
709     }
710     else if( _texture )
711     {
712       osg::Image* img = _texture->getImage();
713
714       if( img )
715       {
716         dim.setRight( img->s() );
717         dim.setBottom( img->t() );
718       }
719     }
720
721     return dim;
722   }
723
724   //----------------------------------------------------------------------------
725   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
726   {
727     int i = index * 4;
728     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
729     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
730     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
731     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
732   }
733
734   //----------------------------------------------------------------------------
735   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
736   {
737     int i = index * 4;
738     (*_texCoords)[i + 0].set(tl.x(), tl.y());
739     (*_texCoords)[i + 1].set(br.x(), tl.y());
740     (*_texCoords)[i + 2].set(br.x(), br.y());
741     (*_texCoords)[i + 3].set(tl.x(), br.y());
742   }
743
744   //----------------------------------------------------------------------------
745   void Image::handleImageLoadDone(HTTP::Request* req)
746   {
747     // Ignore stale/expired requests
748     if( _http_request != req )
749       return;
750     _http_request.reset();
751
752     if( req->responseCode() != 200 )
753     {
754       SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
755                                                     << req->responseReason());
756       return;
757     }
758
759     const std::string ext = SGPath(req->path()).extension(),
760                       mime = req->responseMime();
761
762     SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
763                            " (ext=" << ext << ", MIME=" << mime << ")");
764
765     const std::string& img_data =
766       static_cast<HTTP::MemoryRequest*>(req)->responseBody();
767     osgDB::Registry* reg = osgDB::Registry::instance();
768
769     // First try to detect image type by extension
770     osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
771     if( rw && loadImage(*rw, img_data, *req, "extension") )
772       return;
773
774     // Now try with MIME type
775     rw = reg->getReaderWriterForMimeType(mime);
776     if( rw && loadImage(*rw, img_data, *req, "MIME type") )
777       return;
778
779     SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
780   }
781
782   //----------------------------------------------------------------------------
783   bool Image::loadImage( osgDB::ReaderWriter& reader,
784                          const std::string& data,
785                          HTTP::Request& request,
786                          const std::string& type )
787   {
788     SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
789
790     std::istringstream data_strm(data);
791     osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
792     if( result.success() )
793     {
794       setImage( result.takeImage() );
795       return true;
796     }
797
798     SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
799                                                     << result.message());
800
801     return false;
802   }
803
804 } // namespace canvas
805 } // namespace simgear