]> git.mxchange.org Git - simgear.git/commitdiff
Support image slice/scale-9 for CanvasImage.
authorThomas Geymayer <tomgey@gmail.com>
Sun, 24 Feb 2013 16:39:45 +0000 (17:39 +0100)
committerThomas Geymayer <tomgey@gmail.com>
Sun, 24 Feb 2013 16:57:19 +0000 (17:57 +0100)
simgear/canvas/elements/CanvasImage.cxx
simgear/canvas/elements/CanvasImage.hxx
simgear/math/SGRect.hxx

index ee8f204b60dfe19d5cb45f3143291833750757d8..3539cd4a2f4100bcbec3abfe2a81f75c8d242589 100644 (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();
+        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());
 
-        u0 /= tex_dim.width();
-        u1 /= tex_dim.width();
-        v0 /= tex_dim.height();
-        v1 /= tex_dim.height();
+      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)[0].set(u0, v0);
-      (*_texCoords)[1].set(u1, v0);
-      (*_texCoords)[2].set(u1, v1);
-      (*_texCoords)[3].set(u0, v1);
       _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
index 8d66fca17168f3ba41a18436145b3b0d36d6f0d6..127eece6d9f550e13860b80c0bdf5121d8bce6fb 100644 (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
index 20976a0c35c4711c4c4d3fd04b1f12cf22e4b3b9..7e70a12488a0dec89045f39f5eaec5f8b7560c7d 100644 (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; }