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>
30 #include <osg/Geometry>
31 #include <osg/PrimitiveSet>
32 #include <osgDB/Registry>
39 * Callback to enable/disable rendering of canvas displayed inside windows or
43 public osg::Drawable::CullCallback
46 CullCallback(const CanvasWeakPtr& canvas);
50 CanvasWeakPtr _canvas;
51 mutable bool _cull_next_frame;
53 virtual bool cull( osg::NodeVisitor* nv,
54 osg::Drawable* drawable,
55 osg::RenderInfo* renderInfo ) const;
58 //----------------------------------------------------------------------------
59 CullCallback::CullCallback(const CanvasWeakPtr& canvas):
61 _cull_next_frame( false )
66 //----------------------------------------------------------------------------
67 void CullCallback::cullNextFrame()
69 _cull_next_frame = true;
72 //----------------------------------------------------------------------------
73 bool CullCallback::cull( osg::NodeVisitor* nv,
74 osg::Drawable* drawable,
75 osg::RenderInfo* renderInfo ) const
77 CanvasPtr canvas = _canvas.lock();
79 canvas->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("outset", "", &Image::setOutset);
100 addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
101 addStyle("slice", "", &Image::setSlice);
102 addStyle("slice-width", "", &Image::setSliceWidth);
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 //----------------------------------------------------------------------------
153 _http_request->abort("image destroyed");
156 //----------------------------------------------------------------------------
157 void Image::update(double dt)
161 osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
163 _geom->getOrCreateStateSet()
164 ->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
166 simgear::canvas::CanvasPtr canvas = _src_canvas.lock();
168 if( (_attributes_dirty & SRC_CANVAS)
169 // check if texture has changed (eg. due to resizing)
170 || (canvas && texture != canvas->getTexture()) )
172 _geom->getOrCreateStateSet()
173 ->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
175 if( !canvas || canvas->isInit() )
176 _attributes_dirty &= ~SRC_CANVAS;
179 if( !_attributes_dirty )
182 const SGRect<int>& tex_dim = getTextureDimensions();
184 // http://www.w3.org/TR/css3-background/#border-image-slice
186 // The ‘fill’ keyword, if present, causes the middle part of the image to be
187 // preserved. (By default it is discarded, i.e., treated as empty.)
188 bool fill = (_slice.getKeyword() == "fill");
190 if( _attributes_dirty & DEST_SIZE )
192 size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
194 if( num_vertices != _prim->getNumPrimitives() )
196 _vertices->resize(num_vertices);
197 _texCoords->resize(num_vertices);
198 _prim->setCount(num_vertices);
201 _attributes_dirty |= SRC_RECT;
204 // http://www.w3.org/TR/css3-background/#border-image-outset
205 SGRect<float> region = _region;
206 if( _outset.isValid() )
208 const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
209 region.t() -= outset.t;
210 region.r() += outset.r;
211 region.b() += outset.b;
212 region.l() -= outset.l;
215 if( !_slice.isValid() )
217 setQuad(0, region.getMin(), region.getMax());
219 if( !_preserve_aspect_ratio.scaleToFill() )
220 // We need to update texture coordinates to keep the aspect ratio
221 _attributes_dirty |= SRC_RECT;
226 Image slice, 9-scale, whatever it is called. The four corner images
227 stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
228 fill the remaining space up to the specified size.
232 -------------------- - y[0]
234 -------------------- - y[1]
241 -------------------- - y[2]
243 -------------------- - y[3]
246 const CSSBorder::Offsets& slice =
247 (_slice_width.isValid() ? _slice_width : _slice)
248 .getAbsOffsets(tex_dim);
251 region.l() + slice.l,
252 region.r() - slice.r,
257 region.t() + slice.t,
258 region.b() - slice.b,
263 for(int ix = 0; ix < 3; ++ix)
264 for(int iy = 0; iy < 3; ++iy)
266 if( ix == 1 && iy == 1 && !fill )
267 // The ‘fill’ keyword, if present, causes the middle part of the
268 // image to be filled.
272 SGVec2f(x[ix ], y[iy ]),
273 SGVec2f(x[ix + 1], y[iy + 1]) );
278 _attributes_dirty &= ~DEST_SIZE;
282 if( _attributes_dirty & SRC_RECT )
284 SGRect<float> src_rect = _src_rect;
285 if( !_node_src_rect->getBoolValue("normalized", true) )
287 src_rect.t() /= tex_dim.height();
288 src_rect.r() /= tex_dim.width();
289 src_rect.b() /= tex_dim.height();
290 src_rect.l() /= tex_dim.width();
293 // Image coordinate systems y-axis is flipped
294 std::swap(src_rect.t(), src_rect.b());
296 if( !_slice.isValid() )
298 // Image scaling preserving aspect ratio. Change texture coordinates to
299 // scale image accordingly.
301 // TODO allow to specify what happens to not filled space (eg. color,
302 // or texture repeat/mirror)
304 // http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
305 if( !_preserve_aspect_ratio.scaleToFill() )
307 osg::BoundingBox const& bb = getBoundingBox();
308 float dst_width = bb._max.x() - bb._min.x(),
309 dst_height = bb._max.y() - bb._min.y();
310 float scale_x = dst_width / tex_dim.width(),
311 scale_y = dst_height / tex_dim.height();
313 float scale = _preserve_aspect_ratio.scaleToFit()
314 ? std::min(scale_x, scale_y)
315 : std::max(scale_x, scale_y);
317 if( scale_x != scale )
319 float d = scale_x / scale - 1;
320 if( _preserve_aspect_ratio.alignX()
321 == SVGpreserveAspectRatio::ALIGN_MIN )
325 else if( _preserve_aspect_ratio.alignX()
326 == SVGpreserveAspectRatio::ALIGN_MAX )
332 src_rect.l() -= d / 2;
333 src_rect.r() += d / 2;
337 if( scale_y != scale )
339 float d = scale_y / scale - 1;
340 if( _preserve_aspect_ratio.alignY()
341 == SVGpreserveAspectRatio::ALIGN_MIN )
345 else if( _preserve_aspect_ratio.alignY()
346 == SVGpreserveAspectRatio::ALIGN_MAX )
352 src_rect.t() += d / 2;
353 src_rect.b() -= d / 2;
358 setQuadUV(0, src_rect.getMin(), src_rect.getMax());
362 const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
365 src_rect.l() + slice.l,
366 src_rect.r() - slice.r,
371 src_rect.t() - slice.t,
372 src_rect.b() + slice.b,
377 for(int ix = 0; ix < 3; ++ix)
378 for(int iy = 0; iy < 3; ++iy)
380 if( ix == 1 && iy == 1 && !fill )
381 // The ‘fill’ keyword, if present, causes the middle part of the
382 // image to be filled.
386 SGVec2f(x[ix ], y[iy ]),
387 SGVec2f(x[ix + 1], y[iy + 1]) );
392 _attributes_dirty &= ~SRC_RECT;
396 //----------------------------------------------------------------------------
397 void Image::valueChanged(SGPropertyNode* child)
399 // If the image is switched from invisible to visible, and it shows a
400 // canvas, we need to delay showing it by one frame to ensure the canvas is
401 // updated before the image is displayed.
403 // As canvas::Element handles and filters changes to the "visible" property
404 // we can not check this in Image::childChanged but instead have to override
405 // Element::valueChanged.
407 && child->getParent() == _node
408 && child->getNameString() == "visible"
409 && child->getBoolValue() )
411 CullCallback* cb = static_cast<CullCallback*>(_geom->getCullCallback());
416 Element::valueChanged(child);
419 //----------------------------------------------------------------------------
420 void Image::setSrcCanvas(CanvasPtr canvas)
422 CanvasPtr src_canvas = _src_canvas.lock(),
423 self_canvas = _canvas.lock();
426 src_canvas->removeParentCanvas(self_canvas);
428 self_canvas->removeChildCanvas(src_canvas);
430 _src_canvas = src_canvas = canvas;
431 _attributes_dirty |= SRC_CANVAS;
432 _geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
436 setupDefaultDimensions();
440 self_canvas->addChildCanvas(src_canvas);
441 src_canvas->addParentCanvas(self_canvas);
446 //----------------------------------------------------------------------------
447 CanvasWeakPtr Image::getSrcCanvas() const
452 //----------------------------------------------------------------------------
453 void Image::setImage(osg::Image *img)
456 setSrcCanvas( CanvasPtr() );
458 _texture->setResizeNonPowerOfTwoHint(false);
459 _texture->setImage(img);
460 _texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
461 _texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
462 _geom->getOrCreateStateSet()
463 ->setTextureAttributeAndModes(0, _texture);
466 setupDefaultDimensions();
469 //----------------------------------------------------------------------------
470 void Image::setFill(const std::string& fill)
472 osg::Vec4 color(1,1,1,1);
473 if( !fill.empty() // If no color is given default to white
474 && !parseColor(fill, color) )
477 _colors->front() = color;
481 //----------------------------------------------------------------------------
482 void Image::setOutset(const std::string& outset)
484 _outset = CSSBorder::parse(outset);
485 _attributes_dirty |= DEST_SIZE;
488 //----------------------------------------------------------------------------
489 void Image::setPreserveAspectRatio(const std::string& scale)
491 _preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
492 _attributes_dirty |= SRC_RECT;
495 //----------------------------------------------------------------------------
496 void Image::setSlice(const std::string& slice)
498 _slice = CSSBorder::parse(slice);
499 _attributes_dirty |= SRC_RECT | DEST_SIZE;
502 //----------------------------------------------------------------------------
503 void Image::setSliceWidth(const std::string& width)
505 _slice_width = CSSBorder::parse(width);
506 _attributes_dirty |= DEST_SIZE;
509 //----------------------------------------------------------------------------
510 const SGRect<float>& Image::getRegion() const
515 //----------------------------------------------------------------------------
516 bool Image::handleEvent(const EventPtr& event)
518 bool handled = Element::handleEvent(event);
520 CanvasPtr src_canvas = _src_canvas.lock();
524 MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get());
527 mouse_event.reset( new MouseEvent(*mouse_event) );
529 mouse_event->client_pos = mouse_event->local_pos
530 - toOsg(_region.getMin());
532 osg::Vec2f size(_region.width(), _region.height());
533 if( _outset.isValid() )
535 CSSBorder::Offsets outset =
536 _outset.getAbsOffsets(getTextureDimensions());
538 mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
539 size.x() += outset.l + outset.r;
540 size.y() += outset.t + outset.b;
543 // Scale event pos according to canvas view size vs. displayed/screen size
544 mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
545 mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
546 mouse_event->local_pos = mouse_event->client_pos;
548 handled |= src_canvas->handleMouseEvent(mouse_event);
554 //----------------------------------------------------------------------------
555 void Image::childChanged(SGPropertyNode* child)
557 const std::string& name = child->getNameString();
559 if( child->getParent() == _node_src_rect )
561 _attributes_dirty |= SRC_RECT;
564 _src_rect.setLeft( child->getFloatValue() );
565 else if( name == "right" )
566 _src_rect.setRight( child->getFloatValue() );
567 else if( name == "top" )
568 _src_rect.setTop( child->getFloatValue() );
569 else if( name == "bottom" )
570 _src_rect.setBottom( child->getFloatValue() );
574 else if( child->getParent() != _node )
579 _region.setX( child->getFloatValue() );
580 _attributes_dirty |= DEST_SIZE;
582 else if( name == "y" )
584 _region.setY( child->getFloatValue() );
585 _attributes_dirty |= DEST_SIZE;
587 else if( name == "size" )
589 if( child->getIndex() == 0 )
590 _region.setWidth( child->getFloatValue() );
592 _region.setHeight( child->getFloatValue() );
594 _attributes_dirty |= DEST_SIZE;
596 else if( name == "src" || name == "file" )
599 SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
601 // Abort pending request
604 _http_request->abort("setting new image");
605 _http_request.reset();
608 static const std::string PROTOCOL_SEP = "://";
610 std::string url = child->getStringValue(),
613 size_t sep_pos = url.find(PROTOCOL_SEP);
614 if( sep_pos != std::string::npos )
616 protocol = url.substr(0, sep_pos);
617 path = url.substr(sep_pos + PROTOCOL_SEP.length());
622 if( protocol == "canvas" )
624 CanvasPtr canvas = _canvas.lock();
627 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
631 CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
634 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
638 const SGPropertyNode* canvas_node =
639 canvas_mgr->getPropertyRoot()
644 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
648 // TODO add support for other means of addressing canvases (eg. by
650 CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
653 SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
657 setSrcCanvas(src_canvas);
659 else if( protocol == "http" || protocol == "https" )
663 Canvas::getSystemAdapter()
666 // TODO handle capture of 'this'
667 ->done(this, &Image::handleImageLoadDone);
671 setImage( Canvas::getSystemAdapter()->getImage(path) );
676 //----------------------------------------------------------------------------
677 void Image::setupDefaultDimensions()
679 if( !_src_rect.width() || !_src_rect.height() )
681 // Show whole image by default
682 _node_src_rect->setBoolValue("normalized", true);
683 _node_src_rect->setFloatValue("right", 1);
684 _node_src_rect->setFloatValue("bottom", 1);
687 if( !_region.width() || !_region.height() )
689 // Default to image size.
690 // TODO handle showing only part of image?
691 const SGRect<int>& dim = getTextureDimensions();
692 _node->setFloatValue("size[0]", dim.width());
693 _node->setFloatValue("size[1]", dim.height());
697 //----------------------------------------------------------------------------
698 SGRect<int> Image::getTextureDimensions() const
700 CanvasPtr canvas = _src_canvas.lock();
701 SGRect<int> dim(0,0);
703 // Use canvas/image dimensions rather than texture dimensions, as they could
704 // be resized internally by OpenSceneGraph (eg. to nearest power of two).
707 dim.setRight( canvas->getViewWidth() );
708 dim.setBottom( canvas->getViewHeight() );
712 osg::Image* img = _texture->getImage();
716 dim.setRight( img->s() );
717 dim.setBottom( img->t() );
724 //----------------------------------------------------------------------------
725 void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
728 (*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
729 (*_vertices)[i + 1].set(br.x(), tl.y(), 0);
730 (*_vertices)[i + 2].set(br.x(), br.y(), 0);
731 (*_vertices)[i + 3].set(tl.x(), br.y(), 0);
734 //----------------------------------------------------------------------------
735 void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
738 (*_texCoords)[i + 0].set(tl.x(), tl.y());
739 (*_texCoords)[i + 1].set(br.x(), tl.y());
740 (*_texCoords)[i + 2].set(br.x(), br.y());
741 (*_texCoords)[i + 3].set(tl.x(), br.y());
744 //----------------------------------------------------------------------------
745 void Image::handleImageLoadDone(HTTP::Request* req)
747 // Ignore stale/expired requests
748 if( _http_request != req )
750 _http_request.reset();
752 if( req->responseCode() != 200 )
754 SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
755 << req->responseReason());
759 const std::string ext = SGPath(req->path()).extension(),
760 mime = req->responseMime();
762 SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
763 " (ext=" << ext << ", MIME=" << mime << ")");
765 const std::string& img_data =
766 static_cast<HTTP::MemoryRequest*>(req)->responseBody();
767 osgDB::Registry* reg = osgDB::Registry::instance();
769 // First try to detect image type by extension
770 osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
771 if( rw && loadImage(*rw, img_data, *req, "extension") )
774 // Now try with MIME type
775 rw = reg->getReaderWriterForMimeType(mime);
776 if( rw && loadImage(*rw, img_data, *req, "MIME type") )
779 SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
782 //----------------------------------------------------------------------------
783 bool Image::loadImage( osgDB::ReaderWriter& reader,
784 const std::string& data,
785 HTTP::Request& request,
786 const std::string& type )
788 SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
790 std::istringstream data_strm(data);
791 osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
792 if( result.success() )
794 setImage( result.takeImage() );
798 SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
799 << result.message());
804 } // namespace canvas
805 } // namespace simgear