]> git.mxchange.org Git - simgear.git/blob - simgear/canvas/elements/CanvasImage.cxx
Canvas: fix transformation of clip rect.
[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 #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     CanvasPtr canvas = _canvas.lock();
79     if( canvas )
80       canvas->enableRendering();
81
82     if( !_cull_next_frame )
83       // TODO check if window/image should be culled
84       return false;
85
86     _cull_next_frame = false;
87     return true;
88   }
89
90   //----------------------------------------------------------------------------
91   const std::string Image::TYPE_NAME = "image";
92
93   //----------------------------------------------------------------------------
94   void Image::staticInit()
95   {
96     if( isInit<Image>() )
97       return;
98
99     addStyle("fill", "color", &Image::setFill);
100     addStyle("slice", "", &Image::setSlice);
101     addStyle("slice-width", "", &Image::setSliceWidth);
102     addStyle("outset", "", &Image::setOutset);
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
153   }
154
155   //----------------------------------------------------------------------------
156   void Image::update(double dt)
157   {
158     Element::update(dt);
159
160     osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
161     (
162       _geom->getOrCreateStateSet()
163            ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
164     );
165     simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
166
167     if(    (_attributes_dirty & SRC_CANVAS)
168            // check if texture has changed (eg. due to resizing)
169         || (canvas && texture != canvas->getTexture()) )
170     {
171       _geom->getOrCreateStateSet()
172            ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
173
174       if( !canvas || canvas->isInit() )
175         _attributes_dirty &= ~SRC_CANVAS;
176     }
177
178     if( !_attributes_dirty )
179       return;
180
181     const SGRect<int>& tex_dim = getTextureDimensions();
182
183     // http://www.w3.org/TR/css3-background/#border-image-slice
184
185     // The ‘fill’ keyword, if present, causes the middle part of the image to be
186     // preserved. (By default it is discarded, i.e., treated as empty.)
187     bool fill = (_slice.getKeyword() == "fill");
188
189     if( _attributes_dirty & DEST_SIZE )
190     {
191       size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
192
193       if( num_vertices != _prim->getNumPrimitives() )
194       {
195         _vertices->resize(num_vertices);
196         _texCoords->resize(num_vertices);
197         _prim->setCount(num_vertices);
198         _prim->dirty();
199
200         _attributes_dirty |= SRC_RECT;
201       }
202
203       // http://www.w3.org/TR/css3-background/#border-image-outset
204       SGRect<float> region = _region;
205       if( _outset.isValid() )
206       {
207         const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
208         region.t() -= outset.t;
209         region.r() += outset.r;
210         region.b() += outset.b;
211         region.l() -= outset.l;
212       }
213
214       if( !_slice.isValid() )
215       {
216         setQuad(0, region.getMin(), region.getMax());
217       }
218       else
219       {
220         /*
221         Image slice, 9-scale, whatever it is called. The four corner images
222         stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
223         fill the remaining space up to the specified size.
224
225         x[0] x[1]     x[2] x[3]
226           |    |        |    |
227           -------------------- - y[0]
228           | tl |   top  | tr |
229           -------------------- - y[1]
230           |    |        |    |
231           | l  |        |  r |
232           | e  | center |  i |
233           | f  |        |  g |
234           | t  |        |  h |
235           |    |        |  t |
236           -------------------- - y[2]
237           | bl | bottom | br |
238           -------------------- - y[3]
239          */
240
241         const CSSBorder::Offsets& slice =
242           (_slice_width.isValid() ? _slice_width : _slice)
243           .getAbsOffsets(tex_dim);
244         float x[4] = {
245           region.l(),
246           region.l() + slice.l,
247           region.r() - slice.r,
248           region.r()
249         };
250         float y[4] = {
251           region.t(),
252           region.t() + slice.t,
253           region.b() - slice.b,
254           region.b()
255         };
256
257         int i = 0;
258         for(int ix = 0; ix < 3; ++ix)
259           for(int iy = 0; iy < 3; ++iy)
260           {
261             if( ix == 1 && iy == 1 && !fill )
262               // The ‘fill’ keyword, if present, causes the middle part of the
263               // image to be filled.
264               continue;
265
266             setQuad( i++,
267                      SGVec2f(x[ix    ], y[iy    ]),
268                      SGVec2f(x[ix + 1], y[iy + 1]) );
269           }
270       }
271
272       _vertices->dirty();
273       _attributes_dirty &= ~DEST_SIZE;
274       _geom->dirtyBound();
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(const 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 = dynamic_cast<MouseEvent*>(event.get());
453     if( mouse_event )
454     {
455       mouse_event.reset( new MouseEvent(*mouse_event) );
456
457       mouse_event->client_pos = mouse_event->local_pos
458                               - toOsg(_region.getMin());
459
460       osg::Vec2f size(_region.width(), _region.height());
461       if( _outset.isValid() )
462       {
463         CSSBorder::Offsets outset =
464           _outset.getAbsOffsets(getTextureDimensions());
465
466         mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
467         size.x() += outset.l + outset.r;
468         size.y() += outset.t + outset.b;
469       }
470
471       // Scale event pos according to canvas view size vs. displayed/screen size
472       mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
473       mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
474       mouse_event->local_pos = mouse_event->client_pos;
475
476       handled |= src_canvas->handleMouseEvent(mouse_event);
477     }
478
479     return handled;
480   }
481
482   //----------------------------------------------------------------------------
483   void Image::childChanged(SGPropertyNode* child)
484   {
485     const std::string& name = child->getNameString();
486
487     if( child->getParent() == _node_src_rect )
488     {
489       _attributes_dirty |= SRC_RECT;
490
491       if(      name == "left" )
492         _src_rect.setLeft( child->getFloatValue() );
493       else if( name == "right" )
494         _src_rect.setRight( child->getFloatValue() );
495       else if( name == "top" )
496         _src_rect.setTop( child->getFloatValue() );
497       else if( name == "bottom" )
498         _src_rect.setBottom( child->getFloatValue() );
499
500       return;
501     }
502     else if( child->getParent() != _node )
503       return;
504
505     if( name == "x" )
506     {
507       _region.setX( child->getFloatValue() );
508       _attributes_dirty |= DEST_SIZE;
509     }
510     else if( name == "y" )
511     {
512       _region.setY( child->getFloatValue() );
513       _attributes_dirty |= DEST_SIZE;
514     }
515     else if( name == "size" )
516     {
517       if( child->getIndex() == 0 )
518         _region.setWidth( child->getFloatValue() );
519       else
520         _region.setHeight( child->getFloatValue() );
521
522       _attributes_dirty |= DEST_SIZE;
523     }
524     else if( name == "src" || name == "file" )
525     {
526       if( name == "file" )
527         SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
528
529       static const std::string PROTOCOL_SEP = "://";
530
531       std::string url = child->getStringValue(),
532                   protocol, path;
533
534       size_t sep_pos = url.find(PROTOCOL_SEP);
535       if( sep_pos != std::string::npos )
536       {
537         protocol = url.substr(0, sep_pos);
538         path = url.substr(sep_pos + PROTOCOL_SEP.length());
539       }
540       else
541         path = url;
542
543       if( protocol == "canvas" )
544       {
545         CanvasPtr canvas = _canvas.lock();
546         if( !canvas )
547         {
548           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
549           return;
550         }
551
552         CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
553         if( !canvas_mgr )
554         {
555           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
556           return;
557         }
558
559         const SGPropertyNode* canvas_node =
560           canvas_mgr->getPropertyRoot()
561                     ->getParent()
562                     ->getNode( path );
563         if( !canvas_node )
564         {
565           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
566           return;
567         }
568
569         // TODO add support for other means of addressing canvases (eg. by
570         // name)
571         CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
572         if( !src_canvas )
573         {
574           SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
575           return;
576         }
577
578         setSrcCanvas(src_canvas);
579       }
580       else if( protocol == "http" || protocol == "https" )
581       // TODO check https
582       {
583         Canvas::getSystemAdapter()
584           ->getHTTPClient()
585           ->load(url)
586           ->done(this, &Image::handleImageLoadDone);
587       }
588       else
589       {
590         setImage( Canvas::getSystemAdapter()->getImage(path) );
591       }
592     }
593   }
594
595   //----------------------------------------------------------------------------
596   void Image::setupDefaultDimensions()
597   {
598     if( !_src_rect.width() || !_src_rect.height() )
599     {
600       // Show whole image by default
601       _node_src_rect->setBoolValue("normalized", true);
602       _node_src_rect->setFloatValue("right", 1);
603       _node_src_rect->setFloatValue("bottom", 1);
604     }
605
606     if( !_region.width() || !_region.height() )
607     {
608       // Default to image size.
609       // TODO handle showing only part of image?
610       const SGRect<int>& dim = getTextureDimensions();
611       _node->setFloatValue("size[0]", dim.width());
612       _node->setFloatValue("size[1]", dim.height());
613     }
614   }
615
616   //----------------------------------------------------------------------------
617   SGRect<int> Image::getTextureDimensions() const
618   {
619     CanvasPtr canvas = _src_canvas.lock();
620     SGRect<int> dim(0,0);
621
622     // Use canvas/image dimensions rather than texture dimensions, as they could
623     // be resized internally by OpenSceneGraph (eg. to nearest power of two).
624     if( canvas )
625     {
626       dim.setRight( canvas->getViewWidth() );
627       dim.setBottom( canvas->getViewHeight() );
628     }
629     else if( _texture )
630     {
631       osg::Image* img = _texture->getImage();
632
633       if( img )
634       {
635         dim.setRight( img->s() );
636         dim.setBottom( img->t() );
637       }
638     }
639
640     return dim;
641   }
642
643   //----------------------------------------------------------------------------
644   void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
645   {
646     int i = index * 4;
647     (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
648     (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
649     (*_vertices)[i + 2].set(br.x(), br.y(), 0);
650     (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
651   }
652
653   //----------------------------------------------------------------------------
654   void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
655   {
656     int i = index * 4;
657     (*_texCoords)[i + 0].set(tl.x(), tl.y());
658     (*_texCoords)[i + 1].set(br.x(), tl.y());
659     (*_texCoords)[i + 2].set(br.x(), br.y());
660     (*_texCoords)[i + 3].set(tl.x(), br.y());
661   }
662
663   //----------------------------------------------------------------------------
664   void Image::handleImageLoadDone(HTTP::Request* req)
665   {
666     if( req->responseCode() != 200 )
667     {
668       SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
669                                                     << req->responseReason());
670       return;
671     }
672
673     const std::string ext = SGPath(req->path()).extension(),
674                       mime = req->responseMime();
675
676     SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
677                            " (ext=" << ext << ", MIME=" << mime << ")");
678
679     const std::string& img_data =
680       static_cast<HTTP::MemoryRequest*>(req)->responseBody();
681     osgDB::Registry* reg = osgDB::Registry::instance();
682
683     // First try to detect image type by extension
684     osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
685     if( rw && loadImage(*rw, img_data, *req, "extension") )
686       return;
687
688     // Now try with MIME type
689     rw = reg->getReaderWriterForMimeType(mime);
690     if( rw && loadImage(*rw, img_data, *req, "MIME type") )
691       return;
692
693     SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
694   }
695
696   //----------------------------------------------------------------------------
697   bool Image::loadImage( osgDB::ReaderWriter& reader,
698                          const std::string& data,
699                          HTTP::Request& request,
700                          const std::string& type )
701   {
702     SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
703
704     std::istringstream data_strm(data);
705     osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
706     if( result.success() )
707     {
708       setImage( result.takeImage() );
709       return true;
710     }
711
712     SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
713                                                     << result.message());
714
715     return false;
716   }
717
718 } // namespace canvas
719 } // namespace simgear