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