843 lines
25 KiB
C++
843 lines
25 KiB
C++
///@file
|
||
/// An image on the Canvas
|
||
//
|
||
// Copyright (C) 2012 Thomas Geymayer <tomgey@gmail.com>
|
||
//
|
||
// This library is free software; you can redistribute it and/or
|
||
// modify it under the terms of the GNU Library General Public
|
||
// License as published by the Free Software Foundation; either
|
||
// version 2 of the License, or (at your option) any later version.
|
||
//
|
||
// This library is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
// Library General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU Library General Public
|
||
// License along with this library; if not, write to the Free Software
|
||
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||
|
||
#include <simgear_config.h>
|
||
|
||
#include "CanvasImage.hxx"
|
||
|
||
#include <simgear/canvas/Canvas.hxx>
|
||
#include <simgear/canvas/CanvasMgr.hxx>
|
||
#include <simgear/canvas/CanvasSystemAdapter.hxx>
|
||
#include <simgear/canvas/events/KeyboardEvent.hxx>
|
||
#include <simgear/canvas/events/MouseEvent.hxx>
|
||
#include <simgear/scene/util/OsgMath.hxx>
|
||
#include <simgear/scene/util/parse_color.hxx>
|
||
#include <simgear/misc/sg_path.hxx>
|
||
|
||
#include <osg/Array>
|
||
#include <osg/Geometry>
|
||
#include <osg/PrimitiveSet>
|
||
#include <osgDB/Registry>
|
||
#include <osg/Version>
|
||
|
||
namespace simgear
|
||
{
|
||
namespace canvas
|
||
{
|
||
/**
|
||
* Callback to enable/disable rendering of canvas displayed inside windows or
|
||
* other canvases.
|
||
*/
|
||
class CullCallback:
|
||
public osg::Drawable::CullCallback
|
||
{
|
||
public:
|
||
CullCallback(const CanvasWeakPtr& canvas);
|
||
void cullNextFrame();
|
||
|
||
private:
|
||
CanvasWeakPtr _canvas;
|
||
mutable bool _cull_next_frame;
|
||
|
||
virtual bool cull( osg::NodeVisitor* nv,
|
||
osg::Drawable* drawable,
|
||
osg::RenderInfo* renderInfo ) const;
|
||
};
|
||
|
||
//----------------------------------------------------------------------------
|
||
CullCallback::CullCallback(const CanvasWeakPtr& canvas):
|
||
_canvas( canvas ),
|
||
_cull_next_frame( false )
|
||
{
|
||
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void CullCallback::cullNextFrame()
|
||
{
|
||
_cull_next_frame = true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool CullCallback::cull( osg::NodeVisitor* nv,
|
||
osg::Drawable* drawable,
|
||
osg::RenderInfo* renderInfo ) const
|
||
{
|
||
CanvasPtr canvas = _canvas.lock();
|
||
if( canvas )
|
||
canvas->enableRendering();
|
||
|
||
if( !_cull_next_frame )
|
||
// TODO check if window/image should be culled
|
||
return false;
|
||
|
||
_cull_next_frame = false;
|
||
return true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
const std::string Image::TYPE_NAME = "image";
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::staticInit()
|
||
{
|
||
if( isInit<Image>() )
|
||
return;
|
||
|
||
addStyle("fill", "color", &Image::setFill);
|
||
addStyle("outset", "", &Image::setOutset);
|
||
addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
|
||
addStyle("slice", "", &Image::setSlice);
|
||
addStyle("slice-width", "", &Image::setSliceWidth);
|
||
|
||
osgDB::Registry* reg = osgDB::Registry::instance();
|
||
if( !reg->getReaderWriterForExtension("png") )
|
||
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Missing 'png' image reader");
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
Image::Image( const CanvasWeakPtr& canvas,
|
||
const SGPropertyNode_ptr& node,
|
||
const Style& parent_style,
|
||
ElementWeakPtr parent ):
|
||
Element(canvas, node, parent_style, parent),
|
||
_texture(new osg::Texture2D),
|
||
_node_src_rect( node->getNode("source", 0, true) )
|
||
{
|
||
staticInit();
|
||
|
||
_geom = new osg::Geometry;
|
||
_geom->setUseDisplayList(false);
|
||
|
||
osg::StateSet *stateSet = _geom->getOrCreateStateSet();
|
||
stateSet->setTextureAttributeAndModes(0, _texture.get());
|
||
stateSet->setDataVariance(osg::Object::STATIC);
|
||
|
||
// allocate arrays for the image
|
||
_vertices = new osg::Vec3Array(4);
|
||
_vertices->setDataVariance(osg::Object::DYNAMIC);
|
||
_geom->setVertexArray(_vertices);
|
||
|
||
_texCoords = new osg::Vec2Array(4);
|
||
_texCoords->setDataVariance(osg::Object::DYNAMIC);
|
||
_geom->setTexCoordArray(0, _texCoords, osg::Array::BIND_PER_VERTEX);
|
||
|
||
_colors = new osg::Vec4Array(1);
|
||
_colors->setDataVariance(osg::Object::DYNAMIC);
|
||
_geom->setColorArray(_colors, osg::Array::BIND_OVERALL);
|
||
|
||
_prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
|
||
_prim->set(osg::PrimitiveSet::QUADS, 0, 4);
|
||
_prim->setDataVariance(osg::Object::DYNAMIC);
|
||
_geom->addPrimitiveSet(_prim);
|
||
|
||
setDrawable(_geom);
|
||
|
||
setFill("#ffffff"); // TODO how should we handle default values?
|
||
setupStyle();
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
Image::~Image()
|
||
{
|
||
if( _http_request )
|
||
{
|
||
Canvas::getSystemAdapter()
|
||
->getHTTPClient()
|
||
->cancelRequest(_http_request, "image destroyed");
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::valueChanged(SGPropertyNode* child)
|
||
{
|
||
// If the image is switched from invisible to visible, and it shows a
|
||
// canvas, we need to delay showing it by one frame to ensure the canvas is
|
||
// updated before the image is displayed.
|
||
//
|
||
// As canvas::Element handles and filters changes to the "visible" property
|
||
// we can not check this in Image::childChanged but instead have to override
|
||
// Element::valueChanged.
|
||
if( !isVisible()
|
||
&& child->getParent() == _node
|
||
&& child->getNameString() == "visible"
|
||
&& child->getBoolValue() )
|
||
{
|
||
CullCallback* cb =
|
||
#if OSG_VERSION_LESS_THAN(3,3,2)
|
||
static_cast<CullCallback*>
|
||
#else
|
||
dynamic_cast<CullCallback*>
|
||
#endif
|
||
( _geom->getCullCallback() );
|
||
|
||
if( cb )
|
||
cb->cullNextFrame();
|
||
}
|
||
|
||
Element::valueChanged(child);
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setSrcCanvas(CanvasPtr canvas)
|
||
{
|
||
CanvasPtr src_canvas = _src_canvas.lock(),
|
||
self_canvas = _canvas.lock();
|
||
|
||
if( src_canvas )
|
||
src_canvas->removeParentCanvas(self_canvas);
|
||
if( self_canvas )
|
||
self_canvas->removeChildCanvas(src_canvas);
|
||
|
||
_src_canvas = src_canvas = canvas;
|
||
_attributes_dirty |= SRC_CANVAS;
|
||
_geom->setCullCallback(canvas ? new CullCallback(canvas) : 0);
|
||
|
||
if( src_canvas )
|
||
{
|
||
setupDefaultDimensions();
|
||
|
||
if( self_canvas )
|
||
{
|
||
self_canvas->addChildCanvas(src_canvas);
|
||
src_canvas->addParentCanvas(self_canvas);
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
CanvasWeakPtr Image::getSrcCanvas() const
|
||
{
|
||
return _src_canvas;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setImage(osg::ref_ptr<osg::Image> img)
|
||
{
|
||
// remove canvas...
|
||
setSrcCanvas( CanvasPtr() );
|
||
|
||
_texture->setResizeNonPowerOfTwoHint(false);
|
||
_texture->setImage(img);
|
||
_texture->setWrap(osg::Texture::WRAP_S, osg::Texture::REPEAT);
|
||
_texture->setWrap(osg::Texture::WRAP_T, osg::Texture::REPEAT);
|
||
_geom->getOrCreateStateSet()
|
||
->setTextureAttributeAndModes(0, _texture);
|
||
|
||
if( img )
|
||
setupDefaultDimensions();
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setFill(const std::string& fill)
|
||
{
|
||
osg::Vec4 color(1,1,1,1);
|
||
if( !fill.empty() // If no color is given default to white
|
||
&& !parseColor(fill, color) )
|
||
return;
|
||
setFill(color);
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setFill(const osg::Vec4& color)
|
||
{
|
||
_colors->front() = color;
|
||
_colors->dirty();
|
||
}
|
||
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setOutset(const std::string& outset)
|
||
{
|
||
_outset = CSSBorder::parse(outset);
|
||
_attributes_dirty |= DEST_SIZE;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setPreserveAspectRatio(const std::string& scale)
|
||
{
|
||
_preserve_aspect_ratio = SVGpreserveAspectRatio::parse(scale);
|
||
_attributes_dirty |= SRC_RECT;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setSourceRect(const SGRect<float>& sourceRect)
|
||
{
|
||
_attributes_dirty |= SRC_RECT;
|
||
_src_rect = sourceRect;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setSlice(const std::string& slice)
|
||
{
|
||
_slice = CSSBorder::parse(slice);
|
||
_attributes_dirty |= SRC_RECT | DEST_SIZE;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setSliceWidth(const std::string& width)
|
||
{
|
||
_slice_width = CSSBorder::parse(width);
|
||
_attributes_dirty |= DEST_SIZE;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
const SGRect<float>& Image::getRegion() const
|
||
{
|
||
return _region;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool Image::handleEvent(const EventPtr& event)
|
||
{
|
||
bool handled = Element::handleEvent(event);
|
||
|
||
CanvasPtr src_canvas = _src_canvas.lock();
|
||
if( !src_canvas )
|
||
return handled;
|
||
|
||
if( MouseEventPtr mouse_event = dynamic_cast<MouseEvent*>(event.get()) )
|
||
{
|
||
mouse_event.reset( new MouseEvent(*mouse_event) );
|
||
|
||
mouse_event->client_pos = mouse_event->local_pos
|
||
- toOsg(_region.getMin());
|
||
|
||
osg::Vec2f size(_region.width(), _region.height());
|
||
if( _outset.isValid() )
|
||
{
|
||
CSSBorder::Offsets outset =
|
||
_outset.getAbsOffsets(getTextureDimensions());
|
||
|
||
mouse_event->client_pos += osg::Vec2f(outset.l, outset.t);
|
||
size.x() += outset.l + outset.r;
|
||
size.y() += outset.t + outset.b;
|
||
}
|
||
|
||
// Scale event pos according to canvas view size vs. displayed/screen size
|
||
mouse_event->client_pos.x() *= src_canvas->getViewWidth() / size.x();
|
||
mouse_event->client_pos.y() *= src_canvas->getViewHeight()/ size.y();
|
||
mouse_event->local_pos = mouse_event->client_pos;
|
||
|
||
handled |= src_canvas->handleMouseEvent(mouse_event);
|
||
}
|
||
else if( KeyboardEventPtr keyboard_event =
|
||
dynamic_cast<KeyboardEvent*>(event.get()) )
|
||
{
|
||
handled |= src_canvas->handleKeyboardEvent(keyboard_event);
|
||
}
|
||
|
||
return handled;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::updateImpl(double dt)
|
||
{
|
||
Element::updateImpl(dt);
|
||
|
||
osg::Texture2D* texture = dynamic_cast<osg::Texture2D*>
|
||
(
|
||
_geom->getOrCreateStateSet()
|
||
->getTextureAttribute(0, osg::StateAttribute::TEXTURE)
|
||
);
|
||
auto canvas = _src_canvas.lock();
|
||
|
||
if( (_attributes_dirty & SRC_CANVAS)
|
||
// check if texture has changed (eg. due to resizing)
|
||
|| (canvas && texture != canvas->getTexture()) )
|
||
{
|
||
_geom->getOrCreateStateSet()
|
||
->setTextureAttribute(0, canvas ? canvas->getTexture() : 0);
|
||
|
||
if( !canvas || canvas->isInit() )
|
||
_attributes_dirty &= ~SRC_CANVAS;
|
||
}
|
||
|
||
if( !_attributes_dirty )
|
||
return;
|
||
|
||
const SGRect<int>& tex_dim = getTextureDimensions();
|
||
|
||
// http://www.w3.org/TR/css3-background/#border-image-slice
|
||
|
||
// The ‘fill’ keyword, if present, causes the middle part of the image to be
|
||
// preserved. (By default it is discarded, i.e., treated as empty.)
|
||
bool fill = (_slice.getKeyword() == "fill");
|
||
|
||
if( _attributes_dirty & DEST_SIZE )
|
||
{
|
||
size_t num_vertices = (_slice.isValid() ? (fill ? 9 : 8) : 1) * 4;
|
||
|
||
if( num_vertices != _prim->getNumPrimitives() )
|
||
{
|
||
_vertices->resize(num_vertices);
|
||
_texCoords->resize(num_vertices);
|
||
_prim->setCount(num_vertices);
|
||
_prim->dirty();
|
||
|
||
_attributes_dirty |= SRC_RECT;
|
||
}
|
||
|
||
// http://www.w3.org/TR/css3-background/#border-image-outset
|
||
SGRect<float> region = _region;
|
||
if( _outset.isValid() )
|
||
{
|
||
const CSSBorder::Offsets& outset = _outset.getAbsOffsets(tex_dim);
|
||
region.t() -= outset.t;
|
||
region.r() += outset.r;
|
||
region.b() += outset.b;
|
||
region.l() -= outset.l;
|
||
}
|
||
|
||
if( !_slice.isValid() )
|
||
{
|
||
setQuad(0, region.getMin(), region.getMax());
|
||
|
||
if( !_preserve_aspect_ratio.scaleToFill() )
|
||
// We need to update texture coordinates to keep the aspect ratio
|
||
_attributes_dirty |= SRC_RECT;
|
||
}
|
||
else
|
||
{
|
||
/*
|
||
Image slice, 9-scale, whatever it is called. The four corner images
|
||
stay unscaled (tl, tr, bl, br) whereas the other parts are scaled to
|
||
fill the remaining space up to the specified size.
|
||
|
||
x[0] x[1] x[2] x[3]
|
||
| | | |
|
||
-------------------- - y[0]
|
||
| tl | top | tr |
|
||
-------------------- - y[1]
|
||
| | | |
|
||
| l | | r |
|
||
| e | center | i |
|
||
| f | | g |
|
||
| t | | h |
|
||
| | | t |
|
||
-------------------- - y[2]
|
||
| bl | bottom | br |
|
||
-------------------- - y[3]
|
||
*/
|
||
|
||
const CSSBorder::Offsets& slice =
|
||
(_slice_width.isValid() ? _slice_width : _slice)
|
||
.getAbsOffsets(tex_dim);
|
||
float x[4] = {
|
||
region.l(),
|
||
region.l() + slice.l,
|
||
region.r() - slice.r,
|
||
region.r()
|
||
};
|
||
float y[4] = {
|
||
region.t(),
|
||
region.t() + slice.t,
|
||
region.b() - slice.b,
|
||
region.b()
|
||
};
|
||
|
||
int i = 0;
|
||
for(int ix = 0; ix < 3; ++ix)
|
||
for(int iy = 0; iy < 3; ++iy)
|
||
{
|
||
if( ix == 1 && iy == 1 && !fill )
|
||
// The ‘fill’ keyword, if present, causes the middle part of the
|
||
// image to be filled.
|
||
continue;
|
||
|
||
setQuad( i++,
|
||
SGVec2f(x[ix ], y[iy ]),
|
||
SGVec2f(x[ix + 1], y[iy + 1]) );
|
||
}
|
||
}
|
||
|
||
_vertices->dirty();
|
||
_attributes_dirty &= ~DEST_SIZE;
|
||
_geom->dirtyBound();
|
||
}
|
||
|
||
if( _attributes_dirty & SRC_RECT )
|
||
{
|
||
SGRect<float> src_rect = _src_rect;
|
||
if( !_node_src_rect->getBoolValue("normalized", true) )
|
||
{
|
||
src_rect.t() /= tex_dim.height();
|
||
src_rect.r() /= tex_dim.width();
|
||
src_rect.b() /= tex_dim.height();
|
||
src_rect.l() /= tex_dim.width();
|
||
}
|
||
|
||
// Image coordinate systems y-axis is flipped
|
||
std::swap(src_rect.t(), src_rect.b());
|
||
|
||
if( !_slice.isValid() )
|
||
{
|
||
// Image scaling preserving aspect ratio. Change texture coordinates to
|
||
// scale image accordingly.
|
||
//
|
||
// TODO allow to specify what happens to not filled space (eg. color,
|
||
// or texture repeat/mirror)
|
||
//
|
||
// http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
|
||
if( !_preserve_aspect_ratio.scaleToFill() )
|
||
{
|
||
osg::BoundingBox const& bb = getBoundingBox();
|
||
float dst_width = bb._max.x() - bb._min.x(),
|
||
dst_height = bb._max.y() - bb._min.y();
|
||
float scale_x = dst_width / tex_dim.width(),
|
||
scale_y = dst_height / tex_dim.height();
|
||
|
||
float scale = _preserve_aspect_ratio.scaleToFit()
|
||
? std::min(scale_x, scale_y)
|
||
: std::max(scale_x, scale_y);
|
||
|
||
if( scale_x != scale )
|
||
{
|
||
float d = scale_x / scale - 1;
|
||
if( _preserve_aspect_ratio.alignX()
|
||
== SVGpreserveAspectRatio::ALIGN_MIN )
|
||
{
|
||
src_rect.r() += d;
|
||
}
|
||
else if( _preserve_aspect_ratio.alignX()
|
||
== SVGpreserveAspectRatio::ALIGN_MAX )
|
||
{
|
||
src_rect.l() -= d;
|
||
}
|
||
else
|
||
{
|
||
src_rect.l() -= d / 2;
|
||
src_rect.r() += d / 2;
|
||
}
|
||
}
|
||
|
||
if( scale_y != scale )
|
||
{
|
||
float d = scale_y / scale - 1;
|
||
if( _preserve_aspect_ratio.alignY()
|
||
== SVGpreserveAspectRatio::ALIGN_MIN )
|
||
{
|
||
src_rect.b() -= d;
|
||
}
|
||
else if( _preserve_aspect_ratio.alignY()
|
||
== SVGpreserveAspectRatio::ALIGN_MAX )
|
||
{
|
||
src_rect.t() += d;
|
||
}
|
||
else
|
||
{
|
||
src_rect.t() += d / 2;
|
||
src_rect.b() -= d / 2;
|
||
}
|
||
}
|
||
}
|
||
|
||
setQuadUV(0, src_rect.getMin(), src_rect.getMax());
|
||
}
|
||
else
|
||
{
|
||
const CSSBorder::Offsets& slice = _slice.getRelOffsets(tex_dim);
|
||
float x[4] = {
|
||
src_rect.l(),
|
||
src_rect.l() + slice.l,
|
||
src_rect.r() - slice.r,
|
||
src_rect.r()
|
||
};
|
||
float y[4] = {
|
||
src_rect.t(),
|
||
src_rect.t() - slice.t,
|
||
src_rect.b() + slice.b,
|
||
src_rect.b()
|
||
};
|
||
|
||
int i = 0;
|
||
for(int ix = 0; ix < 3; ++ix)
|
||
for(int iy = 0; iy < 3; ++iy)
|
||
{
|
||
if( ix == 1 && iy == 1 && !fill )
|
||
// The ‘fill’ keyword, if present, causes the middle part of the
|
||
// image to be filled.
|
||
continue;
|
||
|
||
setQuadUV( i++,
|
||
SGVec2f(x[ix ], y[iy ]),
|
||
SGVec2f(x[ix + 1], y[iy + 1]) );
|
||
}
|
||
}
|
||
|
||
_texCoords->dirty();
|
||
_attributes_dirty &= ~SRC_RECT;
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::childChanged(SGPropertyNode* child)
|
||
{
|
||
const std::string& name = child->getNameString();
|
||
|
||
if( child->getParent() == _node_src_rect )
|
||
{
|
||
_attributes_dirty |= SRC_RECT;
|
||
|
||
if( name == "left" )
|
||
_src_rect.setLeft( child->getFloatValue() );
|
||
else if( name == "right" )
|
||
_src_rect.setRight( child->getFloatValue() );
|
||
else if( name == "top" )
|
||
_src_rect.setTop( child->getFloatValue() );
|
||
else if( name == "bottom" )
|
||
_src_rect.setBottom( child->getFloatValue() );
|
||
|
||
return;
|
||
}
|
||
else if( child->getParent() != _node )
|
||
return;
|
||
|
||
if( name == "x" )
|
||
{
|
||
_region.setX( child->getFloatValue() );
|
||
_attributes_dirty |= DEST_SIZE;
|
||
}
|
||
else if( name == "y" )
|
||
{
|
||
_region.setY( child->getFloatValue() );
|
||
_attributes_dirty |= DEST_SIZE;
|
||
}
|
||
else if( name == "size" )
|
||
{
|
||
if( child->getIndex() == 0 )
|
||
_region.setWidth( child->getFloatValue() );
|
||
else
|
||
_region.setHeight( child->getFloatValue() );
|
||
|
||
_attributes_dirty |= DEST_SIZE;
|
||
}
|
||
else if( name == "src" || name == "file" )
|
||
{
|
||
if( name == "file" )
|
||
SG_LOG(SG_GL, SG_WARN, "'file' is deprecated. Use 'src' instead");
|
||
|
||
// Abort pending request
|
||
if( _http_request )
|
||
{
|
||
Canvas::getSystemAdapter()
|
||
->getHTTPClient()
|
||
->cancelRequest(_http_request, "setting new image");
|
||
_http_request.reset();
|
||
}
|
||
|
||
static const std::string PROTOCOL_SEP = "://";
|
||
|
||
std::string url = child->getStringValue(),
|
||
protocol, path;
|
||
|
||
size_t sep_pos = url.find(PROTOCOL_SEP);
|
||
if( sep_pos != std::string::npos )
|
||
{
|
||
protocol = url.substr(0, sep_pos);
|
||
path = url.substr(sep_pos + PROTOCOL_SEP.length());
|
||
}
|
||
else
|
||
path = url;
|
||
|
||
if( protocol == "canvas" )
|
||
{
|
||
CanvasPtr canvas = _canvas.lock();
|
||
if( !canvas )
|
||
{
|
||
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No canvas available");
|
||
return;
|
||
}
|
||
|
||
CanvasMgr* canvas_mgr = canvas->getCanvasMgr();
|
||
if( !canvas_mgr )
|
||
{
|
||
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Failed to get CanvasMgr");
|
||
return;
|
||
}
|
||
|
||
const SGPropertyNode* canvas_node =
|
||
canvas_mgr->getPropertyRoot()
|
||
->getParent()
|
||
->getNode( path );
|
||
if( !canvas_node )
|
||
{
|
||
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: No such canvas: " << path);
|
||
return;
|
||
}
|
||
|
||
// TODO add support for other means of addressing canvases (eg. by
|
||
// name)
|
||
CanvasPtr src_canvas = canvas_mgr->getCanvas( canvas_node->getIndex() );
|
||
if( !src_canvas )
|
||
{
|
||
SG_LOG(SG_GL, SG_ALERT, "canvas::Image: Invalid canvas: " << path);
|
||
return;
|
||
}
|
||
|
||
setSrcCanvas(src_canvas);
|
||
}
|
||
else if( protocol == "http" || protocol == "https" )
|
||
// TODO check https
|
||
{
|
||
_http_request =
|
||
Canvas::getSystemAdapter()
|
||
->getHTTPClient()
|
||
->load(url)
|
||
// TODO handle capture of 'this'
|
||
->done(this, &Image::handleImageLoadDone);
|
||
}
|
||
else
|
||
{
|
||
setImage( Canvas::getSystemAdapter()->getImage(path) );
|
||
}
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setupDefaultDimensions()
|
||
{
|
||
if( !_src_rect.width() || !_src_rect.height() )
|
||
{
|
||
// Show whole image by default
|
||
_node_src_rect->setBoolValue("normalized", true);
|
||
_node_src_rect->setFloatValue("right", 1);
|
||
_node_src_rect->setFloatValue("bottom", 1);
|
||
}
|
||
|
||
if( !_region.width() || !_region.height() )
|
||
{
|
||
// Default to image size.
|
||
// TODO handle showing only part of image?
|
||
const SGRect<int>& dim = getTextureDimensions();
|
||
_node->setFloatValue("size[0]", dim.width());
|
||
_node->setFloatValue("size[1]", dim.height());
|
||
}
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
SGRect<int> Image::getTextureDimensions() const
|
||
{
|
||
CanvasPtr canvas = _src_canvas.lock();
|
||
SGRect<int> dim(0,0);
|
||
|
||
// Use canvas/image dimensions rather than texture dimensions, as they could
|
||
// be resized internally by OpenSceneGraph (eg. to nearest power of two).
|
||
if( canvas )
|
||
{
|
||
dim.setRight( canvas->getViewWidth() );
|
||
dim.setBottom( canvas->getViewHeight() );
|
||
}
|
||
else if( _texture )
|
||
{
|
||
osg::Image* img = _texture->getImage();
|
||
|
||
if( img )
|
||
{
|
||
dim.setRight( img->s() );
|
||
dim.setBottom( img->t() );
|
||
}
|
||
}
|
||
|
||
return dim;
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br)
|
||
{
|
||
int i = index * 4;
|
||
(*_vertices)[i + 0].set(tl.x(), tl.y(), 0);
|
||
(*_vertices)[i + 1].set(br.x(), tl.y(), 0);
|
||
(*_vertices)[i + 2].set(br.x(), br.y(), 0);
|
||
(*_vertices)[i + 3].set(tl.x(), br.y(), 0);
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br)
|
||
{
|
||
int i = index * 4;
|
||
(*_texCoords)[i + 0].set(tl.x(), tl.y());
|
||
(*_texCoords)[i + 1].set(br.x(), tl.y());
|
||
(*_texCoords)[i + 2].set(br.x(), br.y());
|
||
(*_texCoords)[i + 3].set(tl.x(), br.y());
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
void Image::handleImageLoadDone(HTTP::Request* req)
|
||
{
|
||
// Ignore stale/expired requests
|
||
if( _http_request != req )
|
||
return;
|
||
_http_request.reset();
|
||
|
||
if( req->responseCode() != 200 )
|
||
{
|
||
SG_LOG(SG_IO, SG_WARN, "failed to download '" << req->url() << "': "
|
||
<< req->responseReason());
|
||
return;
|
||
}
|
||
|
||
const std::string ext = SGPath(req->path()).extension(),
|
||
mime = req->responseMime();
|
||
|
||
SG_LOG(SG_IO, SG_INFO, "received " << req->url() <<
|
||
" (ext=" << ext << ", MIME=" << mime << ")");
|
||
|
||
const std::string& img_data =
|
||
static_cast<HTTP::MemoryRequest*>(req)->responseBody();
|
||
osgDB::Registry* reg = osgDB::Registry::instance();
|
||
|
||
// First try to detect image type by extension
|
||
osgDB::ReaderWriter* rw = reg->getReaderWriterForExtension(ext);
|
||
if( rw && loadImage(*rw, img_data, *req, "extension") )
|
||
return;
|
||
|
||
// Now try with MIME type
|
||
rw = reg->getReaderWriterForMimeType(mime);
|
||
if( rw && loadImage(*rw, img_data, *req, "MIME type") )
|
||
return;
|
||
|
||
SG_LOG(SG_IO, SG_WARN, "unable to read image '" << req->url() << "'");
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
bool Image::loadImage( osgDB::ReaderWriter& reader,
|
||
const std::string& data,
|
||
HTTP::Request& request,
|
||
const std::string& type )
|
||
{
|
||
SG_LOG(SG_IO, SG_DEBUG, "use image reader detected by " << type);
|
||
|
||
std::istringstream data_strm(data);
|
||
osgDB::ReaderWriter::ReadResult result = reader.readImage(data_strm);
|
||
if( result.success() )
|
||
{
|
||
setImage( result.takeImage() );
|
||
return true;
|
||
}
|
||
|
||
SG_LOG(SG_IO, SG_WARN, "failed to read image '" << request.url() << "': "
|
||
<< result.message());
|
||
|
||
return false;
|
||
}
|
||
|
||
} // namespace canvas
|
||
} // namespace simgear
|