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/KeyboardEvent.hxx>
25 #include <simgear/canvas/events/MouseEvent.hxx>
26 #include <simgear/scene/util/OsgMath.hxx>
27 #include <simgear/scene/util/parse_color.hxx>
28 #include <simgear/misc/sg_path.hxx>
31 #include <osg/Geometry>
32 #include <osg/PrimitiveSet>
33 #include <osgDB/Registry>
34 #include <osg/Version>
41 * Callback to enable/disable rendering of canvas displayed inside windows or
45 public osg::Drawable::CullCallback
48 CullCallback(const CanvasWeakPtr& canvas);
52 CanvasWeakPtr _canvas;
53 mutable bool _cull_next_frame;
55 virtual bool cull( osg::NodeVisitor* nv,
56 osg::Drawable* drawable,
57 osg::RenderInfo* renderInfo ) const;
60 //----------------------------------------------------------------------------
61 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
63 _cull_next_frame( false )
68 //----------------------------------------------------------------------------
69 void CullCallback::cullNextFrame()
71 _cull_next_frame = true;
74 //----------------------------------------------------------------------------
75 bool CullCallback::cull( osg::NodeVisitor* nv,
76 osg::Drawable* drawable,
77 osg::RenderInfo* renderInfo ) const
79 CanvasPtr canvas = _canvas.lock();
81 canvas->enableRendering();
83 if( !_cull_next_frame )
84 // TODO check if window/image should be culled
87 _cull_next_frame = false;
91 //----------------------------------------------------------------------------
92 const std::string Image::TYPE_NAME = "image";
94 //----------------------------------------------------------------------------
95 void Image::staticInit()
100 addStyle("fill", "color", &Image::setFill);
101 addStyle("outset", "", &Image::setOutset);
102 addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
103 addStyle("slice", "", &Image::setSlice);
104 addStyle("slice-width", "", &Image::setSliceWidth);
106 osgDB::Registry* reg = osgDB::Registry::instance();
107 if( !reg->getReaderWriterForExtension("png") )
108 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Missing 'png' image reader");
111 //----------------------------------------------------------------------------
112 Image::Image( const CanvasWeakPtr& canvas,
113 const SGPropertyNode_ptr& node,
114 const Style& parent_style,
116 Element(canvas, node, parent_style, parent),
117 _texture(new osg::Texture2D),
118 _node_src_rect( node->getNode("source", 0, true) ),
124 _geom = new osg::Geometry;
125 _geom->setUseDisplayList(false);
127 osg::StateSet *stateSet = _geom->getOrCreateStateSet();
128 stateSet->setTextureAttributeAndModes(0, _texture.get());
129 stateSet->setDataVariance(osg::Object::STATIC);
131 // allocate arrays for the image
132 _vertices = new osg::Vec3Array(4);
133 _vertices->setDataVariance(osg::Object::DYNAMIC);
134 _geom->setVertexArray(_vertices);
136 _texCoords = new osg::Vec2Array(4);
137 _texCoords->setDataVariance(osg::Object::DYNAMIC);
138 _geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
140 _colors = new osg::Vec4Array(1);
141 _colors->setDataVariance(osg::Object::DYNAMIC);
142 _geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
144 _prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
145 _prim->set(osg::PrimitiveSet::QUADS, 0, 4);
146 _prim->setDataVariance(osg::Object::DYNAMIC);
147 _geom->addPrimitiveSet(_prim);
151 setFill("#ffffff"); // TODO how should we handle default values?
155 //----------------------------------------------------------------------------
159 _http_request->abort("image destroyed");
162 //----------------------------------------------------------------------------
163 void Image::update(double dt)
167 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
169 _geom->getOrCreateStateSet()
170 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
172 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
174 if( (_attributes_dirty & SRC_CANVAS)
175 // check if texture has changed (eg. due to resizing)
176 || (canvas && texture != canvas->getTexture()) )
178 _geom->getOrCreateStateSet()
179 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
181 if( !canvas || canvas->isInit() )
182 _attributes_dirty &= ~SRC_CANVAS;
185 if( !_attributes_dirty )
188 const SGRect<int>& tex_dim = getTextureDimensions();
190 // http://www.w3.org/TR/css3-background/#border-image-slice
192 // The ‘fill’ keyword, if present, causes the middle part of the image to be
193 // preserved. (By default it is discarded, i.e., treated as empty.)
194 bool fill = (_slice.getKeyword() == "fill");
196 if( _attributes_dirty & DEST_SIZE )
198 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
200 if( num_vertices != _prim->getNumPrimitives() )
202 _vertices->resize(num_vertices);
203 _texCoords->resize(num_vertices);
204 _prim->setCount(num_vertices);
207 _attributes_dirty |= SRC_RECT;
210 // http://www.w3.org/TR/css3-background/#border-image-outset
211 SGRect<float> region = _region;
212 if( _outset.isValid() )
214 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
215 region.t() -= outset.t;
216 region.r() += outset.r;
217 region.b() += outset.b;
218 region.l() -= outset.l;
221 if( !_slice.isValid() )
223 setQuad(0, region.getMin(), region.getMax());
225 if( !_preserve_aspect_ratio.scaleToFill() )
226 // We need to update texture coordinates to keep the aspect ratio
227 _attributes_dirty |= SRC_RECT;
232 Image slice, 9-scale, whatever it is called. The four corner images
233 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
234 fill the remaining space up to the specified size.
238 -------------------- - y[0]
240 -------------------- - y[1]
247 -------------------- - y[2]
249 -------------------- - y[3]
252 const CSSBorder::Offsets& slice =
253 (_slice_width.isValid() ? _slice_width : _slice)
254 .getAbsOffsets(tex_dim);
257 region.l() + slice.l,
258 region.r() - slice.r,
263 region.t() + slice.t,
264 region.b() - slice.b,
269 for(int ix = 0; ix < 3; ++ix)
270 for(int iy = 0; iy < 3; ++iy)
272 if( ix == 1 && iy == 1 && !fill )
273 // The ‘fill’ keyword, if present, causes the middle part of the
274 // image to be filled.
278 SGVec2f(x[ix ], y[iy ]),
279 SGVec2f(x[ix + 1], y[iy + 1]) );
284 _attributes_dirty &= ~DEST_SIZE;
288 if( _attributes_dirty & SRC_RECT )
290 SGRect<float> src_rect = _src_rect;
291 if( !_node_src_rect->getBoolValue("normalized", true) )
293 src_rect.t() /= tex_dim.height();
294 src_rect.r() /= tex_dim.width();
295 src_rect.b() /= tex_dim.height();
296 src_rect.l() /= tex_dim.width();
299 // Image coordinate systems y-axis is flipped
300 std::swap(src_rect.t(), src_rect.b());
302 if( !_slice.isValid() )
304 // Image scaling preserving aspect ratio. Change texture coordinates to
305 // scale image accordingly.
307 // TODO allow to specify what happens to not filled space (eg. color,
308 // or texture repeat/mirror)
310 // http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
311 if( !_preserve_aspect_ratio.scaleToFill() )
313 osg::BoundingBox const& bb = getBoundingBox();
314 float dst_width = bb._max.x() - bb._min.x(),
315 dst_height = bb._max.y() - bb._min.y();
316 float scale_x = dst_width / tex_dim.width(),
317 scale_y = dst_height / tex_dim.height();
319 float scale = _preserve_aspect_ratio.scaleToFit()
320 ? std::min(scale_x, scale_y)
321 : std::max(scale_x, scale_y);
323 if( scale_x != scale )
325 float d = scale_x / scale - 1;
326 if( _preserve_aspect_ratio.alignX()
327 == SVGpreserveAspectRatio::ALIGN_MIN )
331 else if( _preserve_aspect_ratio.alignX()
332 == SVGpreserveAspectRatio::ALIGN_MAX )
338 src_rect.l() -= d / 2;
339 src_rect.r() += d / 2;
343 if( scale_y != scale )
345 float d = scale_y / scale - 1;
346 if( _preserve_aspect_ratio.alignY()
347 == SVGpreserveAspectRatio::ALIGN_MIN )
351 else if( _preserve_aspect_ratio.alignY()
352 == SVGpreserveAspectRatio::ALIGN_MAX )
358 src_rect.t() += d / 2;
359 src_rect.b() -= d / 2;
364 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
368 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
371 src_rect.l() + slice.l,
372 src_rect.r() - slice.r,
377 src_rect.t() - slice.t,
378 src_rect.b() + slice.b,
383 for(int ix = 0; ix < 3; ++ix)
384 for(int iy = 0; iy < 3; ++iy)
386 if( ix == 1 && iy == 1 && !fill )
387 // The ‘fill’ keyword, if present, causes the middle part of the
388 // image to be filled.
392 SGVec2f(x[ix ], y[iy ]),
393 SGVec2f(x[ix + 1], y[iy + 1]) );
398 _attributes_dirty &= ~SRC_RECT;
402 //----------------------------------------------------------------------------
403 void Image::valueChanged(SGPropertyNode* child)
405 // If the image is switched from invisible to visible, and it shows a
406 // canvas, we need to delay showing it by one frame to ensure the canvas is
407 // updated before the image is displayed.
409 // As canvas::Element handles and filters changes to the "visible" property
410 // we can not check this in Image::childChanged but instead have to override
411 // Element::valueChanged.
413 && child->getParent() == _node
414 && child->getNameString() == "visible"
415 && child->getBoolValue() )
418 #if OSG_VERSION_LESS_THAN(3,3,2)
419 static_cast<CullCallback*>
421 dynamic_cast<CullCallback*>
423 ( _geom->getCullCallback() );
429 Element::valueChanged(child);
432 //----------------------------------------------------------------------------
433 void Image::setSrcCanvas(CanvasPtr canvas)
435 CanvasPtr src_canvas = _src_canvas.lock(),
436 self_canvas = _canvas.lock();
439 src_canvas->removeParentCanvas(self_canvas);
441 self_canvas->removeChildCanvas(src_canvas);
443 _src_canvas = src_canvas = canvas;
444 _attributes_dirty |= SRC_CANVAS;
445 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
449 setupDefaultDimensions();
453 self_canvas->addChildCanvas(src_canvas);
454 src_canvas->addParentCanvas(self_canvas);
459 //----------------------------------------------------------------------------
460 CanvasWeakPtr Image::getSrcCanvas() const
465 //----------------------------------------------------------------------------
466 void Image::setImage(osg::Image *img)
469 setSrcCanvas( CanvasPtr() );
471 _texture->setResizeNonPowerOfTwoHint(false);
472 _texture->setImage(img);
473 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
474 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
475 _geom->getOrCreateStateSet()
476 ->setTextureAttributeAndModes(0, _texture);
479 setupDefaultDimensions();
482 //----------------------------------------------------------------------------
483 void Image::setFill(const std::string& fill)
485 osg::Vec4 color(1,1,1,1);
486 if( !fill.empty() // If no color is given default to white
487 && !parseColor(fill, color) )
490 _colors->front() = color;
494 //----------------------------------------------------------------------------
495 void Image::setOutset(const std::string& outset)
497 _outset = CSSBorder::parse(outset);
498 _attributes_dirty |= DEST_SIZE;
501 //----------------------------------------------------------------------------
502 void Image::setPreserveAspectRatio(const std::string& scale)
504 _preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
505 _attributes_dirty |= SRC_RECT;
508 //----------------------------------------------------------------------------
509 void Image::setSlice(const std::string& slice)
511 _slice = CSSBorder::parse(slice);
512 _attributes_dirty |= SRC_RECT | DEST_SIZE;
515 //----------------------------------------------------------------------------
516 void Image::setSliceWidth(const std::string& width)
518 _slice_width = CSSBorder::parse(width);
519 _attributes_dirty |= DEST_SIZE;
522 //----------------------------------------------------------------------------
523 const SGRect<float>& Image::getRegion() const
528 //----------------------------------------------------------------------------
529 bool Image::handleEvent(const EventPtr& event)
531 bool handled = Element::handleEvent(event);
533 CanvasPtr src_canvas = _src_canvas.lock();
537 if( MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get()) )
539 mouse_event.reset( new MouseEvent(*mouse_event) );
541 mouse_event->client_pos = mouse_event->local_pos
542 - toOsg(_region.getMin());
544 osg::Vec2f size(_region.width(), _region.height());
545 if( _outset.isValid() )
547 CSSBorder::Offsets outset =
548 _outset.getAbsOffsets(getTextureDimensions());
550 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
551 size.x() += outset.l + outset.r;
552 size.y() += outset.t + outset.b;
555 // Scale event pos according to canvas view size vs. displayed/screen size
556 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
557 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
558 mouse_event->local_pos = mouse_event->client_pos;
560 handled |= src_canvas->handleMouseEvent(mouse_event);
562 else if( KeyboardEventPtr keyboard_event =
563 dynamic_cast<KeyboardEvent*>(event.get()) )
565 handled |= src_canvas->handleKeyboardEvent(keyboard_event);
571 //----------------------------------------------------------------------------
572 void Image::childChanged(SGPropertyNode* child)
574 const std::string& name = child->getNameString();
576 if( child->getParent() == _node_src_rect )
578 _attributes_dirty |= SRC_RECT;
581 _src_rect.setLeft( child->getFloatValue() );
582 else if( name == "right" )
583 _src_rect.setRight( child->getFloatValue() );
584 else if( name == "top" )
585 _src_rect.setTop( child->getFloatValue() );
586 else if( name == "bottom" )
587 _src_rect.setBottom( child->getFloatValue() );
591 else if( child->getParent() != _node )
596 _region.setX( child->getFloatValue() );
597 _attributes_dirty |= DEST_SIZE;
599 else if( name == "y" )
601 _region.setY( child->getFloatValue() );
602 _attributes_dirty |= DEST_SIZE;
604 else if( name == "size" )
606 if( child->getIndex() == 0 )
607 _region.setWidth( child->getFloatValue() );
609 _region.setHeight( child->getFloatValue() );
611 _attributes_dirty |= DEST_SIZE;
613 else if( name == "src" || name == "file" )
616 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
618 // Abort pending request
621 _http_request->abort("setting new image");
622 _http_request.reset();
625 static const std::string PROTOCOL_SEP = "://";
627 std::string url = child->getStringValue(),
630 size_t sep_pos = url.find(PROTOCOL_SEP);
631 if( sep_pos != std::string::npos )
633 protocol = url.substr(0, sep_pos);
634 path = url.substr(sep_pos + PROTOCOL_SEP.length());
639 if( protocol == "canvas" )
641 CanvasPtr canvas = _canvas.lock();
644 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
648 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
651 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
655 const SGPropertyNode* canvas_node =
656 canvas_mgr->getPropertyRoot()
661 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
665 // TODO add support for other means of addressing canvases (eg. by
667 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
670 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
674 setSrcCanvas(src_canvas);
676 else if( protocol == "http" || protocol == "https" )
680 Canvas::getSystemAdapter()
683 // TODO handle capture of 'this'
684 ->done(this, &Image::handleImageLoadDone);
688 setImage( Canvas::getSystemAdapter()->getImage(path) );
693 //----------------------------------------------------------------------------
694 void Image::setupDefaultDimensions()
696 if( !_src_rect.width() || !_src_rect.height() )
698 // Show whole image by default
699 _node_src_rect->setBoolValue("normalized", true);
700 _node_src_rect->setFloatValue("right", 1);
701 _node_src_rect->setFloatValue("bottom", 1);
704 if( !_region.width() || !_region.height() )
706 // Default to image size.
707 // TODO handle showing only part of image?
708 const SGRect<int>& dim = getTextureDimensions();
709 _node->setFloatValue("size[0]", dim.width());
710 _node->setFloatValue("size[1]", dim.height());
714 //----------------------------------------------------------------------------
715 SGRect<int> Image::getTextureDimensions() const
717 CanvasPtr canvas = _src_canvas.lock();
718 SGRect<int> dim(0,0);
720 // Use canvas/image dimensions rather than texture dimensions, as they could
721 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
724 dim.setRight( canvas->getViewWidth() );
725 dim.setBottom( canvas->getViewHeight() );
729 osg::Image* img = _texture->getImage();
733 dim.setRight( img->s() );
734 dim.setBottom( img->t() );
741 //----------------------------------------------------------------------------
742 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
745 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
746 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
747 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
748 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
751 //----------------------------------------------------------------------------
752 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
755 (*_texCoords)[i + 0].set(tl.x(), tl.y());
756 (*_texCoords)[i + 1].set(br.x(), tl.y());
757 (*_texCoords)[i + 2].set(br.x(), br.y());
758 (*_texCoords)[i + 3].set(tl.x(), br.y());
761 //----------------------------------------------------------------------------
762 void Image::handleImageLoadDone(HTTP::Request* req)
764 // Ignore stale/expired requests
765 if( _http_request != req )
767 _http_request.reset();
769 if( req->responseCode() != 200 )
771 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
772 << req->responseReason());
776 const std::string ext = SGPath(req->path()).extension(),
777 mime = req->responseMime();
779 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
780 " (ext=" << ext << ", MIME=" << mime << ")");
782 const std::string& img_data =
783 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
784 osgDB::Registry* reg = osgDB::Registry::instance();
786 // First try to detect image type by extension
787 osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
788 if( rw && loadImage(*rw, img_data, *req, "extension") )
791 // Now try with MIME type
792 rw = reg->getReaderWriterForMimeType(mime);
793 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
796 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
799 //----------------------------------------------------------------------------
800 bool Image::loadImage( osgDB::ReaderWriter& reader,
801 const std::string& data,
802 HTTP::Request& request,
803 const std::string& type )
805 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
807 std::istringstream data_strm(data);
808 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
809 if( result.success() )
811 setImage( result.takeImage() );
815 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
816 << result.message());
821 } // namespace canvas
822 } // namespace simgear