canvas::Image: allow aspect ratio preserving display.

This commit is contained in:
Thomas Geymayer 2014-06-08 02:12:44 +02:00
parent 6925c2a2be
commit c54e3f8101
6 changed files with 423 additions and 16 deletions

View File

@ -96,9 +96,10 @@ namespace canvas
return; return;
addStyle("fill", "color", &Image::setFill); addStyle("fill", "color", &Image::setFill);
addStyle("outset", "", &Image::setOutset);
addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio);
addStyle("slice", "", &Image::setSlice); addStyle("slice", "", &Image::setSlice);
addStyle("slice-width", "", &Image::setSliceWidth); addStyle("slice-width", "", &Image::setSliceWidth);
addStyle("outset", "", &Image::setOutset);
} }
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
@ -214,6 +215,10 @@ namespace canvas
if( !_slice.isValid() ) if( !_slice.isValid() )
{ {
setQuad(0, region.getMin(), region.getMax()); 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 else
{ {
@ -290,6 +295,66 @@ namespace canvas
if( !_slice.isValid() ) 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()); setQuadUV(0, src_rect.getMin(), src_rect.getMax());
} }
else else
@ -413,6 +478,20 @@ namespace canvas
_colors->dirty(); _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::setSlice(const std::string& slice) void Image::setSlice(const std::string& slice)
{ {
@ -427,13 +506,6 @@ namespace canvas
_attributes_dirty |= DEST_SIZE; _attributes_dirty |= DEST_SIZE;
} }
//----------------------------------------------------------------------------
void Image::setOutset(const std::string& outset)
{
_outset = CSSBorder::parse(outset);
_attributes_dirty |= DEST_SIZE;
}
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
const SGRect<float>& Image::getRegion() const const SGRect<float>& Image::getRegion() const
{ {

View File

@ -24,6 +24,7 @@
#include <simgear/canvas/canvas_fwd.hxx> #include <simgear/canvas/canvas_fwd.hxx>
#include <simgear/io/HTTPClient.hxx> #include <simgear/io/HTTPClient.hxx>
#include <simgear/misc/CSSBorder.hxx> #include <simgear/misc/CSSBorder.hxx>
#include <simgear/misc/SVGpreserveAspectRatio.hxx>
#include <osg/Texture2D> #include <osg/Texture2D>
namespace simgear namespace simgear
@ -61,6 +62,17 @@ namespace canvas
void setImage(osg::Image *img); void setImage(osg::Image *img);
void setFill(const std::string& fill); void setFill(const std::string& fill);
/**
* @see http://www.w3.org/TR/css3-background/#border-image-outset
*/
void setOutset(const std::string& outset);
/**
* @see
* http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
*/
void setPreserveAspectRatio(const std::string& scale);
/** /**
* Set image slice (aka. 9-scale) * Set image slice (aka. 9-scale)
* *
@ -79,11 +91,6 @@ namespace canvas
*/ */
void setSliceWidth(const std::string& width); void setSliceWidth(const std::string& width);
/**
* http://www.w3.org/TR/css3-background/#border-image-outset
*/
void setOutset(const std::string& outset);
const SGRect<float>& getRegion() const; const SGRect<float>& getRegion() const;
bool handleEvent(const EventPtr& event); bool handleEvent(const EventPtr& event);
@ -126,9 +133,11 @@ namespace canvas
SGRect<float> _src_rect, SGRect<float> _src_rect,
_region; _region;
CSSBorder _slice, SVGpreserveAspectRatio _preserve_aspect_ratio;
_slice_width,
_outset; CSSBorder _outset,
_slice,
_slice_width;
}; };
} // namespace canvas } // namespace canvas

View File

