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;
274 setBoundingBox(_geom->getBound());
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 = boost::dynamic_pointer_cast<MouseEvent>(event);
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;
478 return handled | src_canvas->handleMouseEvent(mouse_event);
481 //----------------------------------------------------------------------------
482 void Image::childChanged(SGPropertyNode* child)
484 const std::string& name = child->getNameString();
486 if( child->getParent() == _node_src_rect )
488 _attributes_dirty |= SRC_RECT;
491 _src_rect.setLeft( child->getFloatValue() );
492 else if( name == "right" )
493 _src_rect.setRight( child->getFloatValue() );
494 else if( name == "top" )
495 _src_rect.setTop( child->getFloatValue() );
496 else if( name == "bottom" )
497 _src_rect.setBottom( child->getFloatValue() );
501 else if( child->getParent() != _node )
506 _region.setX( child->getFloatValue() );
507 _attributes_dirty |= DEST_SIZE;
509 else if( name == "y" )
511 _region.setY( child->getFloatValue() );
512 _attributes_dirty |= DEST_SIZE;
514 else if( name == "size" )
516 if( child->getIndex() == 0 )
517 _region.setWidth( child->getFloatValue() );
519 _region.setHeight( child->getFloatValue() );
521 _attributes_dirty |= DEST_SIZE;
523 else if( name == "file" )
525 static const std::string PROTOCOL_SEP = "://";
527 std::string url = child->getStringValue(),
530 size_t sep_pos = url.find(PROTOCOL_SEP);
531 if( sep_pos != std::string::npos )
533 protocol = url.substr(0, sep_pos);
534 path = url.substr(sep_pos + PROTOCOL_SEP.length());
539 if( protocol == "canvas" )
541 CanvasPtr canvas = _canvas.lock();
544 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
548 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
551 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
555 const SGPropertyNode* canvas_node =
556 canvas_mgr->getPropertyRoot()
561 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
565 // TODO add support for other means of addressing canvases (eg. by
567 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
570 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
574 setSrcCanvas(src_canvas);
576 else if( protocol == "http" || protocol == "https" )
579 Canvas::getSystemAdapter()
582 ->done(this, &Image::handleImageLoadDone);
586 setImage( Canvas::getSystemAdapter()->getImage(path) );
591 //----------------------------------------------------------------------------
592 void Image::setupDefaultDimensions()
594 if( !_src_rect.width() || !_src_rect.height() )
596 // Show whole image by default
597 _node_src_rect->setBoolValue("normalized", true);
598 _node_src_rect->setFloatValue("right", 1);
599 _node_src_rect->setFloatValue("bottom", 1);
602 if( !_region.width() || !_region.height() )
604 // Default to image size.
605 // TODO handle showing only part of image?
606 const SGRect<int>& dim = getTextureDimensions();
607 _node->setFloatValue("size[0]", dim.width());
608 _node->setFloatValue("size[1]", dim.height());
612 //----------------------------------------------------------------------------
613 SGRect<int> Image::getTextureDimensions() const
615 CanvasPtr canvas = _src_canvas.lock();
616 SGRect<int> dim(0,0);
618 // Use canvas/image dimensions rather than texture dimensions, as they could
619 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
622 dim.setRight( canvas->getViewWidth() );
623 dim.setBottom( canvas->getViewHeight() );
627 osg::Image* img = _texture->getImage();
631 dim.setRight( img->s() );
632 dim.setBottom( img->t() );
639 //----------------------------------------------------------------------------
640 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
643 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
644 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
645 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
646 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
649 //----------------------------------------------------------------------------
650 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
653 (*_texCoords)[i + 0].set(tl.x(), tl.y());
654 (*_texCoords)[i + 1].set(br.x(), tl.y());
655 (*_texCoords)[i + 2].set(br.x(), br.y());
656 (*_texCoords)[i + 3].set(tl.x(), br.y());
659 //----------------------------------------------------------------------------
660 void Image::handleImageLoadDone(HTTP::Request* req)
662 if( req->responseCode() != 200 )
666 "canvas::Image: failed to download '" << req->url() << "': "
667 << req->responseReason() );
671 std::string ext = SGPath(req->path()).extension();
672 SG_LOG(SG_GL, SG_INFO, "canvas::Image: received " << req->url());
674 osgDB::ReaderWriter* rw =
675 osgDB::Registry::instance()->getReaderWriterForExtension(ext);
677 std::istringstream img_data(
678 static_cast<HTTP::MemoryRequest*>(req)->responseBody()
681 osgDB::ReaderWriter::ReadResult result = rw->readImage(img_data);
682 if( result.success() )
683 setImage( result.takeImage() );
687 "canvas::Image: failed to read image '" << req->url() << "': "
688 << result.message() );
691 } // namespace canvas
692 } // namespace simgear