From: Xiangyin Ma Date: Mon, 5 Dec 2016 18:38:43 +0000 (+0000) Subject: (ImageAtlas) Add method for packing a group of pixelData into atlas X-Git-Tag: dali_1.2.20~18^2~1 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=34c7e90e1a2542994e329fe19875701cfe52c66e (ImageAtlas) Add method for packing a group of pixelData into atlas Change-Id: Ibca837868795cbfe951c5805a930af51f0f2c2c9 --- diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp index 0bee5e5..17b4432 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp @@ -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( malloc( bufferSize ) ); + PixelData pixelData = PixelData::New( buffer, bufferSize, width, height, Pixel::RGBA8888, PixelData::FREE ); + + return pixelData; +} + Rect TextureCoordinateToPixelArea( const Vector4& textureCoordinate, float size ) { Vector4 temp = textureCoordinate * size; Rect pixelArea; pixelArea.x = static_cast( temp.x ); pixelArea.y = static_cast( temp.y ); - pixelArea.width = static_cast( temp.z-temp.x+1.f ); - pixelArea.height = static_cast( temp.w-temp.y+1.f ); + pixelArea.width = static_cast( temp.z-temp.x+1.01f ); + pixelArea.height = static_cast( temp.w-temp.y+1.01f ); + + return pixelArea; +} + +Rect TextureCoordinateToPixelArea( const Vector4& textureCoordinate, float width, float height ) +{ + Rect pixelArea; + pixelArea.x = static_cast( textureCoordinate.x*width ); + pixelArea.y = static_cast( textureCoordinate.y*height); + pixelArea.width = static_cast( (textureCoordinate.z-textureCoordinate.x)*width+1.01f ); + pixelArea.height = static_cast( (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 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 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 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; +} diff --git a/dali-toolkit/devel-api/image-loader/image-atlas.cpp b/dali-toolkit/devel-api/image-loader/image-atlas.cpp index fd6825e..84903b3 100644 --- a/dali-toolkit/devel-api/image-loader/image-atlas.cpp +++ b/dali-toolkit/devel-api/image-loader/image-atlas.cpp @@ -35,6 +35,11 @@ ImageAtlas::~ImageAtlas() { } +Texture ImageAtlas::PackToAtlas( const std::vector& pixelData, Dali::Vector& textureRects ) +{ + return Internal::ImageAtlas::PackToAtlas( pixelData, textureRects ); +} + ImageAtlas::ImageAtlas(Internal::ImageAtlas* internal) : BaseHandle( internal ) { diff --git a/dali-toolkit/devel-api/image-loader/image-atlas.h b/dali-toolkit/devel-api/image-loader/image-atlas.h index 98796bd..a0305fd 100644 --- a/dali-toolkit/devel-api/image-loader/image-atlas.h +++ b/dali-toolkit/devel-api/image-loader/image-atlas.h @@ -20,6 +20,7 @@ // EXTERNAL INCLUDES #include #include +#include #include #include #include @@ -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, Dali::Vector& textureRects ); + + /** * @brief Create a new ImageAtlas. * * @param [in] width The atlas width in pixels. diff --git a/dali-toolkit/internal/image-loader/atlas-packer.cpp b/dali-toolkit/internal/image-loader/atlas-packer.cpp index f452399..4212188 100644 --- a/dali-toolkit/internal/image-loader/atlas-packer.cpp +++ b/dali-toolkit/internal/image-loader/atlas-packer.cpp @@ -20,6 +20,7 @@ // EXTERNAL HEADER #include // For abs() +#include 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& blockSizes, Dali::Vector& 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 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 diff --git a/dali-toolkit/internal/image-loader/atlas-packer.h b/dali-toolkit/internal/image-loader/atlas-packer.h index bab993f..2d6c95b 100644 --- a/dali-toolkit/internal/image-loader/atlas-packer.h +++ b/dali-toolkit/internal/image-loader/atlas-packer.h @@ -18,7 +18,9 @@ */ #include +#include #include +#include 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& blockSizes, Dali::Vector& 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: diff --git a/dali-toolkit/internal/image-loader/image-atlas-impl.cpp b/dali-toolkit/internal/image-loader/image-atlas-impl.cpp index bdd2780..da8fe90 100644 --- a/dali-toolkit/internal/image-loader/image-atlas-impl.cpp +++ b/dali-toolkit/internal/image-loader/image-atlas-impl.cpp @@ -34,6 +34,49 @@ namespace Toolkit namespace Internal { +Texture ImageAtlas::PackToAtlas( const std::vector& pixelData, Dali::Vector& textureRects ) +{ + // Record each block size + Dali::Vector 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 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( atlasTexture.GetWidth() ); + float atlasHeight = static_cast( 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( packPositionX ) +0.5f ) / atlasWidth; // left + textureRects[index].y = ( static_cast( packPositionY ) +0.5f ) / atlasHeight; // right + textureRects[index].z = ( static_cast( packPositionX + pixelData[index].GetWidth() )-0.5f ) / atlasWidth; // right + textureRects[index].w = ( static_cast( 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 ), diff --git a/dali-toolkit/internal/image-loader/image-atlas-impl.h b/dali-toolkit/internal/image-loader/image-atlas-impl.h index 4f735ef..2980627 100644 --- a/dali-toolkit/internal/image-loader/image-atlas-impl.h +++ b/dali-toolkit/internal/image-loader/image-atlas-impl.h @@ -47,6 +47,11 @@ public: typedef Toolkit::ImageAtlas::SizeType SizeType; /** + * @copydoc ImageAtlas::PackToAtlas( const std::vector&, Dali::Vector& ) + */ + static Texture PackToAtlas( const std::vector& pixelData, Dali::Vector& textureRects ); + + /** * Constructor * @param [in] width The atlas width in pixels. * @param [in] height The atlas height in pixels.