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