DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("foobar.jpeg"), TEST_LOCATION );
- DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("foobar.gif"), TEST_LOCATION );
-
DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("foobar.PNG"), TEST_LOCATION );
DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("foobar.Png123"), TEST_LOCATION );
{
tet_infoline( "UtcDaliResolveUrl N_PATCH" );
- DALI_TEST_EQUALS( UrlType::N_PATCH, ResolveUrlType("foobar.9.gif"), TEST_LOCATION );
-
DALI_TEST_EQUALS( UrlType::N_PATCH, ResolveUrlType("foobar.#.png"), TEST_LOCATION );
DALI_TEST_EQUALS( UrlType::N_PATCH, ResolveUrlType("foobar.9.9.bmp"), TEST_LOCATION );
END_TEST;
}
+
+int UtcDaliResolveUrlGif(void)
+{
+ tet_infoline( "UtcDaliResolveUrl GIF" );
+
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType("foobar.gif"), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType("foobar.gif.gif"), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType("foobar.giF"), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType("foobar.GIF"), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType(".GiF"), TEST_LOCATION );
+
+ // GIFs aren't N-patch
+ DALI_TEST_EQUALS( UrlType::GIF, ResolveUrlType("foobar.9.gif"), TEST_LOCATION );
+
+ DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("gif.png"), TEST_LOCATION );
+ DALI_TEST_EQUALS( UrlType::REGULAR_IMAGE, ResolveUrlType("gif.gif1"), TEST_LOCATION );
+
+ END_TEST;
+}
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;
}
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;
+}
const char* TEST_IMAGE_FILE_NAME = TEST_RESOURCE_DIR "/gallery-small-1.jpg";
const char* TEST_NPATCH_FILE_NAME = "gallery_image_01.9.jpg";
const char* TEST_SVG_FILE_NAME = TEST_RESOURCE_DIR "/svg1.svg";
+const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif";
const char* TEST_OBJ_FILE_NAME = TEST_RESOURCE_DIR "/Cube.obj";
const char* TEST_MTL_FILE_NAME = TEST_RESOURCE_DIR "/ToyRobot-Metal.mtl";
const char* TEST_RESOURCE_LOCATION = TEST_RESOURCE_DIR "/";
const float height = textVisual.GetHeightForWidth( 40.f );
DALI_TEST_EQUALS( height, 40.f, Math::MACHINE_EPSILON_1000, TEST_LOCATION );
+ //AnimatedImageVisual
+ Visual::Base animatedImageVisual = factory.CreateVisual( TEST_GIF_FILE_NAME, ImageDimensions() );
+ animatedImageVisual.SetTransformAndSize(DefaultTransform(), controlSize );
+ animatedImageVisual.GetNaturalSize(naturalSize);
+ // TEST_GIF_FILE: anim.gif
+ // resolution: 50*50, frame count: 4, frame delay: 0.2 second for each frame
+ DALI_TEST_EQUALS( naturalSize, Vector2(50.f, 50.f), TEST_LOCATION );
+
END_TEST;
}
END_TEST;
}
+int UtcDaliVisualGetPropertyMap11(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline( "UtcDaliVisualGetPropertyMap7: AnimatedImageVisual" );
+
+ // request SvgVisual with a property map
+ VisualFactory factory = VisualFactory::Get();
+ Property::Map propertyMap;
+ Visual::Base svgVisual = factory.CreateVisual( Property::Map()
+ .Add( Visual::Property::TYPE, Visual::IMAGE )
+ .Add( ImageVisual::Property::URL, TEST_GIF_FILE_NAME ) );
+
+ Property::Map resultMap;
+ svgVisual.CreatePropertyMap( resultMap );
+ // check the property values from the returned map from a visual
+ Property::Value* value = resultMap.Find( Visual::Property::TYPE, Property::INTEGER );
+ DALI_TEST_CHECK( value );
+ DALI_TEST_CHECK( value->Get<int>() == Visual::IMAGE );
+
+ value = resultMap.Find( ImageVisual::Property::URL, Property::STRING );
+ DALI_TEST_CHECK( value );
+ DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
+
+ // request SvgVisual with an URL
+ Visual::Base svgVisual2 = factory.CreateVisual( TEST_GIF_FILE_NAME, ImageDimensions() );
+ resultMap.Clear();
+ svgVisual2.CreatePropertyMap( resultMap );
+ // check the property values from the returned map from a visual
+ value = resultMap.Find( Visual::Property::TYPE, Property::INTEGER );
+ DALI_TEST_CHECK( value );
+ DALI_TEST_CHECK( value->Get<int>() == Visual::IMAGE );
+
+ value = resultMap.Find( ImageVisual::Property::URL, Property::STRING );
+ DALI_TEST_CHECK( value );
+ DALI_TEST_CHECK( value->Get<std::string>() == TEST_GIF_FILE_NAME );
+
+ END_TEST;
+}
+
int UtcDaliVisualGetPropertyMapBatchImageVisualNoAtlas(void)
{
ToolkitTestApplication application;
#include <iostream>
#include <stdlib.h>
#include <dali-toolkit-test-suite-utils.h>
+#include <toolkit-timer.h>
#include <toolkit-bitmap-loader.h>
#include <toolkit-event-thread-callback.h>
#include <dali/public-api/rendering/renderer.h>
const char* TEST_SIMPLE_OBJ_FILE_NAME = TEST_RESOURCE_DIR "/Cube-Points-Only.obj";
const char* TEST_SIMPLE_MTL_FILE_NAME = TEST_RESOURCE_DIR "/ToyRobot-Metal-Simple.mtl";
+// resolution: 50*50, frame count: 4, frame delay: 0.2 second for each frame
+const char* TEST_GIF_FILE_NAME = TEST_RESOURCE_DIR "/anim.gif";
+
// resolution: 34*34, pixel format: RGBA8888
static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png";
// resolution: 600*600, pixel format: RGB888
END_TEST;
}
+
+int UtcDaliVisualFactoryGetAnimatedImageVisual(void)
+{
+ ToolkitTestApplication application;
+ tet_infoline( "UtcDaliVisualFactoryGetAnimatedImageVisual: Request animated image visual with a gif url" );
+
+ VisualFactory factory = VisualFactory::Get();
+ Visual::Base visual = factory.CreateVisual( TEST_GIF_FILE_NAME, ImageDimensions() );
+ DALI_TEST_CHECK( visual );
+
+ TestGlAbstraction& gl = application.GetGlAbstraction();
+ TraceCallStack& textureTrace = gl.GetTextureTrace();
+ textureTrace.Enable(true);
+
+ DummyControl actor = DummyControl::New();
+ DummyControlImpl& dummyImpl = static_cast<DummyControlImpl&>(actor.GetImplementation());
+ dummyImpl.RegisterVisual( Control::CONTROL_PROPERTY_END_INDEX + 1, visual );
+ Stage::GetCurrent().Add( actor );
+
+ application.SendNotification();
+ application.Render();
+
+ // renderer is added to actor
+ DALI_TEST_CHECK( actor.GetRendererCount() == 1u );
+
+ // test the uniforms which used to handle the atlas rect
+ // the four frames should be located inside atlas as follows: atlas size 100*100
+ // -------------
+ // | | |
+ // | 0 | 1 |
+ // -------------
+ // | | |
+ // | 2 | 3 |
+ // -------------
+
+ Renderer renderer = actor.GetRendererAt( 0u );
+ DALI_TEST_CHECK( renderer );
+
+ Property::Value atlasRectValue = renderer.GetProperty( renderer.GetPropertyIndex( "uAtlasRect" ) );
+ // take into consideration the half pixel correction
+ DALI_TEST_EQUALS( atlasRectValue.Get<Vector4>(), Vector4(0.5f, 0.5f, 49.5f, 49.5f)/100.f, Math::MACHINE_EPSILON_100, TEST_LOCATION );
+
+ // waiting for the resource uploading
+ application.SendNotification();
+ application.Render();
+
+ DALI_TEST_EQUALS( textureTrace.FindMethod("BindTexture"), true, TEST_LOCATION );
+
+ // Force the timer used by the animatedImageVisual to tick,
+ Dali::Timer timer = Timer::New( 0 );
+ timer.MockEmitSignal();
+ application.SendNotification();
+ application.Render();
+ atlasRectValue = renderer.GetProperty( renderer.GetPropertyIndex( "uAtlasRect" ) );
+ // take into consideration the half pixel correction
+ DALI_TEST_EQUALS( atlasRectValue.Get<Vector4>(), Vector4(50.5f, 0.5f, 99.5f, 49.5f)/100.f, Math::MACHINE_EPSILON_100, TEST_LOCATION );
+
+ // Force the timer used by the animatedImageVisual to tick,
+ timer.MockEmitSignal();
+ application.SendNotification();
+ application.Render();
+ atlasRectValue = renderer.GetProperty( renderer.GetPropertyIndex( "uAtlasRect" ) );
+ // take into consideration the half pixel correction
+ DALI_TEST_EQUALS( atlasRectValue.Get<Vector4>(), Vector4(0.5f, 50.5f, 49.5f, 99.5f)/100.f, Math::MACHINE_EPSILON_100, TEST_LOCATION );
+
+ // Force the timer used by the animatedImageVisual to tick,
+ timer.MockEmitSignal();
+ application.SendNotification();
+ application.Render();
+ atlasRectValue = renderer.GetProperty( renderer.GetPropertyIndex( "uAtlasRect" ) );
+ // take into consideration the half pixel correction
+ DALI_TEST_EQUALS( atlasRectValue.Get<Vector4>(), Vector4(50.5f, 50.5f, 99.5f, 99.5f)/100.f, Math::MACHINE_EPSILON_100, TEST_LOCATION );
+
+ // Test SetOffStage().
+ actor.Unparent();
+ DALI_TEST_CHECK( actor.GetRendererCount() == 0u );
+
+ END_TEST;
+}
{
}
+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 )
{
// 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>
namespace Toolkit
{
+
namespace Internal DALI_INTERNAL
{
class ImageAtlas;
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.
int numberOfColumns = GetColumns();
int numberOfRows = GetRows();
+ bool lastCell = false;
+ Actor nextValidActor;
+
switch ( direction )
{
case Toolkit::Control::KeyboardFocus::LEFT:
{
- if(--currentColumn < 0)
+ do
{
- currentColumn = numberOfColumns - 1;
- if(--currentRow < 0)
+ if(--currentColumn < 0)
{
- currentRow = loopEnabled ? numberOfRows - 1 : 0;
- focusLost = (currentRow == 0);
+ currentColumn = numberOfColumns - 1;
+ if(--currentRow < 0)
+ {
+ lastCell = true;
+ currentRow = loopEnabled ? numberOfRows - 1 : 0;
+ focusLost = (currentRow == 0);
+ }
}
- }
+ nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+ } while ( !nextValidActor && !lastCell );
break;
}
case Toolkit::Control::KeyboardFocus::RIGHT:
{
- if(++currentColumn > numberOfColumns - 1)
+ do
{
- currentColumn = 0;
- if(++currentRow > numberOfRows - 1)
+ if(++currentColumn > numberOfColumns - 1)
{
- currentRow = loopEnabled ? 0 : numberOfRows - 1;
- focusLost = (currentRow == numberOfRows - 1);
+ currentColumn = 0;
+ if(++currentRow > numberOfRows - 1)
+ {
+ lastCell = true;
+ currentRow = loopEnabled ? 0 : numberOfRows - 1;
+ focusLost = (currentRow == numberOfRows - 1);
+ }
}
- }
+ nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+ } while ( !nextValidActor && !lastCell );
break;
}
case Toolkit::Control::KeyboardFocus::UP:
{
- if(--currentRow < 0)
+ do
{
- currentRow = loopEnabled ? numberOfRows - 1 : 0;
- focusLost = (currentRow == 0);
- }
+ if(--currentRow < 0)
+ {
+ lastCell = true;
+ currentRow = loopEnabled ? numberOfRows - 1 : 0;
+ focusLost = (currentRow == 0);
+ }
+ nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+ } while ( !nextValidActor && !lastCell );
break;
}
case Toolkit::Control::KeyboardFocus::DOWN:
{
- if(++currentRow > numberOfRows - 1)
+ do
{
- currentRow = loopEnabled ? 0 : numberOfRows - 1;
- focusLost = (currentRow == numberOfRows - 1);
- }
+ if(++currentRow > numberOfRows - 1)
+ {
+ lastCell = true;
+ currentRow = loopEnabled ? 0 : numberOfRows - 1;
+ focusLost = (currentRow == numberOfRows - 1);
+ }
+ nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+ } while ( !nextValidActor && !lastCell );
break;
}
}
$(toolkit_src_dir)/visuals/visual-factory-cache.cpp \
$(toolkit_src_dir)/visuals/visual-factory-impl.cpp \
$(toolkit_src_dir)/visuals/visual-string-constants.cpp \
+ $(toolkit_src_dir)/visuals/animated-image/animated-image-visual.cpp \
$(toolkit_src_dir)/visuals/border/border-visual.cpp \
$(toolkit_src_dir)/visuals/color/color-visual.cpp \
$(toolkit_src_dir)/visuals/gradient/gradient.cpp \
// EXTERNAL HEADER
#include <stdlib.h> // For abs()
+#include <dali/integration-api/debug.h>
namespace Dali
{
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 )
}
}
+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
*/
#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
{
*/
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:
/*
*/
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:
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 ),
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.
--- /dev/null
+/*
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// CLASS HEADER
+#include "animated-image-visual.h"
+
+// EXTERNAL INCLUDES
+#include <dali/devel-api/adaptor-framework/gif-loading.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/public-api/visuals/image-visual-properties.h>
+#include <dali-toolkit/devel-api/visuals/visual-properties-devel.h>
+#include <dali-toolkit/internal/visuals/visual-factory-impl.h>
+#include <dali-toolkit/internal/visuals/visual-factory-cache.h>
+#include <dali-toolkit/internal/visuals/visual-string-constants.h>
+#include <dali-toolkit/internal/visuals/visual-base-data-impl.h>
+#include <dali-toolkit/internal/visuals/image/image-visual.h>
+#include <dali-toolkit/devel-api/image-loader/image-atlas.h>
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Internal
+{
+
+AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const std::string& imageUrl, const Property::Map& properties )
+{
+ AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache );
+ visual->mImageUrl = imageUrl;
+ visual->SetProperties( properties );
+
+ return visual;
+}
+
+AnimatedImageVisualPtr AnimatedImageVisual::New( VisualFactoryCache& factoryCache, const std::string& imageUrl )
+{
+ AnimatedImageVisual* visual = new AnimatedImageVisual( factoryCache );
+ visual->mImageUrl = imageUrl;
+
+ return visual;
+}
+
+AnimatedImageVisual::AnimatedImageVisual( VisualFactoryCache& factoryCache )
+: Visual::Base( factoryCache ),
+ mFrameDelayTimer(),
+ mImageUrl(),
+ mImageSize(),
+ mCurrentFrameIndex( 0 )
+{}
+
+AnimatedImageVisual::~AnimatedImageVisual()
+{
+}
+
+void AnimatedImageVisual::GetNaturalSize( Vector2& naturalSize )
+{
+ if( mImageSize.GetWidth() == 0 && mImageSize.GetHeight() == 0)
+ {
+ mImageSize = Dali::GetGifImageSize( mImageUrl );
+ }
+
+ naturalSize.width = mImageSize.GetWidth();
+ naturalSize.height = mImageSize.GetHeight();
+}
+
+void AnimatedImageVisual::DoCreatePropertyMap( Property::Map& map ) const
+{
+ map.Clear();
+ map.Insert( Toolkit::DevelVisual::Property::TYPE, Toolkit::Visual::IMAGE );
+ if( !mImageUrl.empty() )
+ {
+ map.Insert( Toolkit::ImageVisual::Property::URL, mImageUrl );
+ }
+}
+
+void AnimatedImageVisual::DoSetProperties( const Property::Map& propertyMap )
+{
+ // url already passed in from constructor
+}
+
+void AnimatedImageVisual::DoSetOnStage( Actor& actor )
+{
+ Texture texture = PrepareAnimatedImage();
+ if( texture ) // if the image loading is successful
+ {
+ Shader shader = ImageVisual::GetImageShader( mFactoryCache, true, true );
+ Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
+ TextureSet textureSet = TextureSet::New();
+ mImpl->mRenderer = Renderer::New( geometry, shader );
+ mImpl->mRenderer.SetTextures( textureSet );
+ textureSet.SetTexture( 0u, PrepareAnimatedImage() );
+ mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[0] );
+
+ // Register transform properties
+ mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
+
+ mCurrentFrameIndex = 0;
+ if( mFrameDelayContainer.Count() > 1 )
+ {
+ mFrameDelayTimer = Timer::New( mFrameDelayContainer[0] );
+ mFrameDelayTimer.TickSignal().Connect( this, &AnimatedImageVisual::DisplayNextFrame );
+ mFrameDelayTimer.Start();
+ }
+
+ actor.AddRenderer( mImpl->mRenderer );
+ }
+}
+
+void AnimatedImageVisual::DoSetOffStage( Actor& actor )
+{
+ if( !mImpl->mRenderer )
+ {
+ return;
+ }
+
+ if( mFrameDelayTimer )
+ {
+ mFrameDelayTimer.Stop();
+ mFrameDelayTimer.Reset();
+ }
+
+ mTextureRectContainer.Clear();
+ mFrameDelayContainer.Clear();
+
+ actor.RemoveRenderer( mImpl->mRenderer );
+ mImpl->mRenderer.Reset();
+}
+
+void AnimatedImageVisual::OnSetTransform()
+{
+ if( mImpl->mRenderer )
+ {
+ mImpl->mTransform.RegisterUniforms( mImpl->mRenderer, Direction::LEFT_TO_RIGHT );
+ }
+}
+
+Texture AnimatedImageVisual::PrepareAnimatedImage()
+{
+ // load from image file
+ std::vector<Dali::PixelData> pixelDataList;
+ if( Dali::LoadAnimatedGifFromFile( mImageUrl.c_str() , pixelDataList, mFrameDelayContainer ) )
+ {
+ mImageSize.SetWidth( pixelDataList[0].GetWidth() );
+ mImageSize.SetHeight( pixelDataList[0].GetHeight() );
+
+ return Toolkit::ImageAtlas::PackToAtlas( pixelDataList, mTextureRectContainer );
+ }
+
+ return Texture();
+}
+
+bool AnimatedImageVisual::DisplayNextFrame()
+{
+ mCurrentFrameIndex = (mCurrentFrameIndex+1) % mFrameDelayContainer.Count();
+ mImpl->mRenderer.RegisterProperty( ATLAS_RECT_UNIFORM_NAME, mTextureRectContainer[mCurrentFrameIndex] );
+ if( mFrameDelayTimer.GetInterval() != mFrameDelayContainer[mCurrentFrameIndex] )
+ {
+ mFrameDelayTimer.SetInterval( mFrameDelayContainer[mCurrentFrameIndex] );
+ }
+
+ return true;
+}
+
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
--- /dev/null
+#ifndef DALI_TOOLKIT_INTERNAL_ANIMATED_IMAGE_VISUAL_H
+#define DALI_TOOLKIT_INTERNAL_ANIMATED_IMAGE_VISUAL_H
+
+/*
+ * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// EXTERNAL INCLUDES
+#include <dali/public-api/common/intrusive-ptr.h>
+#include <dali/public-api/common/dali-vector.h>
+#include <dali/public-api/math/vector4.h>
+#include <dali/public-api/adaptor-framework/timer.h>
+
+// INTERNAL INCLUDES
+#include <dali-toolkit/internal/visuals/visual-base-impl.h>
+
+namespace Dali
+{
+
+namespace Toolkit
+{
+
+namespace Internal
+{
+
+class AnimatedImageVisual;
+typedef IntrusivePtr< AnimatedImageVisual > AnimatedImageVisualPtr;
+
+/**
+ * The visual which renders an animated image
+ *
+ * The following property is essential
+ *
+ * | %Property Name | Type |
+ * |--------------------------|------------------|
+ * | url | STRING |
+ *
+ */
+
+class AnimatedImageVisual : public Visual::Base, public ConnectionTracker
+{
+
+public:
+
+ /**
+ * @brief Create the animated image Visual using the image URL.
+ *
+ * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
+ * @param[in] imageUrl The URL to svg resource to use
+ * @param[in] properties A Property::Map containing settings for this visual
+ * @return A smart-pointer to the newly allocated visual.
+ */
+ static AnimatedImageVisualPtr New( VisualFactoryCache& factoryCache, const std::string& imageUrl, const Property::Map& properties );
+
+ /**
+ * @brief Create the animated image visual using the image URL.
+ *
+ * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
+ * @param[in] imageUrl The URL to animated image resource to use
+ */
+ static AnimatedImageVisualPtr New( VisualFactoryCache& factoryCache, const std::string& imageUrl );
+
+public: // from Visual
+
+ /**
+ * @copydoc Visual::Base::GetNaturalSize
+ */
+ virtual void GetNaturalSize( Vector2& naturalSize );
+
+ /**
+ * @copydoc Visual::Base::CreatePropertyMap
+ */
+ virtual void DoCreatePropertyMap( Property::Map& map ) const;
+
+protected:
+
+ /**
+ * @brief Constructor.
+ *
+ * @param[in] factoryCache A pointer pointing to the VisualFactoryCache object
+ */
+ AnimatedImageVisual( VisualFactoryCache& factoryCache );
+
+ /**
+ * @brief A reference counted object may only be deleted by calling Unreference().
+ */
+ virtual ~AnimatedImageVisual();
+
+ /**
+ * @copydoc Visual::Base::DoSetProperties
+ */
+ virtual void DoSetProperties( const Property::Map& propertyMap );
+
+ /**
+ * @copydoc Visual::Base::DoSetOnStage
+ */
+ virtual void DoSetOnStage( Actor& actor );
+
+ /**
+ * @copydoc Visual::Base::DoSetOffStage
+ */
+ virtual void DoSetOffStage( Actor& actor );
+
+ /**
+ * @copydoc Visual::Base::OnSetTransform
+ */
+ virtual void OnSetTransform();
+
+private:
+
+ /**
+ * Load the animated image and pack the frames into atlas.
+ * @return That atlas texture.
+ */
+ Texture PrepareAnimatedImage();
+
+ /**
+ * Display the next frame. It is called when the mFrameDelayTimes ticks.
+ */
+ bool DisplayNextFrame();
+
+ // Undefined
+ AnimatedImageVisual( const AnimatedImageVisual& animatedImageVisual );
+
+ // Undefined
+ AnimatedImageVisual& operator=( const AnimatedImageVisual& animatedImageVisual );
+
+private:
+
+ Timer mFrameDelayTimer;
+ Dali::Vector<Vector4> mTextureRectContainer;
+ Dali::Vector<uint32_t> mFrameDelayContainer;
+ std::string mImageUrl;
+ ImageDimensions mImageSize;
+ uint32_t mCurrentFrameIndex;
+};
+
+} // namespace Internal
+
+} // namespace Toolkit
+
+} // namespace Dali
+#endif /* DALI_TOOLKIT_INTERNAL_ANIMATED_IMAGE_VISUAL_H */
void ColorVisual::InitializeRenderer()
{
Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
- if( !geometry )
- {
- geometry = VisualFactoryCache::CreateQuadGeometry();
- mFactoryCache.SaveGeometry( VisualFactoryCache::QUAD_GEOMETRY, geometry );
- }
Shader shader = mFactoryCache.GetShader( VisualFactoryCache::COLOR_SHADER );
if( !shader )
void GradientVisual::InitializeRenderer()
{
Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
- if( !geometry )
- {
- geometry = VisualFactoryCache::CreateQuadGeometry();
- mFactoryCache.SaveGeometry( VisualFactoryCache::QUAD_GEOMETRY, geometry );
- }
Toolkit::GradientVisual::Units::Type gradientUnits = mGradient->GetGradientUnits();
VisualFactoryCache::ShaderType shaderType = GetShaderType( mGradientType, gradientUnits );
if( gridSize == ImageDimensions( 1, 1 ) )
{
geometry = factoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
- if( !geometry )
- {
- geometry = VisualFactoryCache::CreateQuadGeometry();
- factoryCache.SaveGeometry( VisualFactoryCache::QUAD_GEOMETRY, geometry );
- }
}
else
{
{
Shader shader = ImageVisual::GetImageShader( mFactoryCache, true, true );
Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
- if( !geometry )
- {
- geometry = mFactoryCache.CreateQuadGeometry();
- mFactoryCache.SaveGeometry( VisualFactoryCache::QUAD_GEOMETRY, geometry );
- }
TextureSet textureSet = TextureSet::New();
mImpl->mRenderer = Renderer::New( geometry, shader );
mImpl->mRenderer.SetTextures( textureSet );
mControl = actor;
Geometry geometry = mFactoryCache.GetGeometry( VisualFactoryCache::QUAD_GEOMETRY );
- if( !geometry )
- {
- geometry = VisualFactoryCache::CreateQuadGeometry();
- mFactoryCache.SaveGeometry( VisualFactoryCache::QUAD_GEOMETRY , geometry );
- }
Shader shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER_ATLAS_CLAMP );
mFactoryCache.SaveShader( VisualFactoryCache::IMAGE_SHADER_ATLAS_DEFAULT_WRAP, shader );
Geometry VisualFactoryCache::GetGeometry( GeometryType type )
{
+ if( !mGeometry[type] && type == QUAD_GEOMETRY )
+ {
+ mGeometry[type] = CreateQuadGeometry();
+ }
+
return mGeometry[type];
}
#include <dali-toolkit/internal/visuals/primitive/primitive-visual.h>
#include <dali-toolkit/internal/visuals/svg/svg-visual.h>
#include <dali-toolkit/internal/visuals/text/text-visual.h>
+#include <dali-toolkit/internal/visuals/animated-image/animated-image-visual.h>
#include <dali-toolkit/internal/visuals/wireframe/wireframe-visual.h>
#include <dali-toolkit/internal/visuals/visual-factory-cache.h>
#include <dali-toolkit/internal/visuals/visual-factory-resolve-url.h>
{
visualPtr = SvgVisual::New( *( mFactoryCache.Get() ), imageUrl, propertyMap );
}
+ else if( UrlType::GIF == type )
+ {
+ visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), imageUrl, propertyMap );
+ }
else // Regular image
{
bool batchingEnabled( false );
{
visualPtr = SvgVisual::New( *( mFactoryCache.Get() ), url );
}
+ else if( UrlType::GIF == type )
+ {
+ visualPtr = AnimatedImageVisual::New( *( mFactoryCache.Get() ), url );
+ }
else // Regular image
{
visualPtr = ImageVisual::New( *( mFactoryCache.Get() ), url, size );
{
REGULAR_IMAGE,
N_PATCH,
- SVG
+ SVG,
+ GIF
};
}
// parsing from the end for better chance of early outs
enum { SUFFIX, HASH, HASH_DOT } state = SUFFIX;
char SVG[ 4 ] = { 'g', 'v', 's', '.' };
+ char GIF[ 4 ] = { 'f', 'i', 'g', '.' };
unsigned int svgScore = 0;
+ unsigned int gifScore = 0;
int index = count;
while( --index >= 0 )
{
return UrlType::SVG;
}
}
+ if( ( offsetFromEnd < sizeof(GIF) )&&( tolower( currentChar ) == GIF[ offsetFromEnd ] ) )
+ {
+ // early out if GIF
+ if( ++gifScore == sizeof(GIF) )
+ {
+ return UrlType::GIF;
+ }
+ }
switch( state )
{
case SUFFIX:
| Dali::Toolkit::Visual::Transform::Property::OFFSET | offset | VECTOR2 | No | The offset of the visual. |
| Dali::Toolkit::Visual::Transform::Property::SIZE | size | VECTOR2 | No | The size of the visual. |
| Dali::Toolkit::Visual::Transform::Property::OFFSET_SIZE_MODE | offsetSizeMode | VECTOR4 | No | Whether the size or offset components are Relative or Absolute [More info](@ref offset-size-mode)|
-| Dali::Toolkit::Visual::Transform::Property::ORIGIN | origin | INTEGER or STRING | No | The origin of the visual within the control's area. [More info](@ref align-type)] |
+| Dali::Toolkit::Visual::Transform::Property::ORIGIN | origin | INTEGER or STRING | No | The origin of the visual within the control's area. [More info](@ref align-type) |
| Dali::Toolkit::Visual::Transform::Property::ANCHOR_POINT | anchorPoint | INTEGER or STRING | No | The anchor point of the visual. [More info](@ref align-type)|
+ [Normal (Quad)](@ref image-visual)
+ [N-Patch](@ref n-patch-visual)
+ [SVG](@ref svg-visual)
+ + [Animated Image]( @ref animated-image-visual )
___________________________
url : "path-to-image.svg"
};
~~~
+
+___________________________________________________________________________________________________
+
+## Animated Image Visual {#animated-image-visual}
+
+Renders an animated image into the visual's quad geometry. Currently, only the GIF format is supported.
+
+![ ](../assets/img/visuals/animated-image-visual.gif)
+![ ](animated-image-visual.gif)
+
+#### Properties Supported
+
+**VisualType:** Dali::Toolkit::Visual::IMAGE, "IMAGE"
+
+| Property | String | Type | Required | Description |
+|-------------------------------------------|--------|:-------:|:--------:|----------------------------------|
+| Dali::Toolkit::ImageVisual::Property::URL | url | STRING | Yes | The URL of the animated image. |
+
+#### Usage
+
+~~~{.cpp}
+// C++
+Dali::Toolkit::Control control = Dali::Toolkit::Control::New();
+
+control.SetProperty( Control::Property::BACKGROUND,
+ Property::Map().Add( Visual::Property::TYPE, Dali::Toolkit::Visual::IMAGE )
+ .Add( Dali::Toolkit::ImageVisual::Property::URL, "path-to-image.gif" ) );
+~~~
+
+~~~{.js}
+// JavaScript
+var control = new dali.Control( "Control" );
+
+control.background =
+{
+ visualType : "IMAGE",
+ url : "path-to-image.gif"
+};
+~~~
___________________________________________________________________________________________________
## Border Visual {#border-visual}