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/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 if( !_canvas.expired() )
79 _canvas.lock()->enableRendering();
81 if( !_cull_next_frame )
82 // TODO check if window/image should be culled
85 _cull_next_frame = false;
89 //----------------------------------------------------------------------------
90 const std::string Image::TYPE_NAME = "image";
92 //----------------------------------------------------------------------------
93 void Image::staticInit()
98 addStyle("fill", "color", &Image::setFill);
99 addStyle("slice", "", &Image::setSlice);
100 addStyle("slice-width", "", &Image::setSliceWidth);
101 addStyle("outset", "", &Image::setOutset);
104 //----------------------------------------------------------------------------
105 Image::Image( const CanvasWeakPtr& canvas,
106 const SGPropertyNode_ptr& node,
107 const Style& parent_style,
109 Element(canvas, node, parent_style, parent),
110 _texture(new osg::Texture2D),
111 _node_src_rect( node->getNode("source", 0, true) ),
117 _geom = new osg::Geometry;
118 _geom->setUseDisplayList(false);
120 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
121 stateSet->setTextureAttributeAndModes(0, _texture.get());
122 stateSet->setDataVariance(osg::Object::STATIC);
124 // allocate arrays for the image
125 _vertices = new osg::Vec3Array(4);
126 _vertices->setDataVariance(osg::Object::DYNAMIC);
127 _geom->setVertexArray(_vertices);
129 _texCoords = new osg::Vec2Array(4);
130 _texCoords->setDataVariance(osg::Object::DYNAMIC);
131 _geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
133 _colors = new osg::Vec4Array(1);
134 _colors->setDataVariance(osg::Object::DYNAMIC);
135 _geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
137 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
138 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
139 _prim->setDataVariance(osg::Object::DYNAMIC);
140 _geom->addPrimitiveSet(_prim);
144 setFill("#ffffff"); // TODO how should we handle default values?
148 //----------------------------------------------------------------------------
154 //----------------------------------------------------------------------------
155 void Image::update(double dt)
159 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
161 _geom->getOrCreateStateSet()
162 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
164 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
166 if( (_attributes_dirty & SRC_CANVAS)
167 // check if texture has changed (eg. due to resizing)
168 || (canvas && texture != canvas->getTexture()) )
170 _geom->getOrCreateStateSet()
171 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
173 if( !canvas || canvas->isInit() )
174 _attributes_dirty &= ~SRC_CANVAS;
177 if( !_attributes_dirty )
180 const SGRect<int>& tex_dim = getTextureDimensions();
182 // http://www.w3.org/TR/css3-background/#border-image-slice
184 // The ‘fill’ keyword, if present, causes the middle part of the image to be
185 // preserved. (By default it is discarded, i.e., treated as empty.)
186 bool fill = (_slice.getKeyword() == "fill");
188 if( _attributes_dirty & DEST_SIZE )
190 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
192 if( num_vertices != _prim->getNumPrimitives() )
194 _vertices->resize(num_vertices);
195 _texCoords->resize(num_vertices);
196 _prim->setCount(num_vertices);
199 _attributes_dirty |= SRC_RECT;
202 // http://www.w3.org/TR/css3-background/#border-image-outset
203 SGRect<float> region = _region;
204 if( _outset.isValid() )
206 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
207 region.t() -= outset.t;
208 region.r() += outset.r;
209 region.b() += outset.b;
210 region.l() -= outset.l;
213 if( !_slice.isValid() )
215 setQuad(0, region.getMin(), region.getMax());
220 Image slice, 9-scale, whatever it is called. The four corner images
221 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
222 fill the remaining space up to the specified size.
226 -------------------- - y[0]
228 -------------------- - y[1]
235 -------------------- - y[2]
237 -------------------- - y[3]
240 const CSSBorder::Offsets& slice =
241 (_slice_width.isValid() ? _slice_width : _slice)
242 .getAbsOffsets(tex_dim);
245 region.l() + slice.l,
246 region.r() - slice.r,
251 region.t() + slice.t,
252 region.b() - slice.b,
257 for(int ix = 0; ix < 3; ++ix)
258 for(int iy = 0; iy < 3; ++iy)
260 if( ix == 1 && iy == 1 && !fill )
261 // The ‘fill’ keyword, if present, causes the middle part of the
262 // image to be filled.
266 SGVec2f(x[ix ], y[iy ]),
267 SGVec2f(x[ix + 1], y[iy + 1]) );
272 _attributes_dirty &= ~DEST_SIZE;
276 if( _attributes_dirty & SRC_RECT )
278 SGRect<float> src_rect = _src_rect;
279 if( !_node_src_rect->getBoolValue("normalized", true) )
281 src_rect.t() /= tex_dim.height();
282 src_rect.r() /= tex_dim.width();
283 src_rect.b() /= tex_dim.height();
284 src_rect.l() /= tex_dim.width();
287 // Image coordinate systems y-axis is flipped
288 std::swap(src_rect.t(), src_rect.b());
290 if( !_slice.isValid() )
292 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
296 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
299 src_rect.l() + slice.l,
300 src_rect.r() - slice.r,
305 src_rect.t() - slice.t,
306 src_rect.b() + slice.b,
311 for(int ix = 0; ix < 3; ++ix)
312 for(int iy = 0; iy < 3; ++iy)
314 if( ix == 1 && iy == 1 && !fill )
315 // The ‘fill’ keyword, if present, causes the middle part of the
316 // image to be filled.
320 SGVec2f(x[ix ], y[iy ]),
321 SGVec2f(x[ix + 1], y[iy + 1]) );
326 _attributes_dirty &= ~SRC_RECT;
330 //----------------------------------------------------------------------------
331 void Image::valueChanged(SGPropertyNode* child)
333 // If the image is switched from invisible to visible, and it shows a
334 // canvas, we need to delay showing it by one frame to ensure the canvas is
335 // updated before the image is displayed.
337 // As canvas::Element handles and filters changes to the "visible" property
338 // we can not check this in Image::childChanged but instead have to override
339 // Element::valueChanged.
341 && child->getParent() == _node
342 && child->getNameString() == "visible"
343 && child->getBoolValue() )
345 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
350 Element::valueChanged(child);
353 //----------------------------------------------------------------------------
354 void Image::setSrcCanvas(CanvasPtr canvas)
356 CanvasPtr src_canvas = _src_canvas.lock(),
357 self_canvas = _canvas.lock();
360 src_canvas->removeParentCanvas(self_canvas);
362 self_canvas->removeChildCanvas(src_canvas);
364 _src_canvas = src_canvas = canvas;
365 _attributes_dirty |= SRC_CANVAS;
366 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
370 setupDefaultDimensions();
374 self_canvas->addChildCanvas(src_canvas);
375 src_canvas->addParentCanvas(self_canvas);
380 //----------------------------------------------------------------------------
381 CanvasWeakPtr Image::getSrcCanvas() const
386 //----------------------------------------------------------------------------
387 void Image::setImage(osg::Image *img)
390 setSrcCanvas( CanvasPtr() );
392 _texture->setResizeNonPowerOfTwoHint(false);
393 _texture->setImage(img);
394 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
395 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
396 _geom->getOrCreateStateSet()
397 ->setTextureAttributeAndModes(0, _texture);
400 setupDefaultDimensions();
403 //----------------------------------------------------------------------------
404 void Image::setFill(const std::string& fill)
406 osg::Vec4 color(1,1,1,1);
407 if( !fill.empty() // If no color is given default to white
408 && !parseColor(fill, color) )
411 _colors->front() = color;
415 //----------------------------------------------------------------------------
416 void Image::setSlice(const std::string& slice)
418 _slice = CSSBorder::parse(slice);
419 _attributes_dirty |= SRC_RECT | DEST_SIZE;
422 //----------------------------------------------------------------------------
423 void Image::setSliceWidth(const std::string& width)
425 _slice_width = CSSBorder::parse(width);
426 _attributes_dirty |= DEST_SIZE;
429 //----------------------------------------------------------------------------
430 void Image::setOutset(const std::string& outset)
432 _outset = CSSBorder::parse(outset);
433 _attributes_dirty |= DEST_SIZE;
436 //----------------------------------------------------------------------------
437 const SGRect<float>& Image::getRegion() const
442 //----------------------------------------------------------------------------
443 bool Image::handleEvent(EventPtr event)
445 bool handled = Element::handleEvent(event);
447 CanvasPtr src_canvas = _src_canvas.lock();
451 MouseEventPtr mouse_event = boost::dynamic_pointer_cast<MouseEvent>(event);
454 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;
477 return handled | src_canvas->handleMouseEvent(mouse_event);
480 //----------------------------------------------------------------------------
481 void Image::childChanged(SGPropertyNode* child)
483 const std::string& name = child->getNameString();
485 if( child->getParent() == _node_src_rect )
487 _attributes_dirty |= SRC_RECT;
490 _src_rect.setLeft( child->getFloatValue() );
491 else if( name == "right" )
492 _src_rect.setRight( child->getFloatValue() );
493 else if( name == "top" )
494 _src_rect.setTop( child->getFloatValue() );
495 else if( name == "bottom" )
496 _src_rect.setBottom( child->getFloatValue() );
500 else if( child->getParent() != _node )
505 _region.setX( child->getFloatValue() );
506 _attributes_dirty |= DEST_SIZE;
508 else if( name == "y" )
510 _region.setY( child->getFloatValue() );
511 _attributes_dirty |= DEST_SIZE;
513 else if( name == "size" )
515 if( child->getIndex() == 0 )
516 _region.setWidth( child->getFloatValue() );
518 _region.setHeight( child->getFloatValue() );
520 _attributes_dirty |= DEST_SIZE;
522 else if( name == "src" || name == "file" )
525 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
527 static const std::string PROTOCOL_SEP = "://";
529 std::string url = child->getStringValue(),
532 size_t sep_pos = url.find(PROTOCOL_SEP);
533 if( sep_pos != std::string::npos )
535 protocol = url.substr(0, sep_pos);
536 path = url.substr(sep_pos + PROTOCOL_SEP.length());
541 if( protocol == "canvas" )
543 CanvasPtr canvas = _canvas.lock();
546 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
550 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
553 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
557 const SGPropertyNode* canvas_node =
558 canvas_mgr->getPropertyRoot()
563 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
567 // TODO add support for other means of addressing canvases (eg. by
569 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
572 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
576 setSrcCanvas(src_canvas);
578 else if( protocol == "http" || protocol == "https" )
581 Canvas::getSystemAdapter()
584 ->done(this, &Image::handleImageLoadDone);
588 setImage( Canvas::getSystemAdapter()->getImage(path) );
593 //----------------------------------------------------------------------------
594 void Image::setupDefaultDimensions()
596 if( !_src_rect.width() || !_src_rect.height() )
598 // Show whole image by default
599 _node_src_rect->setBoolValue("normalized", true);
600 _node_src_rect->setFloatValue("right", 1);
601 _node_src_rect->setFloatValue("bottom", 1);
604 if( !_region.width() || !_region.height() )
606 // Default to image size.
607 // TODO handle showing only part of image?
608 const SGRect<int>& dim = getTextureDimensions();
609 _node->setFloatValue("size[0]", dim.width());
610 _node->setFloatValue("size[1]", dim.height());
614 //----------------------------------------------------------------------------
615 SGRect<int> Image::getTextureDimensions() const
617 CanvasPtr canvas = _src_canvas.lock();
618 SGRect<int> dim(0,0);
620 // Use canvas/image dimensions rather than texture dimensions, as they could
621 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
624 dim.setRight( canvas->getViewWidth() );
625 dim.setBottom( canvas->getViewHeight() );
629 osg::Image* img = _texture->getImage();
633 dim.setRight( img->s() );
634 dim.setBottom( img->t() );
641 //----------------------------------------------------------------------------
642 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
645 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
646 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
647 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
648 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
651 //----------------------------------------------------------------------------
652 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
655 (*_texCoords)[i + 0].set(tl.x(), tl.y());
656 (*_texCoords)[i + 1].set(br.x(), tl.y());
657 (*_texCoords)[i + 2].set(br.x(), br.y());
658 (*_texCoords)[i + 3].set(tl.x(), br.y());
661 //----------------------------------------------------------------------------
662 void Image::handleImageLoadDone(HTTP::Request* req)
664 if( req->responseCode() != 200 )
666 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
667 << req->responseReason());
671 const std::string ext = SGPath(req->path()).extension(),
672 mime = req->responseMime();
674 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
675 " (ext=" << ext << ", MIME=" << mime << ")");
677 const std::string& img_data =
678 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
679 osgDB::Registry* reg = osgDB::Registry::instance();
681 // First try to detect image type by extension
682 osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
683 if( rw && loadImage(*rw, img_data, *req, "extension") )
686 // Now try with MIME type
687 rw = reg->getReaderWriterForMimeType(mime);
688 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
691 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
694 //----------------------------------------------------------------------------
695 bool Image::loadImage( osgDB::ReaderWriter& reader,
696 const std::string& data,
697 HTTP::Request& request,
698 const std::string& type )
700 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
702 std::istringstream data_strm(data);
703 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
704 if( result.success() )
706 setImage( result.takeImage() );
710 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
711 << result.message());
716 } // namespace canvas
717 } // namespace simgear