@ -5,6 +5,7 @@ set(HEADERS
CSSBorder.hxx CSSBorder.hxx
ListDiff.hxx ListDiff.hxx
ResourceManager.hxx ResourceManager.hxx
SVGpreserveAspectRatio.hxx
interpolator.hxx interpolator.hxx
make_new.hxx make_new.hxx
sg_dir.hxx sg_dir.hxx
@ -22,6 +23,7 @@ set(HEADERS
set(SOURCES set(SOURCES
CSSBorder.cxx CSSBorder.cxx
ResourceManager.cxx ResourceManager.cxx
SVGpreserveAspectRatio.cxx
interpolator.cxx interpolator.cxx
sg_dir.cxx sg_dir.cxx
sg_path.cxx sg_path.cxx
@ -62,3 +64,8 @@ add_test(path ${EXECUTABLE_OUTPUT_PATH}/test_path)
target_link_libraries(test_path ${TEST_LIBS}) target_link_libraries(test_path ${TEST_LIBS})
endif(ENABLE_TESTS) endif(ENABLE_TESTS)
add_boost_test(SVGpreserveAspectRatio
SOURCES SVGpreserveAspectRatio_test.cxx
LIBRARIES ${TEST_LIBS}
)

View File

@ -0,0 +1,192 @@
// Parse and represent SVG preserveAspectRatio attribute
//
// Copyright (C) 2014 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 "SVGpreserveAspectRatio.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <boost/tokenizer.hpp>
namespace simgear
{
//----------------------------------------------------------------------------
SVGpreserveAspectRatio::SVGpreserveAspectRatio():
_align_x(ALIGN_NONE),
_align_y(ALIGN_NONE),
_meet(true)
{
}
//----------------------------------------------------------------------------
SVGpreserveAspectRatio::Align SVGpreserveAspectRatio::alignX() const
{
return _align_x;
}
//----------------------------------------------------------------------------
SVGpreserveAspectRatio::Align SVGpreserveAspectRatio::alignY() const
{
return _align_y;
}
//----------------------------------------------------------------------------
bool SVGpreserveAspectRatio::scaleToFill() const
{
return (_align_x == ALIGN_NONE) && (_align_y == ALIGN_NONE);
}
//----------------------------------------------------------------------------
bool SVGpreserveAspectRatio::scaleToFit() const
{
return !scaleToFill() && _meet;
}
//----------------------------------------------------------------------------
bool SVGpreserveAspectRatio::scaleToCrop() const
{
return !scaleToFill() && !_meet;
}
//----------------------------------------------------------------------------
bool SVGpreserveAspectRatio::meet() const
{
return _meet;
}
//----------------------------------------------------------------------------
bool
SVGpreserveAspectRatio::operator==(const SVGpreserveAspectRatio& rhs) const
{
return (_align_x == rhs._align_x)
&& (_align_y == rhs._align_y)
&& (_meet == rhs._meet || scaleToFill());
}
//----------------------------------------------------------------------------
SVGpreserveAspectRatio SVGpreserveAspectRatio::parse(const std::string& str)
{
SVGpreserveAspectRatio ret;
enum
{
PARSE_defer,
PARSE_align,
PARSE_meetOrSlice,
PARSE_done,
PARSE_error
} parse_state = PARSE_defer;
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()
&& parse_state != PARSE_error;
++tok )
{
const std::string& cur_tok = tok.current_token();
switch( parse_state )
{
case PARSE_defer:
if( cur_tok == "defer" )
{
SG_LOG( SG_GENERAL,
SG_INFO,
"SVGpreserveAspectRatio: 'defer' is ignored." );
parse_state = PARSE_align;
break;
}
// fall through
case PARSE_align:
if( cur_tok == "none" )
{
ret._align_x = ALIGN_NONE;
ret._align_y = ALIGN_NONE;
}
else if( cur_tok.length() == 8 )
{
if( strutils::starts_with(cur_tok, "xMin") )
ret._align_x = ALIGN_MIN;
else if( strutils::starts_with(cur_tok, "xMid") )
ret._align_x = ALIGN_MID;
else if( strutils::starts_with(cur_tok, "xMax") )
ret._align_x = ALIGN_MAX;
else
{
parse_state = PARSE_error;
break;
}
if( strutils::ends_with(cur_tok, "YMin") )
ret._align_y = ALIGN_MIN;
else if( strutils::ends_with(cur_tok, "YMid") )
ret._align_y = ALIGN_MID;
else if( strutils::ends_with(cur_tok, "YMax") )
ret._align_y = ALIGN_MAX;
else
{
parse_state = PARSE_error;
break;
}
}
else
{
parse_state = PARSE_error;
break;
}
parse_state = PARSE_meetOrSlice;
break;
case PARSE_meetOrSlice:
if( cur_tok == "meet" )
ret._meet = true;
else if( cur_tok == "slice" )
ret._meet = false;
else
{
parse_state = PARSE_error;
break;
}
parse_state = PARSE_done;
break;
case PARSE_done:
SG_LOG( SG_GENERAL,
SG_WARN,
"SVGpreserveAspectRatio: Ignoring superfluous token"
" '" << cur_tok << "'" );
break;
default:
break;
}
}
if( parse_state == PARSE_error )
{
SG_LOG( SG_GENERAL,
SG_WARN,
"SVGpreserveAspectRatio: Failed to parse: '" << str << "'" );
return SVGpreserveAspectRatio();
}
return ret;
}
} // namespace simgear

View File

@ -0,0 +1,67 @@
///@file Parse and represent SVG preserveAspectRatio attribute
//
// Copyright (C) 2014 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
#ifndef SG_SVG_PRESERVE_ASPECT_RATIO_HXX_
#define SG_SVG_PRESERVE_ASPECT_RATIO_HXX_
#include <string>
namespace simgear
{
/**
* SVG preserveAspectRatio attribute
*
* @see http://www.w3.org/TR/SVG11/coords.html#PreserveAspectRatioAttribute
*/
class SVGpreserveAspectRatio
{
public:
enum Align
{
ALIGN_NONE,
ALIGN_MIN,
ALIGN_MID,
ALIGN_MAX
};
SVGpreserveAspectRatio();
Align alignX() const;
Align alignY() const;
bool scaleToFill() const;
bool scaleToFit() const;
bool scaleToCrop() const;
bool meet() const;
bool operator==(const SVGpreserveAspectRatio& rhs) const;
static SVGpreserveAspectRatio parse(const std::string& str);
private:
Align _align_x,
_align_y;
bool _meet; //!< uniform scale to fit, if true
// uniform scale to fill+crop, if false
};
} // namespace simgear
#endif /* SG_SVG_PRESERVE_ASPECT_RATIO_HXX_ */

View File

@ -0,0 +1,60 @@
/// Unit tests for SVGpreserveAspectRatio
#define BOOST_TEST_MODULE misc
#include <BoostTestTargetConfig.h>
#include "SVGpreserveAspectRatio.hxx"
namespace simgear
{
std::ostream& operator<<( std::ostream& strm,
const SVGpreserveAspectRatio& ar )
{
strm << "[ align_x=" << ar.alignX() <<
", align_y=" << ar.alignY() <<
", meet=" << ar.meet() <<
"]";
return strm;
}
}
BOOST_AUTO_TEST_CASE( parse_attribute )
{
using simgear::SVGpreserveAspectRatio;
SVGpreserveAspectRatio ar = SVGpreserveAspectRatio::parse("none");
BOOST_CHECK( ar.scaleToFill() );
BOOST_CHECK( !ar.scaleToFit() );
BOOST_CHECK( !ar.scaleToCrop() );
BOOST_CHECK_EQUAL( ar.alignX(), SVGpreserveAspectRatio::ALIGN_NONE );
BOOST_CHECK_EQUAL( ar.alignY(), SVGpreserveAspectRatio::ALIGN_NONE );
SVGpreserveAspectRatio ar_meet = SVGpreserveAspectRatio::parse("none meet");
SVGpreserveAspectRatio ar_slice = SVGpreserveAspectRatio::parse("none slice");
BOOST_CHECK_EQUAL( ar, ar_meet );
BOOST_CHECK_EQUAL( ar, ar_slice );
ar_meet = SVGpreserveAspectRatio::parse("xMidYMid meet");
BOOST_CHECK( !ar_meet.scaleToFill() );
BOOST_CHECK( ar_meet.scaleToFit() );
BOOST_CHECK( !ar_meet.scaleToCrop() );
BOOST_CHECK_EQUAL( ar_meet.alignX(), SVGpreserveAspectRatio::ALIGN_MID );
BOOST_CHECK_EQUAL( ar_meet.alignY(), SVGpreserveAspectRatio::ALIGN_MID );
ar_slice = SVGpreserveAspectRatio::parse("xMidYMid slice");
BOOST_CHECK( !ar_slice.scaleToFill() );
BOOST_CHECK( !ar_slice.scaleToFit() );
BOOST_CHECK( ar_slice.scaleToCrop() );
BOOST_CHECK_EQUAL( ar_slice.alignX(), SVGpreserveAspectRatio::ALIGN_MID );
BOOST_CHECK_EQUAL( ar_slice.alignY(), SVGpreserveAspectRatio::ALIGN_MID );
BOOST_CHECK_NE(ar_meet, ar_slice);
// defer is ignored, meet is default
ar_meet = SVGpreserveAspectRatio::parse("defer xMinYMin");
BOOST_CHECK( !ar_meet.scaleToFill() );
BOOST_CHECK( ar_meet.scaleToFit() );
BOOST_CHECK( !ar_meet.scaleToCrop() );
BOOST_CHECK_EQUAL( ar_meet.alignX(), SVGpreserveAspectRatio::ALIGN_MIN );
BOOST_CHECK_EQUAL( ar_meet.alignY(), SVGpreserveAspectRatio::ALIGN_MIN );
}