1 // An image on the Canvas
3 // Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
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.
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.
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
19 #include "CanvasImage.hxx"
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>
31 #include <osg/Geometry>
32 #include <osg/PrimitiveSet>
33 #include <osgDB/Registry>
40 * Callback to enable/disable rendering of canvas displayed inside windows or
44 public osg::Drawable::CullCallback
47 CullCallback(const CanvasWeakPtr& canvas);
51 CanvasWeakPtr _canvas;
52 mutable bool _cull_next_frame;
54 virtual bool cull( osg::NodeVisitor* nv,
55 osg::Drawable* drawable,
56 osg::RenderInfo* renderInfo ) const;
59 //----------------------------------------------------------------------------
60 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
62 _cull_next_frame( false )
67 //----------------------------------------------------------------------------
68 void CullCallback::cullNextFrame()
70 _cull_next_frame = true;
73 //----------------------------------------------------------------------------
74 bool CullCallback::cull( osg::NodeVisitor* nv,
75 osg::Drawable* drawable,
76 osg::RenderInfo* renderInfo ) const
78 CanvasPtr canvas = _canvas.lock();
80 canvas->enableRendering();
82 if( !_cull_next_frame )
83 // TODO check if window/image should be culled
86 _cull_next_frame = false;
90 //----------------------------------------------------------------------------
91 const std::string Image::TYPE_NAME = "image";
93 //----------------------------------------------------------------------------
94 void Image::staticInit()
99 addStyle("fill", "color", &Image::setFill);
100 addStyle("slice", "", &Image::setSlice);
101 addStyle("slice-width", "", &Image::setSliceWidth);
102 addStyle("outset", "", &Image::setOutset);
105 //----------------------------------------------------------------------------
106 Image::Image( const CanvasWeakPtr& canvas,
107 const SGPropertyNode_ptr& node,
108 const Style& parent_style,
110 Element(canvas, node, parent_style, parent),
111 _texture(new osg::Texture2D),
112 _node_src_rect( node->getNode("source", 0, true) ),
118 _geom = new osg::Geometry;
119 _geom->setUseDisplayList(false);
121 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
122 stateSet->setTextureAttributeAndModes(0, _texture.get());
123 stateSet->setDataVariance(osg::Object::STATIC);
125 // allocate arrays for the image
126 _vertices = new osg::Vec3Array(4);
127 _vertices->setDataVariance(osg::Object::DYNAMIC);
128 _geom->setVertexArray(_vertices);
130 _texCoords = new osg::Vec2Array(4);
131 _texCoords->setDataVariance(osg::Object::DYNAMIC);
132 _geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
134 _colors = new osg::Vec4Array(1);
135 _colors->setDataVariance(osg::Object::DYNAMIC);
136 _geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
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);
145 setFill("#ffffff"); // TODO how should we handle default values?
149 //----------------------------------------------------------------------------
155 //----------------------------------------------------------------------------
156 void Image::update(double dt)
160 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
162 _geom->getOrCreateStateSet()
163 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
165 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
167 if( (_attributes_dirty & SRC_CANVAS)
168 // check if texture has changed (eg. due to resizing)
169 || (canvas && texture != canvas->getTexture()) )
171 _geom->getOrCreateStateSet()
172 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
174 if( !canvas || canvas->isInit() )
175 _attributes_dirty &= ~SRC_CANVAS;
178 if( !_attributes_dirty )
181 const SGRect<int>& tex_dim = getTextureDimensions();
183 // http://www.w3.org/TR/css3-background/#border-image-slice
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");
189 if( _attributes_dirty & DEST_SIZE )
191 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
193 if( num_vertices != _prim->getNumPrimitives() )
195 _vertices->resize(num_vertices);
196 _texCoords->resize(num_vertices);
197 _prim->setCount(num_vertices);
200 _attributes_dirty |= SRC_RECT;
203 // http://www.w3.org/TR/css3-background/#border-image-outset
204 SGRect<float> region = _region;
205 if( _outset.isValid() )
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;
214 if( !_slice.isValid() )
216 setQuad(0, region.getMin(), region.getMax());
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.
227 -------------------- - y[0]
229 -------------------- - y[1]
236 -------------------- - y[2]
238 -------------------- - y[3]
241 const CSSBorder::Offsets& slice =
242 (_slice_width.isValid() ? _slice_width : _slice)
243 .getAbsOffsets(tex_dim);
246 region.l() + slice.l,
247 region.r() - slice.r,
252 region.t() + slice.t,
253 region.b() - slice.b,
258 for(int ix = 0; ix < 3; ++ix)
259 for(int iy = 0; iy < 3; ++iy)
261 if( ix == 1 && iy == 1 && !fill )
262 // The ‘fill’ keyword, if present, causes the middle part of the
263 // image to be filled.
267 SGVec2f(x[ix ], y[iy ]),
268 SGVec2f(x[ix + 1], y[iy + 1]) );
273 _attributes_dirty &= ~DEST_SIZE;
277 if( _attributes_dirty & SRC_RECT )
279 SGRect<float> src_rect = _src_rect;
280 if( !_node_src_rect->getBoolValue("normalized", true) )
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();
288 // Image coordinate systems y-axis is flipped
289 std::swap(src_rect.t(), src_rect.b());
291 if( !_slice.isValid() )
293 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
297 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
300 src_rect.l() + slice.l,
301 src_rect.r() - slice.r,
306 src_rect.t() - slice.t,
307 src_rect.b() + slice.b,
312 for(int ix = 0; ix < 3; ++ix)
313 for(int iy = 0; iy < 3; ++iy)
315 if( ix == 1 && iy == 1 && !fill )
316 // The ‘fill’ keyword, if present, causes the middle part of the
317 // image to be filled.
321 SGVec2f(x[ix ], y[iy ]),
322 SGVec2f(x[ix + 1], y[iy + 1]) );
327 _attributes_dirty &= ~SRC_RECT;
331 //----------------------------------------------------------------------------
332 void Image::valueChanged(SGPropertyNode* child)
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.
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.
342 && child->getParent() == _node
343 && child->getNameString() == "visible"
344 && child->getBoolValue() )
346 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
351 Element::valueChanged(child);
354 //----------------------------------------------------------------------------
355 void Image::setSrcCanvas(CanvasPtr canvas)
357 CanvasPtr src_canvas = _src_canvas.lock(),
358 self_canvas = _canvas.lock();
361 src_canvas->removeParentCanvas(self_canvas);
363 self_canvas->removeChildCanvas(src_canvas);
365 _src_canvas = src_canvas = canvas;
366 _attributes_dirty |= SRC_CANVAS;
367 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
371 setupDefaultDimensions();
375 self_canvas->addChildCanvas(src_canvas);
376 src_canvas->addParentCanvas(self_canvas);
381 //----------------------------------------------------------------------------
382 CanvasWeakPtr Image::getSrcCanvas() const
387 //----------------------------------------------------------------------------
388 void Image::setImage(osg::Image *img)
391 setSrcCanvas( CanvasPtr() );
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);
401 setupDefaultDimensions();
404 //----------------------------------------------------------------------------
405 void Image::setFill(const std::string& fill)
407 osg::Vec4 color(1,1,1,1);
408 if( !fill.empty() // If no color is given default to white
409 && !parseColor(fill, color) )
412 _colors->front() = color;
416 //----------------------------------------------------------------------------
417 void Image::setSlice(const std::string& slice)
419 _slice = CSSBorder::parse(slice);
420 _attributes_dirty |= SRC_RECT | DEST_SIZE;
423 //----------------------------------------------------------------------------
424 void Image::setSliceWidth(const std::string& width)
426 _slice_width = CSSBorder::parse(width);
427 _attributes_dirty |= DEST_SIZE;
430 //----------------------------------------------------------------------------
431 void Image::setOutset(const std::string& outset)
433 _outset = CSSBorder::parse(outset);
434 _attributes_dirty |= DEST_SIZE;
437 //----------------------------------------------------------------------------
438 const SGRect<float>& Image::getRegion() const
443 //----------------------------------------------------------------------------
444 bool Image::handleEvent(const EventPtr& event)
446 bool handled = Element::handleEvent(event);
448 CanvasPtr src_canvas = _src_canvas.lock();
452 MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
455 mouse_event.reset( new MouseEvent(*mouse_event) );
457 mouse_event->client_pos = mouse_event->local_pos
458 - toOsg(_region.getMin());
460 osg::Vec2f size(_region.width(), _region.height());
461 if( _outset.isValid() )
463 CSSBorder::Offsets outset =
464 _outset.getAbsOffsets(getTextureDimensions());
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;
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;
476 handled |= src_canvas->handleMouseEvent(mouse_event);
482 //----------------------------------------------------------------------------
483 void Image::childChanged(SGPropertyNode* child)
485 const std::string& name = child->getNameString();
487 if( child->getParent() == _node_src_rect )
489 _attributes_dirty |= SRC_RECT;
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() );
502 else if( child->getParent() != _node )
507 _region.setX( child->getFloatValue() );
508 _attributes_dirty |= DEST_SIZE;
510 else if( name == "y" )
512 _region.setY( child->getFloatValue() );
513 _attributes_dirty |= DEST_SIZE;
515 else if( name == "size" )
517 if( child->getIndex() == 0 )
518 _region.setWidth( child->getFloatValue() );
520 _region.setHeight( child->getFloatValue() );
522 _attributes_dirty |= DEST_SIZE;
524 else if( name == "src" || name == "file" )
527 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
529 static const std::string PROTOCOL_SEP = "://";
531 std::string url = child->getStringValue(),
534 size_t sep_pos = url.find(PROTOCOL_SEP);
535 if( sep_pos != std::string::npos )
537 protocol = url.substr(0, sep_pos);
538 path = url.substr(sep_pos + PROTOCOL_SEP.length());
543 if( protocol == "canvas" )
545 CanvasPtr canvas = _canvas.lock();
548 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
552 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
555 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
559 const SGPropertyNode* canvas_node =
560 canvas_mgr->getPropertyRoot()
565 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
569 // TODO add support for other means of addressing canvases (eg. by
571 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
574 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
578 setSrcCanvas(src_canvas);
580 else if( protocol == "http" || protocol == "https" )
583 Canvas::getSystemAdapter()
586 ->done(this, &Image::handleImageLoadDone);
590 setImage( Canvas::getSystemAdapter()->getImage(path) );
595 //----------------------------------------------------------------------------
596 void Image::setupDefaultDimensions()
598 if( !_src_rect.width() || !_src_rect.height() )
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);
606 if( !_region.width() || !_region.height() )
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());
616 //----------------------------------------------------------------------------
617 SGRect<int> Image::getTextureDimensions() const
619 CanvasPtr canvas = _src_canvas.lock();
620 SGRect<int> dim(0,0);
622 // Use canvas/image dimensions rather than texture dimensions, as they could
623 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
626 dim.setRight( canvas->getViewWidth() );
627 dim.setBottom( canvas->getViewHeight() );
631 osg::Image* img = _texture->getImage();
635 dim.setRight( img->s() );
636 dim.setBottom( img->t() );
643 //----------------------------------------------------------------------------
644 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
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);
653 //----------------------------------------------------------------------------
654 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
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());
663 //----------------------------------------------------------------------------
664 void Image::handleImageLoadDone(HTTP::Request* req)
666 if( req->responseCode() != 200 )
668 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
669 << req->responseReason());
673 const std::string ext = SGPath(req->path()).extension(),
674 mime = req->responseMime();
676 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
677 " (ext=" << ext << ", MIME=" << mime << ")");
679 const std::string& img_data =
680 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
681 osgDB::Registry* reg = osgDB::Registry::instance();
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") )
688 // Now try with MIME type
689 rw = reg->getReaderWriterForMimeType(mime);
690 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
693 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
696 //----------------------------------------------------------------------------
697 bool Image::loadImage( osgDB::ReaderWriter& reader,
698 const std::string& data,
699 HTTP::Request& request,
700 const std::string& type )
702 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
704 std::istringstream data_strm(data);
705 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
706 if( result.success() )
708 setImage( result.takeImage() );
712 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
713 << result.message());
718 } // namespace canvas
719 } // namespace simgear