Support image slice/scale-9 for CanvasImage.

This commit is contained in:
Thomas Geymayer
2013-02-24 17:39:45 +01:00
parent 06bfa4f1a1
commit 5441db9b18
3 changed files with 294 additions and 32 deletions

View File

@@ -29,6 +29,9 @@
#include <osg/PrimitiveSet>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/range.hpp>
#include <boost/tokenizer.hpp>
namespace simgear
{
@@ -91,26 +94,29 @@ namespace canvas
// allocate arrays for the image
_vertices = new osg::Vec3Array(4);
_vertices->setDataVariance(osg::Object::STATIC);
_vertices->setDataVariance(osg::Object::DYNAMIC);
_geom->setVertexArray(_vertices);
_texCoords = new osg::Vec2Array(4);
_texCoords->setDataVariance(osg::Object::STATIC);
_texCoords->setDataVariance(osg::Object::DYNAMIC);
_geom->setTexCoordArray(0, _texCoords);
_colors = new osg::Vec4Array(4);
_colors->setDataVariance(osg::Object::STATIC);
_geom->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
_colors = new osg::Vec4Array(1);
_colors->setDataVariance(osg::Object::DYNAMIC);
_geom->setColorBinding(osg::Geometry::BIND_OVERALL);
_geom->setColorArray(_colors);
osg::DrawArrays* prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
prim->set(osg::PrimitiveSet::QUADS, 0, 4);
prim->setDataVariance(osg::Object::STATIC);
_geom->addPrimitiveSet(prim);
_prim = new osg::DrawArrays(osg::PrimitiveSet::QUADS);
_prim->set(osg::PrimitiveSet::QUADS, 0, 4);
_prim->setDataVariance(osg::Object::DYNAMIC);
_geom->addPrimitiveSet(_prim);
setDrawable(_geom);
addStyle("fill", &Image::setFill, this);
addStyle("slice", &Image::setSlice, this);
addStyle("outset", &Image::setOutset, this);
setFill("#ffffff"); // TODO how should we handle default values?
setupStyle();
@@ -127,14 +133,97 @@ namespace canvas
{
Element::update(dt);
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.keyword == "fill");
if( _attributes_dirty & DEST_SIZE )
{
(*_vertices)[0].set(_region.l(), _region.t(), 0);
(*_vertices)[1].set(_region.r(), _region.t(), 0);
(*_vertices)[2].set(_region.r(), _region.b(), 0);
(*_vertices)[3].set(_region.l(), _region.b(), 0);
_vertices->dirty();
size_t num_vertices = (_slice.valid ? (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.valid )
{
region.t() -= _outset.t;
region.r() += _outset.r;
region.b() += _outset.b;
region.l() -= _outset.l;
}
if( !_slice.valid )
{
setQuad(0, region.getMin(), region.getMax());
}
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]
*/
float x[4] = {
region.l(),
region.l() + _slice.l * tex_dim.width(),
region.r() - _slice.r * tex_dim.width(),
region.r()
};
float y[4] = {
region.t(),
region.t() + _slice.t * tex_dim.height(),
region.b() - _slice.b * tex_dim.height(),
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();
setBoundingBox(_geom->getBound());
@@ -142,27 +231,53 @@ namespace canvas
if( _attributes_dirty & SRC_RECT )
{
double u0 = _src_rect.l(),
u1 = _src_rect.r(),
v0 = _src_rect.b(),
v1 = _src_rect.t();
SGRect<float> src_rect = _src_rect;
if( !_node_src_rect->getBoolValue("normalized", true) )
{
const SGRect<int>& tex_dim = getTextureDimensions();
u0 /= tex_dim.width();
u1 /= tex_dim.width();
v0 /= tex_dim.height();
v1 /= tex_dim.height();
src_rect.t() /= tex_dim.height();
src_rect.r() /= tex_dim.width();
src_rect.b() /= tex_dim.height();
src_rect.l() /= tex_dim.width();
}
(*_texCoords)[0].set(u0, v0);
(*_texCoords)[1].set(u1, v0);
(*_texCoords)[2].set(u1, v1);
(*_texCoords)[3].set(u0, v1);
_texCoords->dirty();
// Image coordinate systems y-axis is flipped
std::swap(src_rect.t(), src_rect.b());
if( !_slice.valid )
{
setQuadUV(0, src_rect.getMin(), src_rect.getMax());
}
else
{
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;
}
}
@@ -212,11 +327,24 @@ namespace canvas
if( !parseColor(fill, color) )
return;
for( int i = 0; i < 4; ++i )
(*_colors)[i] = color;
_colors->front() = color;
_colors->dirty();
}
//----------------------------------------------------------------------------
void Image::setSlice(const std::string& slice)
{
_slice = parseSideOffsets(slice);
_attributes_dirty |= SRC_RECT | DEST_SIZE;
}
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = parseSideOffsets(outset);
_attributes_dirty |= DEST_SIZE;
}
//----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const
{
@@ -362,5 +490,97 @@ namespace canvas
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());
}
//----------------------------------------------------------------------------
Image::CSSOffsets Image::parseSideOffsets(const std::string& str) const
{
if( str.empty() )
return CSSOffsets();
const SGRect<int>& dim = getTextureDimensions();
// [<number>'%'?]{1,4} (top[,right[,bottom[,left]]])
//
// Percentages are relative to the size of the image: the width of the
// image for the horizontal offsets, the height for vertical offsets.
// Numbers represent pixels in the image.
int c = 0;
CSSOffsets ret;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
const boost::char_separator<char> del(" \t\n");
tokenizer tokens(str.begin(), str.end(), del);
for( tokenizer::const_iterator tok = tokens.begin();
tok != tokens.end() && c < 4;
++tok )
{
if( isalpha(*tok->begin()) )
ret.keyword = *tok;
else
{
bool rel = (*tok->rbegin() == '%');
ret.offsets[c] =
// Negative values are not allowed and values bigger than the size of
// the image are interpreted as 100%.
std::max(0.f,
std::min(1.f,
boost::lexical_cast<float>
(
rel ? boost::make_iterator_range(tok->begin(), tok->end() - 1)
: *tok
)
/
(
rel ? 100
: (c & 1) ? dim.width() : dim.height()
)
));
++c;
}
}
// When four values are specified, they set the offsets on the top, right,
// bottom and left sides in that order.
if( c < 4 )
{
if( c < 3 )
{
if( c < 2 )
// if the right is missing, it is the same as the top.
ret.offsets[1] = ret.offsets[0];
// if the bottom is missing, it is the same as the top
ret.offsets[2] = ret.offsets[0];
}
// If the left is missing, it is the same as the right
ret.offsets[3] = ret.offsets[1];
}
ret.valid = true;
return ret;
}
} // namespace canvas
} // namespace simgear

