(ImageAtlas) Add method for packing a group of pixelData into atlas 30/102430/10
authorXiangyin Ma <x1.ma@samsung.com>
Mon, 5 Dec 2016 18:38:43 +0000 (18:38 +0000)
committerXiangyin Ma <x1.ma@samsung.com>
Fri, 16 Dec 2016 11:42:25 +0000 (03:42 -0800)
Change-Id: Ibca837868795cbfe951c5805a930af51f0f2c2c9

automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp
dali-toolkit/devel-api/image-loader/image-atlas.cpp
dali-toolkit/devel-api/image-loader/image-atlas.h
dali-toolkit/internal/image-loader/atlas-packer.cpp
dali-toolkit/internal/image-loader/atlas-packer.h
dali-toolkit/internal/image-loader/image-atlas-impl.cpp
dali-toolkit/internal/image-loader/image-atlas-impl.h

index 0bee5e5..17b4432 100644 (file)
@@ -40,14 +40,35 @@ static const char* gImageNonExist = "non-exist.jpg";
 
 const int RENDER_FRAME_INTERVAL = 16; ///< Duration of each frame in ms. (at approx 60FPS)
 
+PixelData CreatePixelData( unsigned int width, unsigned int height )
+{
+  unsigned int bufferSize = width*height*Pixel::GetBytesPerPixel( Pixel::RGBA8888 );
+
+  unsigned char* buffer= reinterpret_cast<unsigned char*>( malloc( bufferSize ) );
+  PixelData pixelData = PixelData::New( buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE );
+
+  return pixelData;
+}
+
 Rect<int> TextureCoordinateToPixelArea( const Vector4& textureCoordinate, float size )
 {
   Vector4 temp = textureCoordinate * size;
   Rect<int> pixelArea;
   pixelArea.x = static_cast<int>( temp.x );
   pixelArea.y = static_cast<int>( temp.y );
-  pixelArea.width = static_cast<int>( temp.z-temp.x+1.f );
-  pixelArea.height = static_cast<int>( temp.w-temp.y+1.f );
+  pixelArea.width = static_cast<int>( temp.z-temp.x+1.01f );
+  pixelArea.height = static_cast<int>( temp.w-temp.y+1.01f );
+
+  return pixelArea;
+}
+
+Rect<int> TextureCoordinateToPixelArea( const Vector4& textureCoordinate, float width, float height )
+{
+  Rect<int> pixelArea;
+  pixelArea.x = static_cast<int>( textureCoordinate.x*width );
+  pixelArea.y = static_cast<int>( textureCoordinate.y*height);
+  pixelArea.width = static_cast<int>( (textureCoordinate.z-textureCoordinate.x)*width+1.01f );
+  pixelArea.height = static_cast<int>( (textureCoordinate.w-textureCoordinate.y)*height+1.01f );
 
   return pixelArea;
 }
@@ -434,3 +455,56 @@ int UtcDaliImageAtlasImageView(void)
 
   END_TEST;
 }
+
+int UtcDaliImageAtlasPackToAtlas(void)
+{
+  ToolkitTestApplication application;
+
+  std::vector<PixelData> pixelDataContainer;
+  pixelDataContainer.push_back( CreatePixelData( 20, 30 ) );
+  pixelDataContainer.push_back( CreatePixelData( 10, 10 ) );
+  pixelDataContainer.push_back( CreatePixelData( 45, 30 ) );
+  pixelDataContainer.push_back( CreatePixelData( 20, 20 ) );
+
+  Dali::Vector<Vector4> textureRects;
+  Texture texture = ImageAtlas::PackToAtlas( pixelDataContainer, textureRects  );
+
+ // --------------
+ // |            |
+ // |    45*30   |
+//  |            |
+//  --------------
+//  | 20 |    | 20*20
+//  |  * |____|
+//  | 30 |  |  10*10
+//  --------
+
+  DALI_TEST_EQUALS( texture.GetWidth(), 45, TEST_LOCATION );
+  DALI_TEST_EQUALS( texture.GetHeight(), 60, TEST_LOCATION );
+
+  Rect<int> pixelArea = TextureCoordinateToPixelArea(textureRects[0], texture.GetWidth(), texture.GetHeight());
+  DALI_TEST_EQUALS( pixelArea.x, 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.y, 30, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.width, 20, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.height, 30, TEST_LOCATION );
+
+  pixelArea = TextureCoordinateToPixelArea(textureRects[1], texture.GetWidth(), texture.GetHeight());
+  DALI_TEST_EQUALS( pixelArea.x, 20, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.y, 50, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.width, 10, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.height, 10, TEST_LOCATION );
+
+  pixelArea = TextureCoordinateToPixelArea(textureRects[2], texture.GetWidth(), texture.GetHeight());
+  DALI_TEST_EQUALS( pixelArea.x, 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.y, 0, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.width, 45, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.height, 30, TEST_LOCATION );
+
+  pixelArea = TextureCoordinateToPixelArea(textureRects[3], texture.GetWidth(), texture.GetHeight());
+  DALI_TEST_EQUALS( pixelArea.x, 20, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.y, 30, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.width, 20, TEST_LOCATION );
+  DALI_TEST_EQUALS( pixelArea.height, 20, TEST_LOCATION );
+
+  END_TEST;
+}
index fd6825e..84903b3 100644 (file)
@@ -35,6 +35,11 @@ ImageAtlas::~ImageAtlas()
 {
 }
 
