From c54e3f81011bbb4d584cf79e34881a55f60ecc0b Mon Sep 17 00:00:00 2001 From: Thomas Geymayer Date: Sun, 8 Jun 2014 02:12:44 +0200 Subject: [PATCH] canvas::Image: allow aspect ratio preserving display. --- simgear/canvas/elements/CanvasImage.cxx | 88 ++++++++- simgear/canvas/elements/CanvasImage.hxx | 25 ++- simgear/misc/CMakeLists.txt | 7 + simgear/misc/SVGpreserveAspectRatio.cxx | 192 +++++++++++++++++++ simgear/misc/SVGpreserveAspectRatio.hxx | 67 +++++++ simgear/misc/SVGpreserveAspectRatio_test.cxx | 60 ++++++ 6 files changed, 423 insertions(+), 16 deletions(-) create mode 100644 simgear/misc/SVGpreserveAspectRatio.cxx create mode 100644 simgear/misc/SVGpreserveAspectRatio.hxx create mode 100644 simgear/misc/SVGpreserveAspectRatio_test.cxx diff --git a/simgear/canvas/elements/CanvasImage.cxx b/simgear/canvas/elements/CanvasImage.cxx index 2b910539..20d19908 100644 --- a/simgear/canvas/elements/CanvasImage.cxx +++ b/simgear/canvas/elements/CanvasImage.cxx @@ -96,9 +96,10 @@ namespace canvas return; addStyle("fill", "color", &Image::setFill); + addStyle("outset", "", &Image::setOutset); + addStyle("preserveAspectRatio", "", &Image::setPreserveAspectRatio); addStyle("slice", "", &Image::setSlice); addStyle("slice-width", "", &Image::setSliceWidth); - addStyle("outset", "", &Image::setOutset); } //---------------------------------------------------------------------------- @@ -214,6 +215,10 @@ namespace canvas 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 { @@ -290,6 +295,66 @@ namespace canvas 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 @@ -413,6 +478,20 @@ namespace canvas _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) { @@ -427,13 +506,6 @@ namespace canvas _attributes_dirty |= DEST_SIZE; } - //---------------------------------------------------------------------------- - void Image::setOutset(const std::string& outset) - { - _outset = CSSBorder::parse(outset); - _attributes_dirty |= DEST_SIZE; - } - //---------------------------------------------------------------------------- const SGRect& Image::getRegion() const { diff --git a/simgear/canvas/elements/CanvasImage.hxx b/simgear/canvas/elements/CanvasImage.hxx index a6839045..73e6d4d5 100644 --- a/simgear/canvas/elements/CanvasImage.hxx +++ b/simgear/canvas/elements/CanvasImage.hxx @@ -24,6 +24,7 @@ #include #include #include +#include #include namespace simgear @@ -61,6 +62,17 @@ namespace canvas void setImage(osg::Image *img); 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) * @@ -79,11 +91,6 @@ namespace canvas */ void setSliceWidth(const std::string& width); - /** - * http://www.w3.org/TR/css3-background/#border-image-outset - */ - void setOutset(const std::string& outset); - const SGRect& getRegion() const; bool handleEvent(const EventPtr& event); @@ -126,9 +133,11 @@ namespace canvas SGRect _src_rect, _region; - CSSBorder _slice, - _slice_width, - _outset; + SVGpreserveAspectRatio _preserve_aspect_ratio; + + CSSBorder _outset, + _slice, + _slice_width; }; } // namespace canvas diff --git a/simgear/misc/CMakeLists.txt b/simgear/misc/CMakeLists.txt index 16e74bbd..00c7e978 100644 --- a/simgear/misc/CMakeLists.txt +++ b/simgear/misc/CMakeLists.txt @@ -5,6 +5,7 @@ set(HEADERS CSSBorder.hxx ListDiff.hxx ResourceManager.hxx + SVGpreserveAspectRatio.hxx interpolator.hxx make_new.hxx sg_dir.hxx @@ -22,6 +23,7 @@ set(HEADERS set(SOURCES CSSBorder.cxx ResourceManager.cxx + SVGpreserveAspectRatio.cxx interpolator.cxx sg_dir.cxx sg_path.cxx @@ -62,3 +64,8 @@ add_test(path ${EXECUTABLE_OUTPUT_PATH}/test_path) target_link_libraries(test_path ${TEST_LIBS}) endif(ENABLE_TESTS) + +add_boost_test(SVGpreserveAspectRatio + SOURCES SVGpreserveAspectRatio_test.cxx + LIBRARIES ${TEST_LIBS} +) \ No newline at end of file diff --git a/simgear/misc/SVGpreserveAspectRatio.cxx b/simgear/misc/SVGpreserveAspectRatio.cxx new file mode 100644 index 00000000..4b994de1 --- /dev/null +++ b/simgear/misc/SVGpreserveAspectRatio.cxx @@ -0,0 +1,192 @@ +// Parse and represent SVG preserveAspectRatio attribute +// +// Copyright (C) 2014 Thomas Geymayer +// +// 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 +#include + +#include + +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 > tokenizer; + const boost::char_separator 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 diff --git a/simgear/misc/SVGpreserveAspectRatio.hxx b/simgear/misc/SVGpreserveAspectRatio.hxx new file mode 100644 index 00000000..83cce89c --- /dev/null +++ b/simgear/misc/SVGpreserveAspectRatio.hxx @@ -0,0 +1,67 @@ +///@file Parse and represent SVG preserveAspectRatio attribute +// +// Copyright (C) 2014 Thomas Geymayer +// +// 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 + +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_ */ diff --git a/simgear/misc/SVGpreserveAspectRatio_test.cxx b/simgear/misc/SVGpreserveAspectRatio_test.cxx new file mode 100644 index 00000000..ba8b0f5c --- /dev/null +++ b/simgear/misc/SVGpreserveAspectRatio_test.cxx @@ -0,0 +1,60 @@ +/// Unit tests for SVGpreserveAspectRatio +#define BOOST_TEST_MODULE misc +#include + +#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 ); +} -- 2.39.5