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(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) );
458 mouse_event->client_pos = mouse_event->local_pos
459 - toOsg(_region.getMin());
461 osg::Vec2f size(_region.width(), _region.height());
462 if( _outset.isValid() )
464 CSSBorder::Offsets outset =
465 _outset.getAbsOffsets(getTextureDimensions());
467 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
468 size.x() += outset.l + outset.r;
469 size.y() += outset.t + outset.b;
472 // Scale event pos according to canvas view size vs. displayed/screen size
473 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
474 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
475 mouse_event->local_pos = mouse_event->client_pos;
477 handled |= src_canvas->handleMouseEvent(mouse_event);
483 //----------------------------------------------------------------------------
484 void Image::childChanged(SGPropertyNode* child)
486 const std::string& name = child->getNameString();
488 if( child->getParent() == _node_src_rect )
490 _attributes_dirty |= SRC_RECT;
493 _src_rect.setLeft( child->getFloatValue() );
494 else if( name == "right" )
495 _src_rect.setRight( child->getFloatValue() );
496 else if( name == "top" )
497 _src_rect.setTop( child->getFloatValue() );
498 else if( name == "bottom" )
499 _src_rect.setBottom( child->getFloatValue() );
503 else if( child->getParent() != _node )
508 _region.setX( child->getFloatValue() );
509 _attributes_dirty |= DEST_SIZE;
511 else if( name == "y" )
513 _region.setY( child->getFloatValue() );
514 _attributes_dirty |= DEST_SIZE;
516 else if( name == "size" )
518 if( child->getIndex() == 0 )
519 _region.setWidth( child->getFloatValue() );
521 _region.setHeight( child->getFloatValue() );
523 _attributes_dirty |= DEST_SIZE;
525 else if( name == "src" || name == "file" )
528 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
530 static const std::string PROTOCOL_SEP = "://";
532 std::string url = child->getStringValue(),
535 size_t sep_pos = url.find(PROTOCOL_SEP);
536 if( sep_pos != std::string::npos )
538 protocol = url.substr(0, sep_pos);
539 path = url.substr(sep_pos + PROTOCOL_SEP.length());
544 if( protocol == "canvas" )
546 CanvasPtr canvas = _canvas.lock();
549 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
553 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
556 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
560 const SGPropertyNode* canvas_node =
561 canvas_mgr->getPropertyRoot()
566 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
570 // TODO add support for other means of addressing canvases (eg. by
572 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
575 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
579 setSrcCanvas(src_canvas);
581 else if( protocol == "http" || protocol == "https" )
584 Canvas::getSystemAdapter()
587 ->done(this, &Image::handleImageLoadDone);
591 setImage( Canvas::getSystemAdapter()->getImage(path) );
596 //----------------------------------------------------------------------------
597 void Image::setupDefaultDimensions()
599 if( !_src_rect.width() || !_src_rect.height() )
601 // Show whole image by default
602 _node_src_rect->setBoolValue("normalized", true);
603 _node_src_rect->setFloatValue("right", 1);
604 _node_src_rect->setFloatValue("bottom", 1);
607 if( !_region.width() || !_region.height() )
609 // Default to image size.
610 // TODO handle showing only part of image?
611 const SGRect<int>& dim = getTextureDimensions();
612 _node->setFloatValue("size[0]", dim.width());
613 _node->setFloatValue("size[1]", dim.height());
617 //----------------------------------------------------------------------------
618 SGRect<int> Image::getTextureDimensions() const
620 CanvasPtr canvas = _src_canvas.lock();
621 SGRect<int> dim(0,0);
623 // Use canvas/image dimensions rather than texture dimensions, as they could
624 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
627 dim.setRight( canvas->getViewWidth() );
628 dim.setBottom( canvas->getViewHeight() );
632 osg::Image* img = _texture->getImage();
636 dim.setRight( img->s() );
637 dim.setBottom( img->t() );
644 //----------------------------------------------------------------------------
645 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
648 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
649 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
650 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
651 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
654 //----------------------------------------------------------------------------
655 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
658 (*_texCoords)[i + 0].set(tl.x(), tl.y());
659 (*_texCoords)[i + 1].set(br.x(), tl.y());
660 (*_texCoords)[i + 2].set(br.x(), br.y());
661 (*_texCoords)[i + 3].set(tl.x(), br.y());
664 //----------------------------------------------------------------------------
665 void Image::handleImageLoadDone(HTTP::Request* req)
667 if( req->responseCode() != 200 )
669 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
670 << req->responseReason());
674 const std::string ext = SGPath(req->path()).extension(),
675 mime = req->responseMime();
677 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
678 " (ext=" << ext << ", MIME=" << mime << ")");
680 const std::string& img_data =
681 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
682 osgDB::Registry* reg = osgDB::Registry::instance();
684 // First try to detect image type by extension
685 osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
686 if( rw && loadImage(*rw, img_data, *req, "extension") )
689 // Now try with MIME type
690 rw = reg->getReaderWriterForMimeType(mime);
691 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
694 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
697 //----------------------------------------------------------------------------
698 bool Image::loadImage( osgDB::ReaderWriter& reader,
699 const std::string& data,
700 HTTP::Request& request,
701 const std::string& type )
703 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
705 std::istringstream data_strm(data);
706 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
707 if( result.success() )
709 setImage( result.takeImage() );
713 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
714 << result.message());
719 } // namespace canvas
720 } // namespace simgear