+Texture ImageAtlas::PackToAtlas( const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects  )
+{
+  return Internal::ImageAtlas::PackToAtlas( pixelData, textureRects );
+}
+
 ImageAtlas::ImageAtlas(Internal::ImageAtlas* internal)
 : BaseHandle( internal )
 {
index 98796bd..a0305fd 100644 (file)
@@ -20,6 +20,7 @@
 // EXTERNAL INCLUDES
 #include <string>
 #include <stdint.h>
+#include <dali/public-api/common/vector-wrapper.h>
 #include <dali/public-api/object/base-handle.h>
 #include <dali/public-api/images/image-operations.h>
 #include <dali/public-api/images/pixel.h>
@@ -34,6 +35,7 @@ namespace Dali
 
 namespace Toolkit
 {
+
 namespace Internal DALI_INTERNAL
 {
 class ImageAtlas;
@@ -54,6 +56,14 @@ public:
 public:
 
   /**
+   * @brief Pack a group of  pixel data into atlas.
+   * @param[in] pixelData The group of the pixel data to be packed into the atlas.
+   * @param[out] textureRects The list of texture areas where each frame is located inside the atlas.
+   * @return The atlas texture.
+   */
+  static Texture PackToAtlas( const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects  );
+
+  /**
    * @brief Create a new ImageAtlas.
    *
    * @param [in] width          The atlas width in pixels.
index f452399..4212188 100644 (file)
@@ -20,6 +20,7 @@
 
 // EXTERNAL HEADER
 #include <stdlib.h> // For abs()
+#include <dali/integration-api/debug.h>
 
 namespace Dali
 {
@@ -38,6 +39,18 @@ bool ApproximatelyEqual( uint32_t a, uint32_t b  )
   return abs( a-b ) <= 1;
 }
 
+uint16_t MaxDimension( const Uint16Pair& dimensions )
+{
+  return dimensions.GetWidth() >= dimensions.GetHeight() ? dimensions.GetWidth() : dimensions.GetHeight();
+}
+
+void Swap( Uint16Pair& first, Uint16Pair& second )
+{
+  Uint16Pair temp = first;
+  first = second;
+  second = temp;
+}
+
 }
 
 AtlasPacker::Node::Node( Node* parent, SizeType x, SizeType y, SizeType width, SizeType height  )
@@ -195,6 +208,103 @@ void AtlasPacker::DeleteNode( Node *node )
   }
 }
 
+Uint16Pair AtlasPacker::GroupPack( const Dali::Vector<Uint16Pair>& blockSizes, Dali::Vector<Uint16Pair>& packPositions )
+{
+  uint16_t count = blockSizes.Count();
+  packPositions.Resize( count );
+
+  // Sort the blocks according to its maximum dimension. The bigger blocks are packed first.
+  Dali::Vector<Uint16Pair> packOrder;
+  packOrder.Resize( count );
+  for( uint16_t i = 0; i < count; i++ )
+  {
+    packOrder[i].SetX( MaxDimension( blockSizes[i] ) );
+    packOrder[i].SetY( i );
+  }
+  for( uint16_t i = 0; i < count-1; i++ )
+    for( uint16_t j = 0; j < count-i-1; j++ )
+    {
+      if( packOrder[j].GetX() < packOrder[j+1].GetX() )
+      {
+        Swap( packOrder[j], packOrder[j+1] );
+      }
+    }
+
+  int index = packOrder[0].GetY();
+  AtlasPacker packer( blockSizes[index].GetWidth(), blockSizes[index].GetHeight() );
+
+  SizeType packPositionX, packPositionY;
+  // pack the blocks one by one with descending size, grows as necessary to accommodate each subsequent block.
+  for( uint16_t i = 0; i < count; i++ )
+  {
+    index = packOrder[i].GetY();
+    packer.GrowPack( blockSizes[index].GetWidth(), blockSizes[index].GetHeight(),
+                     packPositionX, packPositionY );
+    packPositions[index].SetX( packPositionX );
+    packPositions[index].SetY( packPositionY );
+  }
+
+  return Uint16Pair( packer.mRoot->rectArea.width, packer.mRoot->rectArea.height );
+}
+
+void AtlasPacker::GrowPack( SizeType blockWidth, SizeType blockHeight,
+                            SizeType& packPositionX, SizeType& packPositionY )
+{
+  Node* firstFit = InsertNode( mRoot, blockWidth, blockHeight );
+  if( firstFit == NULL )
+  {
+    // Could fit in the current left space, grow the partition tree to get more space.
+    GrowNode( blockWidth, blockHeight );
+    firstFit = InsertNode( mRoot->child[1], blockWidth, blockHeight );
+  }
+
+  if( firstFit != NULL )
+  {
+    firstFit->occupied = true;
+    packPositionX = firstFit->rectArea.x;
+    packPositionY = firstFit->rectArea.y;
+  }
+}
+
+void AtlasPacker::GrowNode( SizeType blockWidth, SizeType blockHeight )
+{
+  // Attempts to maintain a roughly square ratio when choosing the growing direction: right or down
+  bool canGrowRight = blockWidth <= mRoot->rectArea.width;
+  bool canGrowDown = blockHeight <= mRoot->rectArea.height;
+
+  bool shouldGrowRight = canGrowRight && mRoot->rectArea.height >= mRoot->rectArea.width+blockWidth;
+  bool shouldGrowDown = canGrowDown && mRoot->rectArea.width >= mRoot->rectArea.height+blockHeight;
+
+  if( canGrowRight && canGrowRight )
+  {
+    shouldGrowRight = mRoot->rectArea.width+blockWidth <= mRoot->rectArea.height+blockHeight;
+    shouldGrowDown = !shouldGrowRight;
+  }
+
+  if( shouldGrowRight || ( canGrowRight && !shouldGrowDown ) )
+  {
+    Node* newRoot = new Node( NULL, 0u, 0u, mRoot->rectArea.width+blockWidth, mRoot->rectArea.height );
+    newRoot->occupied = true;
+    newRoot->child[0] = mRoot;
+    newRoot->child[1] = new Node( newRoot, mRoot->rectArea.width, 0u, blockWidth, mRoot->rectArea.height );
+
+    mRoot = newRoot;
+  }
+  else if( shouldGrowDown || ( canGrowDown && !shouldGrowRight ) )
+  {
+    Node* newRoot = new Node( NULL, 0u, 0u, mRoot->rectArea.width, mRoot->rectArea.height+blockHeight );
+    newRoot->occupied = true;
+    newRoot->child[0] = mRoot;
+    newRoot->child[1] = new Node( newRoot, 0u, mRoot->rectArea.height, mRoot->rectArea.width, blockHeight );
+
+    mRoot = newRoot;
+  }
+  else
+  {
+    DALI_LOG_ERROR( " Atlas Packer failed to grow: make sure the packing order is sorted with the block size to avoid this happening");
+  }
+}
+
 } // namespace Internal
 
 } // namespace Toolkit
index bab993f..2d6c95b 100644 (file)
@@ -18,7 +18,9 @@
  */
 
 #include <stdint.h>
+#include <dali/public-api/common/dali-vector.h>
 #include <dali/public-api/math/rect.h>
+#include <dali/public-api/math/uint-16-pair.h>
 
 namespace Dali
 {
@@ -99,6 +101,14 @@ public:
    */
   unsigned int GetAvailableArea() const;
 
+  /**
+   * Pack a group of blocks with different sizes, calculate the required packing size and the position of each block.
+   * @param[in] blockSizes The size list of the blocks .
+   * @param[out] packPositions The packing position of each block.
+   * @return The required size to accommodate all the blocks.
+   */
+  static Uint16Pair GroupPack( const Dali::Vector<Uint16Pair>& blockSizes, Dali::Vector<Uint16Pair>& packPositions );
+
 private:
 
   /*
@@ -146,11 +156,30 @@ private:
    */
   void DeleteNode( Node* node );
 
+  /**
+   * Pack a block into the atlas. If there is no enough room, grow the partition tree.
+   *
+   * @param[in] blockWidth The width of the block to pack.
+   * @param[in] blockHeight The height of the block to pack.
+   * @param[out] packPositionX The x coordinate of the position to pack the block.
+   * @param[out] packPositionY The y coordinate of the position to pack the block.
+   */
+  void GrowPack( SizeType blockWidth, SizeType blockHeight,
+                 SizeType& packPositionX, SizeType& packPositionY );
+
+  /**
+   * Add extra node into the partition tree to accommodate the given block.
+   *
+   * @param[in] blockWidth The width of the block to pack.
+   * @param[in] blockHeight The height of the block to pack.
+   */
+  void GrowNode( SizeType blockWidth, SizeType blockHeight );
+
   // Undefined
-  AtlasPacker( const AtlasPacker& imageAtlas);
+  AtlasPacker( const AtlasPacker& atlasPacker);
 
   // Undefined
-  AtlasPacker& operator=( const AtlasPacker& imageAtlas );
+  AtlasPacker& operator=( const AtlasPacker& atlasPacker );
 
 private:
 
index bdd2780..da8fe90 100644 (file)
@@ -34,6 +34,49 @@ namespace Toolkit
 namespace Internal
 {
 
+Texture ImageAtlas::PackToAtlas( const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects  )
+{
+  // Record each block size
+  Dali::Vector<Uint16Pair> blockSizes;
+  SizeType count = pixelData.size();
+  for( SizeType index = 0; index < count; index++ )
+  {
+    blockSizes.PushBack( ImageDimensions( pixelData[index].GetWidth(), pixelData[index].GetHeight() ) );
+  }
+
+  // Ask atlasPacker for packing position of each block
+  Dali::Vector<Uint16Pair> packPositions;
+  ImageDimensions atlasSize = AtlasPacker::GroupPack( blockSizes, packPositions );
+
+  // Prepare for outout texture rect array
+  textureRects.Clear();
+  textureRects.Resize( count );
+
+  // create the texture for uploading the multiple pixel data
+  Texture atlasTexture = Texture::New( Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, atlasSize.GetWidth(), atlasSize.GetHeight() );
+
+  float atlasWidth = static_cast<float>( atlasTexture.GetWidth() );
+  float atlasHeight = static_cast<float>( atlasTexture.GetHeight() );
+  int packPositionX, packPositionY;
+  // Upload the pixel data one by one to its packing position, and record the texture rects
+  for( SizeType index = 0; index < count; index++ )
+  {
+    packPositionX = packPositions[index].GetX();
+    packPositionY = packPositions[index].GetY();
+    atlasTexture.Upload( pixelData[index], 0u, 0u,
+                         packPositionX, packPositionY,
+                         pixelData[index].GetWidth(), pixelData[index].GetHeight() );
+
+    // Apply the half pixel correction to avoid the color bleeding between neighbour blocks
+    textureRects[index].x = ( static_cast<float>( packPositionX ) +0.5f ) / atlasWidth; // left
+    textureRects[index].y = ( static_cast<float>( packPositionY ) +0.5f ) / atlasHeight; // right
+    textureRects[index].z = ( static_cast<float>( packPositionX + pixelData[index].GetWidth() )-0.5f ) / atlasWidth; // right
+    textureRects[index].w = ( static_cast<float>( packPositionY + pixelData[index].GetHeight() )-0.5f ) / atlasHeight;// bottom
+  }
+
+  return atlasTexture;
+}
+
 ImageAtlas::ImageAtlas( SizeType width, SizeType height, Pixel::Format pixelFormat )
 : mAtlas( Texture::New( Dali::TextureType::TEXTURE_2D, pixelFormat, width, height ) ),
   mPacker( width, height ),
index 4f735ef..2980627 100644 (file)
@@ -47,6 +47,11 @@ public:
   typedef Toolkit::ImageAtlas::SizeType SizeType;
 
   /**
+   * @copydoc ImageAtlas::PackToAtlas( const std::vector<PixelData>&, Dali::Vector<Vector4>& )
+   */
+  static Texture PackToAtlas( const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects  );
+
+  /**
    * Constructor
    * @param [in] width          The atlas width in pixels.
    * @param [in] height         The atlas height in pixels.