]> git.mxchange.org Git - simgear.git/commitdiff
canvas::Image: allow aspect ratio preserving display.
authorThomas Geymayer <tomgey@gmail.com>
Sun, 8 Jun 2014 00:12:44 +0000 (02:12 +0200)
committerThomas Geymayer <tomgey@gmail.com>
Sun, 8 Jun 2014 00:16:07 +0000 (02:16 +0200)
simgear/canvas/elements/CanvasImage.cxx
simgear/canvas/elements/CanvasImage.hxx
simgear/misc/CMakeLists.txt
simgear/misc/SVGpreserveAspectRatio.cxx [new file with mode: 0644]
simgear/misc/SVGpreserveAspectRatio.hxx [new file with mode: 0644]
simgear/misc/SVGpreserveAspectRatio_test.cxx [new file with mode: 0644]

index 2b91053933027739a691ed1b309b420a4402f3b0..20d199088289dc12e729f646c77775e0597f9d3e 100644 (file)
@@ -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<float>& Image::getRegion() const
   {
index a68390456a041b38ff9c1b680ab223495acd74e0..73e6d4d55a1e5baf6cfe431a9b252ef79f5486f7 100644 (file)
@@ -24,6 +24,7 @@
 #include <simgear/canvas/canvas_fwd.hxx>
 #include <simgear/io/HTTPClient.hxx>
 #include <simgear/misc/CSSBorder.hxx>
+#include <simgear/misc/SVGpreserveAspectRatio.hxx>
 #include <osg/Texture2D>
 
 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<float>& getRegion() const;
 
       bool handleEvent(const EventPtr& event);
@@ -126,9 +133,11 @@ namespace canvas
       SGRect<float>   _src_rect,
                       _region;
 
-      CSSBorder       _slice,
-                      _slice_width,
-                      _outset;
+      SVGpreserveAspectRatio _preserve_aspect_ratio;
+
+      CSSBorder       _outset,
+                      _slice,
+                      _slice_width;
   };
 
 } // namespace canvas
index 16e74bbd6cddecba3953b34496beaa0582d398ab..00c7e97809199ce4ac1d21a1bf0064b0e8b6d910 100644 (file)
@@ -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 (file)
index 0000000..4b994de
--- /dev/null
@@ -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
diff --git a/simgear/misc/SVGpreserveAspectRatio.hxx b/simgear/misc/SVGpreserveAspectRatio.hxx
new file mode 100644 (file)
index 0000000..83cce89
--- /dev/null
@@ -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_ */
diff --git a/simgear/misc/SVGpreserveAspectRatio_test.cxx b/simgear/misc/SVGpreserveAspectRatio_test.cxx
new file mode 100644 (file)
index 0000000..ba8b0f5
--- /dev/null
@@ -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 );
+}