View File

@@ -55,6 +55,18 @@ namespace canvas
void setImage(osg::Image *img);
void setFill(const std::string& fill);
/**
* Set image slice (aka. 9-scale)
*
* @see http://www.w3.org/TR/css3-background/#border-image-slice
*/
void setSlice(const std::string& slice);
/**
* http://www.w3.org/TR/css3-background/#border-image-outset
*/
void setOutset(const std::string& outset);
const SGRect<float>& getRegion() const;
protected:
@@ -65,16 +77,38 @@ namespace canvas
DEST_SIZE = SRC_RECT << 1 // Element size
};
struct CSSOffsets
{
CSSOffsets():
valid(false)
{}
union
{
float offsets[4];
struct { float t, r, b, l; };
};
std::string keyword;
bool valid;
};
virtual void childChanged(SGPropertyNode * child);
void setupDefaultDimensions();
SGRect<int> getTextureDimensions() const;
void setQuad(size_t index, const SGVec2f& tl, const SGVec2f& br);
void setQuadUV(size_t index, const SGVec2f& tl, const SGVec2f& br);
CSSOffsets parseSideOffsets(const std::string& str) const;
osg::ref_ptr<osg::Texture2D> _texture;
// TODO optionally forward events to canvas
CanvasWeakPtr _src_canvas;
osg::ref_ptr<osg::Geometry> _geom;
osg::ref_ptr<osg::DrawArrays>_prim;
osg::ref_ptr<osg::Vec3Array> _vertices;
osg::ref_ptr<osg::Vec2Array> _texCoords;
osg::ref_ptr<osg::Vec4Array> _colors;
@@ -82,6 +116,9 @@ namespace canvas
SGPropertyNode *_node_src_rect;
SGRect<float> _src_rect,
_region;
CSSOffsets _slice,
_outset;
};
} // namespace canvas

View File

@@ -99,6 +99,11 @@ class SGRect
T t() const { return _min.y(); }
T b() const { return _max.y(); }
T& l() { return _min.x(); }
T& r() { return _max.x(); }
T& t() { return _min.y(); }
T& b() { return _max.y(); }
void setLeft(T l) { _min.x() = l; }
void setRight(T r) { _max.x() = r; }
void setTop(T t) { _min.y() = t; }