From: Xiangyin Ma Date: Wed, 21 Oct 2015 15:52:35 +0000 (+0100) Subject: Automatic image atlasing X-Git-Tag: dali_1.1.10~9^2 X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=commitdiff_plain;h=4c6d34154c81cf86b4c3662b81e1223f3326ceb5;hp=4bf366c7c657eacb5cf150fa52e34263ca16218f Automatic image atlasing Change-Id: Iccff3a5aad466a696211b95f29d1411009cbf102 --- diff --git a/automated-tests/resources/gallery-small-1.jpg b/automated-tests/resources/gallery-small-1.jpg new file mode 100644 index 0000000..9292310 Binary files /dev/null and b/automated-tests/resources/gallery-small-1.jpg differ diff --git a/automated-tests/resources/icon-delete.png b/automated-tests/resources/icon-delete.png new file mode 100644 index 0000000..f509cc0 Binary files /dev/null and b/automated-tests/resources/icon-delete.png differ diff --git a/automated-tests/resources/icon-edit.png b/automated-tests/resources/icon-edit.png new file mode 100644 index 0000000..ce3e327 Binary files /dev/null and b/automated-tests/resources/icon-edit.png differ diff --git a/automated-tests/src/dali-toolkit/CMakeLists.txt b/automated-tests/src/dali-toolkit/CMakeLists.txt index 9b656a7..d6e2eaa 100644 --- a/automated-tests/src/dali-toolkit/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit/CMakeLists.txt @@ -51,6 +51,7 @@ SET(TC_SOURCES utc-Dali-Model3dView.cpp utc-Dali-ControlRenderer.cpp utc-Dali-RendererFactory.cpp + utc-Dali-ImageAtlas.cpp ) # Append list of test harness files (Won't get parsed for test cases) @@ -59,6 +60,7 @@ LIST(APPEND TC_SOURCES dali-toolkit-test-utils/toolkit-accessibility-adaptor.cpp dali-toolkit-test-utils/toolkit-application.cpp dali-toolkit-test-utils/toolkit-clipboard.cpp + dali-toolkit-test-utils/toolkit-event-thread-callback.cpp dali-toolkit-test-utils/toolkit-imf-manager.cpp dali-toolkit-test-utils/toolkit-physical-keyboard.cpp dali-toolkit-test-utils/toolkit-style-monitor.cpp @@ -86,6 +88,8 @@ PKG_CHECK_MODULES(${CAPI_LIB} REQUIRED SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -ggdb --coverage -Wall -Werror=return-type") +ADD_DEFINITIONS(-DTEST_RESOURCE_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../resources\" ) + FOREACH(directory ${${CAPI_LIB}_LIBRARY_DIRS}) SET(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} -L${directory}") ENDFOREACH(directory ${CAPI_LIB_LIBRARY_DIRS}) @@ -99,6 +103,7 @@ INCLUDE_DIRECTORIES( ADD_EXECUTABLE(${EXEC_NAME} ${EXEC_NAME}.cpp ${TC_SOURCES}) TARGET_LINK_LIBRARIES(${EXEC_NAME} ${${CAPI_LIB}_LIBRARIES} + -lpthread ) INSTALL(PROGRAMS ${EXEC_NAME} diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp new file mode 100644 index 0000000..e8ee062 --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2015 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 "toolkit-event-thread-callback.h" + +// EXTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ + +namespace +{ +EventThreadCallback* gEventThreadCallback = NULL; +} + +struct EventThreadCallback::Impl +{ + CallbackBase* callback; + unsigned int triggeredCount; + unsigned int expectedCount; + sem_t mySemaphore; +}; + +EventThreadCallback::EventThreadCallback( CallbackBase* callback ) +: mImpl( new Impl() ) +{ + mImpl->callback = callback; + mImpl->triggeredCount = 0u; + mImpl->expectedCount = INFINITY; + sem_init( &(mImpl->mySemaphore), 0, 0 ); + gEventThreadCallback = this; +} + +EventThreadCallback::~EventThreadCallback() +{ + delete mImpl; +} + +void EventThreadCallback::Trigger() +{ + mImpl->triggeredCount++; + if( mImpl->triggeredCount >= mImpl->expectedCount ) + { + sem_post( &(mImpl->mySemaphore) ); + } +} + +void EventThreadCallback::WaitingForTrigger(unsigned int count) +{ + if( mImpl->triggeredCount >= count ) + { + return; + } + mImpl->expectedCount = count; + sem_wait( &(mImpl->mySemaphore) ); +} + +CallbackBase* EventThreadCallback::GetCallback() +{ + return mImpl->callback; +} + +EventThreadCallback* EventThreadCallback::Get() +{ + return gEventThreadCallback; +} + +} diff --git a/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h new file mode 100644 index 0000000..d67ca24 --- /dev/null +++ b/automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h @@ -0,0 +1,61 @@ +#ifndef __DALI_TOOLKIT_EVENT_THREAD_CALLBACK_H__ +#define __DALI_TOOLKIT_EVENT_THREAD_CALLBACK_H__ + +/* + * Copyright (c) 2015 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. + * + */ +#define __DALI_EVENT_THREAD_CALLBACK_H_ + +// EXTERNAL INCLUDES +#include +#include + +namespace Dali +{ + +class DALI_IMPORT_API EventThreadCallback +{ +public: + + EventThreadCallback( CallbackBase* callback ); + + ~EventThreadCallback(); + + void Trigger(); + + void WaitingForTrigger(unsigned int count); + + CallbackBase* GetCallback(); + + static EventThreadCallback* Get(); + +private: + + // undefined copy constructor. + EventThreadCallback( const EventThreadCallback& ); + + // undefined assignment operator + EventThreadCallback& operator=( const EventThreadCallback& ); + +private: + + struct Impl; + Impl* mImpl; +}; + +} + +#endif /* __DALI_TOOLKIT_EVENT_THREAD_CALLBACK_H__ */ diff --git a/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp new file mode 100644 index 0000000..3883553 --- /dev/null +++ b/automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (c) 2015 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Dali; +using namespace Dali::Toolkit; + +namespace +{ +// resolution: 34*34, pixel format: RGBA8888 +static const char* gImage_34_RGBA = TEST_RESOURCE_DIR "/icon-edit.png"; +// resolution: 50*50, pixel format: RGBA8888 +static const char* gImage_50_RGBA = TEST_RESOURCE_DIR "/icon-delete.png"; +// resolution: 128*128, pixel format: RGB888 +static const char* gImage_128_RGB = TEST_RESOURCE_DIR "/gallery-small-1.jpg"; + +// this is image is not exist, for negative test +static const char* gImageNonExist = "non-exist.jpg"; + +const int RENDER_FRAME_INTERVAL = 16; ///< Duration of each frame in ms. (at approx 60FPS) + +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 ); + + return pixelArea; +} + +bool IsOverlap( Rect rect1, Rect rect2 ) +{ + return rect1.x < rect2.x+rect2.width + && rect2.x < rect1.x+rect1.width + && rect1.y < rect2.y+rect2.height + && rect2.y < rect1.y+rect1.height; +} + +} + +void dali_image_atlas_startup(void) +{ + test_return_value = TET_UNDEF; +} + +void dali_image_atlas_cleanup(void) +{ + test_return_value = TET_PASS; +} + +int UtcDaliImageAtlasNew(void) +{ + ToolkitTestApplication application; + + // invoke default handle constructor + ImageAtlas atlas; + + DALI_TEST_CHECK( !atlas ); + + // initialise handle + atlas = ImageAtlas::New( 32, 32 ); + + DALI_TEST_CHECK( atlas ); + END_TEST; +} + +int UtcDaliImageAtlasCopyConstructor(void) +{ + ToolkitTestApplication application; + + ImageAtlas atlas = ImageAtlas::New( 32, 32); + ImageAtlas atlasCopy(atlas); + + DALI_TEST_EQUALS( (bool)atlasCopy, true, TEST_LOCATION ); + END_TEST; +} + +int UtcDaliImageAtlasAssignmentOperator(void) +{ + ToolkitTestApplication application; + + ImageAtlas atlas = ImageAtlas::New( 32, 32 ); + + ImageAtlas atlas2; + DALI_TEST_EQUALS( (bool)atlas2, false, TEST_LOCATION ); + + atlas2 = atlas; + DALI_TEST_EQUALS( (bool)atlas2, true, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageAtlasGetAtlas(void) +{ + ToolkitTestApplication application; + + ImageAtlas atlas = ImageAtlas::New( 32, 32 ); + Image image = atlas.GetAtlas(); + + // test the atlas created + DALI_TEST_EQUALS( (bool)image, true, TEST_LOCATION ); + DALI_TEST_CHECK( image.GetHeight() == 32u ); + DALI_TEST_CHECK( image.GetWidth() == 32u ); + + Atlas coreAtlas = Atlas::DownCast( image ); + DALI_TEST_EQUALS( (bool)coreAtlas, true, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageAtlasSetBrokenImage(void) +{ + ToolkitTestApplication application; + unsigned int size = 200; + ImageAtlas atlas = ImageAtlas::New( size, size ); + + Vector4 textureRect; + atlas.Upload( textureRect, gImageNonExist ); + DALI_TEST_EQUALS( textureRect, Vector4::ZERO, TEST_LOCATION ); + + // Set broken image + TestPlatformAbstraction& platform = application.GetPlatform(); + platform.SetClosestImageSize(Vector2( 34, 34)); + atlas.SetBrokenImage( gImage_34_RGBA ); + + // the non-exit image will be replaced with the broken image + platform.SetClosestImageSize(Vector2( 0, 0)); + atlas.Upload( textureRect, gImageNonExist ); + + Rect pixelArea = TextureCoordinateToPixelArea(textureRect, size); + DALI_TEST_EQUALS( pixelArea.width, 34.f, TEST_LOCATION ); + DALI_TEST_EQUALS( pixelArea.height, 34.f, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageAtlasUploadP(void) +{ + ToolkitTestApplication application; + unsigned int size = 200; + ImageAtlas atlas = ImageAtlas::New( size, size ); + + EventThreadCallback* eventTrigger = EventThreadCallback::Get(); + CallbackBase* callback = eventTrigger->GetCallback(); + + TraceCallStack& callStack = application.GetGlAbstraction().GetTextureTrace(); + callStack.Reset(); + callStack.Enable(true); + + Vector4 textureRect1; + atlas.Upload( textureRect1, gImage_34_RGBA, ImageDimensions(34, 34) ); + Vector4 textureRect2; + atlas.Upload( textureRect2, gImage_50_RGBA, ImageDimensions(50, 50) ); + Vector4 textureRect3; + atlas.Upload( textureRect3, gImage_128_RGB, ImageDimensions(128, 128) ); + + eventTrigger->WaitingForTrigger( 3 );// waiting until all three images are loaded + + CallbackBase::Execute( *callback ); + + application.SendNotification(); + application.Render(RENDER_FRAME_INTERVAL); + + callStack.Enable(false); + + Rect pixelArea1 = TextureCoordinateToPixelArea(textureRect1, size); + DALI_TEST_EQUALS( pixelArea1.width, 34, TEST_LOCATION ); + DALI_TEST_EQUALS( pixelArea1.height, 34, TEST_LOCATION ); + std::stringstream out; + out< pixelArea2 = TextureCoordinateToPixelArea(textureRect2, size); + DALI_TEST_EQUALS( pixelArea2.width, 50, TEST_LOCATION ); + DALI_TEST_EQUALS( pixelArea2.height, 50, TEST_LOCATION ); + out.str(""); + out< pixelArea3 = TextureCoordinateToPixelArea(textureRect3, size); + DALI_TEST_EQUALS( pixelArea3.width, 128, TEST_LOCATION ); + DALI_TEST_EQUALS( pixelArea3.height, 128, TEST_LOCATION ); + out.str(""); + out< pixelArea = TextureCoordinateToPixelArea(textureRect2, size); + DALI_TEST_EQUALS( pixelArea.x, 0.f, TEST_LOCATION ); + DALI_TEST_EQUALS( pixelArea.y, 0.f, TEST_LOCATION ); + + END_TEST; +} + +int UtcDaliImageAtlasImageView(void) +{ + ToolkitTestApplication application; + + TraceCallStack& callStack = application.GetGlAbstraction().GetTextureTrace(); + callStack.Reset(); + callStack.Enable(true); + + ImageView imageView1 = ImageView::New( gImage_34_RGBA, ImageDimensions(34, 34) ); + ImageView imageView2 = ImageView::New( gImage_50_RGBA, ImageDimensions(50, 50) ); + Stage::GetCurrent().Add( imageView1 ); + Stage::GetCurrent().Add( imageView2 ); + + EventThreadCallback* eventTrigger = EventThreadCallback::Get(); + while( eventTrigger == NULL) // waiting uintil the ImageAtlas is created by ImageAtlasManager + { + usleep(10); + eventTrigger = EventThreadCallback::Get(); + } + CallbackBase* callback = eventTrigger->GetCallback(); + + eventTrigger->WaitingForTrigger( 2 );// waiting until both images are loaded + + CallbackBase::Execute( *callback ); + + application.SendNotification(); + application.Render(RENDER_FRAME_INTERVAL); + + callStack.Enable(false); + + DALI_TEST_CHECK( callStack.FindMethodAndParams("TexSubImage2D", "0, 0, 34, 34" ) ); + DALI_TEST_CHECK( callStack.FindMethodAndParams("TexSubImage2D", "0, 34, 50, 50" ) ); + + callStack.Reset(); + callStack.Enable(true); + + // remove the imageView2 from stage, the second image will also be removed from atlas + // then the space on the atlas will be used by the third image added. + Stage::GetCurrent().Remove( imageView2 ); + application.SendNotification(); + application.Render(RENDER_FRAME_INTERVAL); + ImageView imageView3 = ImageView::New( gImage_128_RGB, ImageDimensions(100, 100) ); + Stage::GetCurrent().Add( imageView3 ); + + eventTrigger->WaitingForTrigger( 3 ); // waiting for the third image loaded + CallbackBase::Execute( *callback ); + + application.SendNotification(); + application.Render(RENDER_FRAME_INTERVAL); + + callStack.Enable(false); + DALI_TEST_CHECK( callStack.FindMethodAndParams("TexSubImage2D", "0, 34, 100, 100" ) ); + + END_TEST; +} diff --git a/build/tizen/dali-toolkit/Makefile.am b/build/tizen/dali-toolkit/Makefile.am index 50bd34e..833348a 100644 --- a/build/tizen/dali-toolkit/Makefile.am +++ b/build/tizen/dali-toolkit/Makefile.am @@ -99,6 +99,7 @@ develapisliderdir = $(develapicontrolsdir)/slider develapishadowviewdir = $(develapicontrolsdir)/shadow-view develapisuperblurviewdir = $(develapicontrolsdir)/super-blur-view develapifocusmanagerdir = $(develapidir)/focus-manager +develapiimageatlasdir = $(develapidir)/image-atlas develapiscriptingdir = $(develapidir)/scripting develapishadereffectsdir = $(develapidir)/shader-effects develapitransitioneffectsdir = $(develapidir)/transition-effects @@ -112,6 +113,7 @@ develapibubbleemitter_HEADERS = $(devel_api_bubble_emitter_header_files) develapibuilder_HEADERS = $(devel_api_builder_header_files) develapieffectsview_HEADERS = $(devel_api_effects_view_header_files) develapifocusmanager_HEADERS = $(devel_api_focus_manager_header_files) +develapiimageatlas_HEADERS = $(devel_api_image_atlas_header_files) develapimagnifier_HEADERS = $(devel_api_magnifier_header_files) develapipopup_HEADERS = $(devel_api_popup_header_files) develapirendererfactory_HEADERS = $(devel_api_renderer_factory_header_files) diff --git a/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.cpp b/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.cpp index 3ed8e73..6eaaea2 100644 --- a/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.cpp +++ b/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.cpp @@ -112,14 +112,14 @@ void RendererFactory::ResetRenderer( ControlRenderer& renderer, Actor& actor, co GetImplementation( *this ).ResetRenderer( renderer, actor, image ); } -ControlRenderer RendererFactory::GetControlRenderer( const std::string& url ) +ControlRenderer RendererFactory::GetControlRenderer( const std::string& url, ImageDimensions size ) { - return GetImplementation( *this ).GetControlRenderer( url ); + return GetImplementation( *this ).GetControlRenderer( url, size ); } -void RendererFactory::ResetRenderer( ControlRenderer& renderer, Actor& actor, const std::string& url ) +void RendererFactory::ResetRenderer( ControlRenderer& renderer, Actor& actor, const std::string& url, ImageDimensions size ) { - GetImplementation( *this ).ResetRenderer( renderer, actor, url ); + GetImplementation( *this ).ResetRenderer( renderer, actor, url, size ); } void RendererFactory::ResetRenderer( ControlRenderer& renderer, Actor& actor, const Property::Map& propertyMap ) diff --git a/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.h b/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.h index 6d7a4b8..5d4052b 100644 --- a/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.h +++ b/dali-toolkit/devel-api/controls/renderer-factory/renderer-factory.h @@ -19,6 +19,7 @@ // EXTERNAL INCLUDES #include +#include // INTERNAK INCLUDES #include @@ -147,9 +148,11 @@ public: * @brief Request the control renderer to render the given resource at the url. * * @param[in] url The URL to the resource to be rendered. + * @param[in] size The width and height to fit the loaded image to. * @return The pointer pointing to the control renderer */ - ControlRenderer GetControlRenderer( const std::string& url ); + ControlRenderer GetControlRenderer( const std::string& url, + ImageDimensions size = ImageDimensions() ); /** * @brief Request the current control renderer to render the given resource at the url @@ -160,8 +163,10 @@ public: * @param[in] renderer The ControlRenderer to reset * @param[in] actor The Actor the renderer is applied to if, empty if the renderer has not been applied to any Actor * @param[in] url The URL to the resource to be rendered. + * @param[in] size The width and height to fit the loaded image to. */ - void ResetRenderer( ControlRenderer& renderer, Actor& actor, const std::string& url ); + void ResetRenderer( ControlRenderer& renderer, Actor& actor, const std::string& url, + ImageDimensions size = ImageDimensions() ); /** diff --git a/dali-toolkit/devel-api/file.list b/dali-toolkit/devel-api/file.list index bf62a04..f234c23 100755 --- a/dali-toolkit/devel-api/file.list +++ b/dali-toolkit/devel-api/file.list @@ -19,6 +19,7 @@ devel_api_src_files = \ $(devel_api_src_dir)/controls/text-controls/text-selection-toolbar.cpp \ $(devel_api_src_dir)/controls/tool-bar/tool-bar.cpp \ $(devel_api_src_dir)/focus-manager/keyinput-focus-manager.cpp \ + $(devel_api_src_dir)/image-atlas/image-atlas.cpp \ $(devel_api_src_dir)/styling/style-manager.cpp \ $(devel_api_src_dir)/scripting/script.cpp \ $(devel_api_src_dir)/transition-effects/cube-transition-cross-effect.cpp \ @@ -62,6 +63,9 @@ devel_api_slider_header_files = \ devel_api_focus_manager_header_files = \ $(devel_api_src_dir)/focus-manager/keyinput-focus-manager.h +devel_api_image_atlas_header_files = \ + $(devel_api_src_dir)/image-atlas/image-atlas.h + devel_api_styling_header_files = \ $(devel_api_src_dir)/styling/style-manager.h diff --git a/dali-toolkit/devel-api/image-atlas/image-atlas.cpp b/dali-toolkit/devel-api/image-atlas/image-atlas.cpp new file mode 100644 index 0000000..3aa9fd2 --- /dev/null +++ b/dali-toolkit/devel-api/image-atlas/image-atlas.cpp @@ -0,0 +1,87 @@ + /* + * Copyright (c) 2015 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 "image-atlas.h" + +// INTERNAL INCLUDES +#include + +namespace Dali +{ + +namespace Toolkit +{ + +ImageAtlas::ImageAtlas() +{ +} + +ImageAtlas::~ImageAtlas() +{ +} + +ImageAtlas::ImageAtlas(Internal::ImageAtlas* internal) +: BaseHandle( internal ) +{ +} + +ImageAtlas::ImageAtlas( const ImageAtlas& handle ) +: BaseHandle( handle ) +{ +} + +ImageAtlas& ImageAtlas::operator=( const ImageAtlas& handle ) +{ + BaseHandle::operator=(handle); + return *this; +} + +ImageAtlas ImageAtlas::New(SizeType width, SizeType height, + Pixel::Format pixelFormat) +{ + IntrusivePtr internal = Internal::ImageAtlas::New( width, height, pixelFormat); + return ImageAtlas( internal.Get() ); +} + +Image ImageAtlas::GetAtlas() +{ + return GetImplementation( *this ).GetAtlas(); +} + +void ImageAtlas::SetBrokenImage( const std::string& brokenImageUrl ) +{ + GetImplementation( *this ).SetBrokenImage( brokenImageUrl ); +} + +bool ImageAtlas::Upload( Vector4& textureRect, + const std::string& url, + ImageDimensions size, + FittingMode::Type fittingMode, + bool orientationCorrection ) +{ + return GetImplementation(*this).Upload( textureRect, url, size, fittingMode, orientationCorrection ); +} + +void ImageAtlas::Remove(const Vector4& textureRect) +{ + GetImplementation(*this).Remove( textureRect ); +} + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/devel-api/image-atlas/image-atlas.h b/dali-toolkit/devel-api/image-atlas/image-atlas.h new file mode 100644 index 0000000..cef3eb7 --- /dev/null +++ b/dali-toolkit/devel-api/image-atlas/image-atlas.h @@ -0,0 +1,148 @@ +#ifndef __DALI_TOOLKIT_IMAGE_ATLAS_H__ +#define __DALI_TOOLKIT_IMAGE_ATLAS_H__ +/* + * Copyright (c) 2015 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 +#include +#include +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal DALI_INTERNAL +{ +class ImageAtlas; +} + +/** + * @brief An ImageAtlas is a large image containing multiple smaller images. + * + * Only images with url provided are supported for uploading. + * The image are loaded by a worker thread to avoid blocking the main event thread. + */ +class DALI_IMPORT_API ImageAtlas : public BaseHandle +{ +public: + + typedef uint32_t SizeType; + +public: + + /** + * @brief Create a new ImageAtlas. + * + * @param [in] width The atlas width in pixels. + * @param [in] height The atlas height in pixels. + * @param [in] pixelFormat The pixel format (rgba 32 bit by default). + * @return A handle to a new ImageAtlas. + */ + static ImageAtlas New( SizeType width, SizeType height, + Pixel::Format pixelFormat = Pixel::RGBA8888 ); + + /** + * @brief Create an empty handle. + * + * Calling member functions of an empty handle is not allowed. + */ + ImageAtlas(); + + /** + * @brief Destructor. + */ + ~ImageAtlas(); + + /** + * @brief This copy constructor is required for (smart) pointer semantics. + * + * @param [in] handle A reference to the copied handle + */ + ImageAtlas( const ImageAtlas& handle ); + + /** + * @brief This assignment operator is required for (smart) pointer semantics. + * + * @param [in] rhs A reference to the copied handle + * @return A reference to this + */ + ImageAtlas& operator=( const ImageAtlas& handle ); + + /** + * @brief Get the atlas image. + * + * This atlas image is still valid after destroying the ImageAtlas object. + * + * @return the atlas image with type of Dali::Atlas + */ + Image GetAtlas(); + + /** + * @brief Set the broken image which is used to replace the image if loading fails. + * + * @param[in] brokenImageUrl The url of the broken image. + */ + void SetBrokenImage( const std::string& brokenImageUrl ); + + /** + * @brief Upload a resource image to the atlas. + * + * @note To make the atlasing efficient, an valid size should be provided. + * If size is not provided, then the image file will be opened to read the actual size for loading. + * Do not set a size that is bigger than the actual image size, as the up-scaling is not available, + * the content of the area not covered by actual image is undefined, it will not be cleared. + * + * SamplingMode::BOX_THEN_LINEAR is used to sampling pixels from the input image while fitting it to desired size. + * + * @param [out] textureRect The texture area of the resource image in the atlas. + * @param [in] url The URL of the resource image file to use. + * @param [in] size The width and height to fit the loaded image to. + * @param [in] fittingMode The method used to fit the shape of the image before loading to the shape defined by the size parameter. + * @param [in] orientationCorrection Reorient the image to respect any orientation metadata in its header. + * @return True if there is enough space to fit this image in,false otherwise. + */ + bool Upload( Vector4& textureRect, + const std::string& url, + ImageDimensions size = ImageDimensions(), + FittingMode::Type fittingMode = FittingMode::DEFAULT, + bool orientationCorrection = true ); + + /** + * @brief Remove the image at the given rectangle. + * + * The rectangular area is marked unoccupied, so new image can be added to this area. + * + * @param [in] textureRect The texture area to be removed. + */ + void Remove( const Vector4& textureRect ); + +public: // Not intended for developer use + + explicit DALI_INTERNAL ImageAtlas( Internal::ImageAtlas* impl ); +}; + +} // namespace Toolkit + +} // namespace Dali + +#endif /* __DALI_TOOLKIT_IMAGE_ATLAS_H__ */ diff --git a/dali-toolkit/internal/controls/image-view/image-view-impl.cpp b/dali-toolkit/internal/controls/image-view/image-view-impl.cpp index c7f1d8c..59a3b33 100644 --- a/dali-toolkit/internal/controls/image-view/image-view-impl.cpp +++ b/dali-toolkit/internal/controls/image-view/image-view-impl.cpp @@ -103,7 +103,7 @@ void ImageView::SetImage( Property::Map map ) mImageSize = ImageDimensions( width, height ); } -void ImageView::SetImage( const std::string& url ) +void ImageView::SetImage( const std::string& url, ImageDimensions size ) { if( mUrl != url ) { @@ -112,10 +112,17 @@ void ImageView::SetImage( const std::string& url ) mUrl = url; - Actor self = Self(); - Toolkit::RendererFactory::Get().ResetRenderer( mRenderer, self, mUrl ); + if( size.GetWidth() == 0u && size.GetHeight() == 0u ) + { + mImageSize = ResourceImage::GetImageSize( mUrl ); + } + else + { + mImageSize = size; + } - mImageSize = ResourceImage::GetImageSize( mUrl ); + Actor self = Self(); + Toolkit::RendererFactory::Get().ResetRenderer( mRenderer, self, mUrl, mImageSize ); } } @@ -209,7 +216,7 @@ void ImageView::SetProperty( BaseObject* object, Property::Index index, const Pr if( value.Get( imageUrl ) ) { ImageView& impl = GetImpl( imageView ); - impl.SetImage( imageUrl ); + impl.SetImage( imageUrl, ImageDimensions() ); } // if its not a string then get a Property::Map from the property if possible. diff --git a/dali-toolkit/internal/controls/image-view/image-view-impl.h b/dali-toolkit/internal/controls/image-view/image-view-impl.h index 92c7f70..124ae5f 100644 --- a/dali-toolkit/internal/controls/image-view/image-view-impl.h +++ b/dali-toolkit/internal/controls/image-view/image-view-impl.h @@ -75,7 +75,7 @@ public: /** * @copydoc Dali::Toolkit::SetImage */ - void SetImage( const std::string& imageUrl ); + void SetImage( const std::string& imageUrl, ImageDimensions size ); // Properties /** diff --git a/dali-toolkit/internal/controls/renderers/control-renderer-impl.cpp b/dali-toolkit/internal/controls/renderers/control-renderer-impl.cpp index 04792c5..3b4a949 100644 --- a/dali-toolkit/internal/controls/renderers/control-renderer-impl.cpp +++ b/dali-toolkit/internal/controls/renderers/control-renderer-impl.cpp @@ -130,8 +130,6 @@ void ControlRenderer::SetOffStage( Actor& actor ) if( GetIsOnStage() ) { DoSetOffStage( actor ); - actor.RemoveRenderer( mImpl->mRenderer ); - mImpl->mRenderer.Reset(); mImpl->mFlags &= ~Impl::IS_ON_STAGE; } @@ -143,6 +141,8 @@ void ControlRenderer::DoSetOnStage( Actor& actor ) void ControlRenderer::DoSetOffStage( Actor& actor ) { + actor.RemoveRenderer( mImpl->mRenderer ); + mImpl->mRenderer.Reset(); } void ControlRenderer::CreatePropertyMap( Property::Map& map ) const diff --git a/dali-toolkit/internal/controls/renderers/image-atlas-manager.cpp b/dali-toolkit/internal/controls/renderers/image-atlas-manager.cpp new file mode 100644 index 0000000..4541ae3 --- /dev/null +++ b/dali-toolkit/internal/controls/renderers/image-atlas-manager.cpp @@ -0,0 +1,123 @@ + /* + * Copyright (c) 2015 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 "image-atlas-manager.h" + +// EXTERNAL HEADER +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +namespace +{ +const uint32_t DEFAULT_ATLAS_SIZE( 1024u ); // this size can fit 8 by 8 images of average size 128*128 +const uint32_t MAX_ITEM_SIZE( 512u ); +const uint32_t MAX_ITEM_AREA( MAX_ITEM_SIZE*MAX_ITEM_SIZE ); +} + +ImageAtlasManager::ImageAtlasManager( Shader shader, const std::string& textureUniformName ) +: mShader( shader ), + mTextureUniformName( textureUniformName ), + mBrokenImageUrl( "" ) +{ +} + +ImageAtlasManager::~ImageAtlasManager() +{ +} + +Material ImageAtlasManager::Add( Vector4& textureRect, + const std::string& url, + ImageDimensions size, + FittingMode::Type fittingMode, + bool orientationCorrection ) +{ + ImageDimensions dimensions = size; + ImageDimensions zero; + if( size == zero ) + { + dimensions = ResourceImage::GetImageSize( url ); + } + + // big image, atlasing is not applied + if( static_cast(dimensions.GetWidth()) * static_cast(dimensions.GetHeight()) > MAX_ITEM_AREA + || dimensions.GetWidth()>DEFAULT_ATLAS_SIZE + || dimensions.GetHeight()>DEFAULT_ATLAS_SIZE) + { + return Material(); + } + + unsigned int i = 0; + for( AtlasContainer::iterator iter = mAtlasList.begin(); iter != mAtlasList.end(); ++iter) + { + if( (*iter).Upload( textureRect, url, size, fittingMode, orientationCorrection ) ) + { + return mMaterialList[i]; + } + i++; + } + + Toolkit::ImageAtlas newAtlas = Toolkit::ImageAtlas::New( DEFAULT_ATLAS_SIZE, DEFAULT_ATLAS_SIZE ); + if( !mBrokenImageUrl.empty() ) + { + newAtlas.SetBrokenImage( mBrokenImageUrl ); + } + mAtlasList.push_back( newAtlas ); + Material newMaterial = Material::New( mShader ); + newMaterial.AddTexture( newAtlas.GetAtlas(), mTextureUniformName ); + mMaterialList.push_back( newMaterial ); + + newAtlas.Upload( textureRect, url, size, fittingMode, orientationCorrection ); + + return newMaterial; +} + +void ImageAtlasManager::Remove( Material material, const Vector4& textureRect ) +{ + unsigned int i = 0; + for( MaterialContainer::iterator iter = mMaterialList.begin(); iter != mMaterialList.end(); ++iter) + { + if( (*iter) == material ) + { + mAtlasList[i].Remove(textureRect); + return; + } + i++; + } +} + +void ImageAtlasManager::SetBrokenImage( const std::string& brokenImageUrl ) +{ + if( !brokenImageUrl.empty() ) + { + mBrokenImageUrl = brokenImageUrl; + } +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/controls/renderers/image-atlas-manager.h b/dali-toolkit/internal/controls/renderers/image-atlas-manager.h new file mode 100644 index 0000000..b0729dd --- /dev/null +++ b/dali-toolkit/internal/controls/renderers/image-atlas-manager.h @@ -0,0 +1,127 @@ +#ifndef __DALI_TOOLKIT_IMAGE_ATLAS_MANAGER_H__ +#define __DALI_TOOLKIT_IMAGE_ATLAS_MANAGER_H__ + +/* + * Copyright (c) 2015 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 +#include +#include +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +/** + * The manager for automatic image atlasing. Owned by RendererFactory + */ +class ImageAtlasManager : public RefObject +{ +public: + typedef std::vector< Toolkit::ImageAtlas > AtlasContainer; + typedef std::vector< Material > MaterialContainer; + +public: + + /** + * Construtor + * + * @param[in] shader The shader for material. + * @param[in] textureUniformName The texture uniform name for the atlas image. + */ + ImageAtlasManager( Shader shader, const std::string& textureUniformName ); + + /** + * @brief Add an image to the atlas. + * + * @note To make the atlasing efficient, an valid size should be provided. + * If size is not provided, then the image file will be opened to read the actual size for loading. + * + * SamplingMode::BOX_THEN_LINEAR is used to sampling pixels from the input image while fitting it to desired size. + * + * @param [out] textureRect The texture area of the resource image in the atlas. + * @param [in] url The URL of the resource image file to use. + * @param [in] size The width and height to fit the loaded image to. + * @param [in] fittingMode The method used to fit the shape of the image before loading to the shape defined by the size parameter. + * @param [in] orientationCorrection Reorient the image to respect any orientation metadata in its header. + * @return True if there is enough space to fit this image in,false otherwise. + */ + Material Add( Vector4& textureRect, + const std::string& url, + ImageDimensions size = ImageDimensions(), + FittingMode::Type fittingMode = FittingMode::DEFAULT, + bool orientationCorrection = true ); + + /** + * Remove the image at the given rectangle from the material. + * + * @param [in] material The material containing the atlas image. + * @param [in] textureRect The texture area to be removed. + */ + void Remove( Material material, const Vector4& textureRect ); + + /** + * @brief Set the broken image which is used to replace the image if loading fails. + * + * @param[in] brokenImageUrl The url of the broken image. + */ + void SetBrokenImage( const std::string& brokenImageUrl ); + +protected: + + /** + * Destructor + */ + virtual ~ImageAtlasManager(); + + /** + * Undefined copy constructor. + */ + ImageAtlasManager(const ImageAtlasManager&); + + /** + * Undefined assignment operator. + */ + ImageAtlasManager& operator=(const ImageAtlasManager& rhs); + + +private: + + AtlasContainer mAtlasList; + MaterialContainer mMaterialList; + Shader mShader; + std::string mTextureUniformName; + std::string mBrokenImageUrl; + +}; + +} // name Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif // __DALI_TOOLKIT_ATLAS_MANAGER_H__ diff --git a/dali-toolkit/internal/controls/renderers/image/image-renderer.cpp b/dali-toolkit/internal/controls/renderers/image/image-renderer.cpp index b78cdab..b07a17f 100644 --- a/dali-toolkit/internal/controls/renderers/image/image-renderer.cpp +++ b/dali-toolkit/internal/controls/renderers/image/image-renderer.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace Dali { @@ -65,13 +66,16 @@ const char * const BOX_THEN_LINEAR("boxThenLinear"); const char * const NO_FILTER("noFilter"); const char * const DONT_CARE("dontCare"); -std::string TEXTURE_UNIFORM_NAME = "sTexture"; +const std::string TEXTURE_UNIFORM_NAME = "sTexture"; +const std::string TEXTURE_RECT_UNIFORM_NAME = "uTextureRect"; +const Vector4 FULL_TEXTURE_RECT(0.f, 0.f, 1.f, 1.f); const char* VERTEX_SHADER = DALI_COMPOSE_SHADER( attribute mediump vec2 aPosition;\n varying mediump vec2 vTexCoord;\n uniform mediump mat4 uMvpMatrix;\n uniform mediump vec3 uSize;\n + uniform mediump vec4 uTextureRect;\n \n void main()\n {\n @@ -79,7 +83,7 @@ const char* VERTEX_SHADER = DALI_COMPOSE_SHADER( vertexPosition.xyz *= uSize;\n vertexPosition = uMvpMatrix * vertexPosition;\n \n - vTexCoord = aPosition + vec2(0.5);\n + vTexCoord = mix( uTextureRect.xy, uTextureRect.zw, aPosition + vec2(0.5));\n gl_Position = vertexPosition;\n }\n ); @@ -186,8 +190,10 @@ Geometry CreateGeometry( RendererFactoryCache& factoryCache, ImageDimensions gri } //unnamed namespace -ImageRenderer::ImageRenderer( RendererFactoryCache& factoryCache ) +ImageRenderer::ImageRenderer( RendererFactoryCache& factoryCache, ImageAtlasManager& atlasManager ) : ControlRenderer( factoryCache ), + mAtlasManager( atlasManager ), + mTextureRect( FULL_TEXTURE_RECT ), mDesiredSize(), mFittingMode( FittingMode::DEFAULT ), mSamplingMode( SamplingMode::DEFAULT ) @@ -317,7 +323,7 @@ void ImageRenderer::DoInitialize( Actor& actor, const Property::Map& propertyMap //clean the cache if( !oldImageUrl.empty() ) { - mFactoryCache.CleanRendererCache( oldImageUrl ); + CleanCache( oldImageUrl ); } //Initialize the renderer @@ -385,24 +391,14 @@ Renderer ImageRenderer::CreateRenderer() const if( !mImpl->mCustomShader ) { geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) ); - shader = mFactoryCache.GetShader( RendererFactoryCache::IMAGE_SHADER ); - if( !shader ) - { - shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ); - mFactoryCache.SaveShader( RendererFactoryCache::IMAGE_SHADER, shader ); - } + shader = GetImageShader(mFactoryCache); } else { geometry = CreateGeometry( mFactoryCache, mImpl->mCustomShader->mGridSize ); if( mImpl->mCustomShader->mVertexShader.empty() && mImpl->mCustomShader->mFragmentShader.empty() ) { - shader = mFactoryCache.GetShader( RendererFactoryCache::IMAGE_SHADER ); - if( !shader ) - { - shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ); - mFactoryCache.SaveShader( RendererFactoryCache::IMAGE_SHADER, shader ); - } + shader = GetImageShader(mFactoryCache); } else { @@ -430,14 +426,29 @@ void ImageRenderer::InitializeRenderer( const std::string& imageUrl ) mImpl->mRenderer = mFactoryCache.GetRenderer( imageUrl ); if( !mImpl->mRenderer ) { - mImpl->mRenderer = CreateRenderer(); + Material material = mAtlasManager.Add(mTextureRect, imageUrl, mDesiredSize, mFittingMode, mSamplingMode ); + if( material ) + { + Geometry geometry = CreateGeometry( mFactoryCache, ImageDimensions( 1, 1 ) ); + mImpl->mRenderer = Renderer::New( geometry, material ); + SetTextureRectUniform(mTextureRect); + } + else // big image, atlasing is not applied + { + mImpl->mRenderer = CreateRenderer(); - ResourceImage image = Dali::ResourceImage::New( imageUrl ); - image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded ); - Material material = mImpl->mRenderer.GetMaterial(); - material.AddTexture( image, TEXTURE_UNIFORM_NAME ); + ResourceImage image = Dali::ResourceImage::New( imageUrl ); + image.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded ); + Material material = mImpl->mRenderer.GetMaterial(); + material.AddTexture( image, TEXTURE_UNIFORM_NAME ); + } - mFactoryCache.SaveRenderer( imageUrl, mImpl->mRenderer ); + mFactoryCache.SaveRenderer( imageUrl, mImpl->mRenderer ); + } + else + { + Property::Value textureRect = mImpl->mRenderer.GetProperty( mImpl->mRenderer.GetPropertyIndex(TEXTURE_RECT_UNIFORM_NAME) ); + textureRect.Get( mTextureRect ); } mImpl->mFlags |= Impl::IS_FROM_CACHE; } @@ -462,6 +473,7 @@ void ImageRenderer::InitializeRenderer( const Image& image ) mImpl->mRenderer = CreateRenderer(); ApplyImageToSampler( image ); + SetTextureRectUniform( FULL_TEXTURE_RECT ); } @@ -484,6 +496,9 @@ void ImageRenderer::DoSetOnStage( Actor& actor ) ResourceImage resourceImage = Dali::ResourceImage::New( mImageUrl, mDesiredSize, mFittingMode, mSamplingMode ); resourceImage.LoadingFinishedSignal().Connect( this, &ImageRenderer::OnImageLoaded ); image = resourceImage; + + // Set value to the uTextureRect uniform + SetTextureRectUniform( FULL_TEXTURE_RECT ); } ApplyImageToSampler( image ); @@ -495,11 +510,16 @@ void ImageRenderer::DoSetOffStage( Actor& actor ) //If we own the image then make sure we release it when we go off stage if( !mImageUrl.empty() ) { - //clean the renderer from the cache since it may no longer be in use - mFactoryCache.CleanRendererCache( mImageUrl ); + actor.RemoveRenderer( mImpl->mRenderer ); + CleanCache(mImageUrl); mImage.Reset(); } + else + { + actor.RemoveRenderer( mImpl->mRenderer ); + mImpl->mRenderer.Reset(); + } } void ImageRenderer::DoCreatePropertyMap( Property::Map& map ) const @@ -598,13 +618,23 @@ void ImageRenderer::DoCreatePropertyMap( Property::Map& map ) const } } -void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl ) +Shader ImageRenderer::GetImageShader( RendererFactoryCache& factoryCache ) { - SetImage( actor, imageUrl, 0, 0, Dali::FittingMode::DEFAULT, Dali::SamplingMode::DEFAULT ); + Shader shader = factoryCache.GetShader( RendererFactoryCache::IMAGE_SHADER ); + if( !shader ) + { + shader = Shader::New( VERTEX_SHADER, FRAGMENT_SHADER ); + factoryCache.SaveShader( RendererFactoryCache::IMAGE_SHADER, shader ); + } + return shader; } -void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, int desiredWidth, int desiredHeight, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode ) +void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, ImageDimensions size, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode ) { + mDesiredSize = size; + mFittingMode = fittingMode; + mSamplingMode = samplingMode; + if( mImageUrl != imageUrl ) { if( mImpl->mRenderer ) @@ -620,7 +650,7 @@ void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, int des //clean the cache if( !mImageUrl.empty() ) { - mFactoryCache.CleanRendererCache( mImageUrl ); + CleanCache(mImageUrl); } //Initialize the renderer @@ -641,9 +671,7 @@ void ImageRenderer::SetImage( Actor& actor, const std::string& imageUrl, int des } mImageUrl = imageUrl; - mDesiredSize = ImageDimensions( desiredWidth, desiredHeight ); - mFittingMode = fittingMode; - mSamplingMode = samplingMode; + mImage.Reset(); } } @@ -665,7 +693,7 @@ void ImageRenderer::SetImage( Actor& actor, const Image& image ) //clean the cache if( !mImageUrl.empty() ) { - mFactoryCache.CleanRendererCache( mImageUrl ); + CleanCache(mImageUrl); } //Initialize the renderer @@ -682,6 +710,7 @@ void ImageRenderer::SetImage( Actor& actor, const Image& image ) ApplyImageToSampler( image ); } } + SetTextureRectUniform( FULL_TEXTURE_RECT ); mImage = image; mImageUrl.clear(); @@ -722,6 +751,32 @@ void ImageRenderer::OnImageLoaded( ResourceImage image ) } } +void ImageRenderer::SetTextureRectUniform( const Vector4& textureRect ) +{ + if( mImpl->mRenderer ) + { + Property::Index index = mImpl->mRenderer.GetPropertyIndex( TEXTURE_RECT_UNIFORM_NAME ); + if( index == Property::INVALID_INDEX ) + { + index = mImpl->mRenderer.RegisterProperty( TEXTURE_RECT_UNIFORM_NAME, textureRect ); + } + else + { + mImpl->mRenderer.SetProperty( index, textureRect ); + } + } +} + +void ImageRenderer::CleanCache(const std::string& url) +{ + Material material = mImpl->mRenderer.GetMaterial(); + mImpl->mRenderer.Reset(); + if( mFactoryCache.CleanRendererCache( url ) ) + { + mAtlasManager.Remove( material, mTextureRect ); + } +} + } // namespace Internal } // namespace Toolkit diff --git a/dali-toolkit/internal/controls/renderers/image/image-renderer.h b/dali-toolkit/internal/controls/renderers/image/image-renderer.h index 8498e54..c11c6b0 100644 --- a/dali-toolkit/internal/controls/renderers/image/image-renderer.h +++ b/dali-toolkit/internal/controls/renderers/image/image-renderer.h @@ -20,6 +20,7 @@ // INTERNAL INCLUDES #include +#include // EXTERNAL INCLUDES #include @@ -76,9 +77,10 @@ public: /** * @brief Constructor. * - * @param[in] factoryCache A pointer pointing to the RendererFactoryCache object + * @param[in] factoryCache The RendererFactoryCache object + * @param[in] atlasManager The atlasManager object */ - ImageRenderer( RendererFactoryCache& factoryCache ); + ImageRenderer( RendererFactoryCache& factoryCache, ImageAtlasManager& atlasManager ); /** * @brief A reference counted object may only be deleted by calling Unreference(). @@ -131,13 +133,10 @@ protected: public: /** - * @brief Sets the image of this renderer to the resource at imageUrl - * The renderer will load the Image asynchronously when the associated actor is put on stage, and destroy the image when it is off stage - * - * @param[in] actor The Actor the renderer is applied to if, empty if the renderer has not been applied to any Actor - * @param[in] imageUrl The URL to to image resource to use + * Get the standard image rendering shader. + * @param[in] factoryCache A pointer pointing to the RendererFactoryCache object */ - void SetImage( Actor& actor, const std::string& imageUrl ); + static Shader GetImageShader( RendererFactoryCache& factoryCache ); /** * @brief Sets the image of this renderer to the resource at imageUrl @@ -145,12 +144,15 @@ public: * * @param[in] actor The Actor the renderer is applied to if, empty if the renderer has not been applied to any Actor * @param[in] imageUrl The URL to to image resource to use - * @param[in] desiredWidth The desired width of the resource to load - * @param[in] desiredHeight The desired height of the resource to load + * @param[in] size The width and height to fit the loaded image to. * @param[in] fittingMode The FittingMode of the resource to load * @param[in] samplingMode The SamplingMode of the resource to load */ - void SetImage( Actor& actor, const std::string& imageUrl, int desiredWidth, int desiredHeight, Dali::FittingMode::Type fittingMode, Dali::SamplingMode::Type samplingMode ); + void SetImage( Actor& actor, + const std::string& imageUrl, + ImageDimensions size=ImageDimensions(), + FittingMode::Type fittingMode = FittingMode::DEFAULT, + Dali::SamplingMode::Type samplingMode = SamplingMode::BOX_THEN_LINEAR ); /** * @brief Sets the image of this renderer to use @@ -196,8 +198,21 @@ private: */ void OnImageLoaded( ResourceImage image ); + /** + * Set the value to the uTextureRect uniform + * @param[in] textureRect The texture rectangular area. + */ + void SetTextureRectUniform( const Vector4& textureRect ); + + /** + * Clean the renderer from cache, and remove the image from atlas if it is not used anymore + */ + void CleanCache(const std::string& url); + private: Image mImage; + ImageAtlasManager& mAtlasManager; + Vector4 mTextureRect; std::string mImageUrl; Dali::ImageDimensions mDesiredSize; diff --git a/dali-toolkit/internal/controls/renderers/npatch/npatch-renderer.cpp b/dali-toolkit/internal/controls/renderers/npatch/npatch-renderer.cpp index 13bf830..e13e85d 100644 --- a/dali-toolkit/internal/controls/renderers/npatch/npatch-renderer.cpp +++ b/dali-toolkit/internal/controls/renderers/npatch/npatch-renderer.cpp @@ -372,6 +372,8 @@ void NPatchRenderer::DoSetOnStage( Actor& actor ) void NPatchRenderer::DoSetOffStage( Actor& actor ) { mCroppedImage.Reset(); + actor.RemoveRenderer( mImpl->mRenderer ); + mImpl->mRenderer.Reset(); } void NPatchRenderer::DoCreatePropertyMap( Property::Map& map ) const diff --git a/dali-toolkit/internal/controls/renderers/renderer-factory-cache.cpp b/dali-toolkit/internal/controls/renderers/renderer-factory-cache.cpp index c7b53b6..524137a 100644 --- a/dali-toolkit/internal/controls/renderers/renderer-factory-cache.cpp +++ b/dali-toolkit/internal/controls/renderers/renderer-factory-cache.cpp @@ -122,7 +122,7 @@ void RendererFactoryCache::SaveRenderer( const std::string& key, Renderer& rende } } -void RendererFactoryCache::CleanRendererCache( const std::string& key ) +bool RendererFactoryCache::CleanRendererCache( const std::string& key ) { int index = FindRenderer( key ); if( index != -1 ) @@ -134,8 +134,10 @@ void RendererFactoryCache::CleanRendererCache( const std::string& key ) delete cachedRenderer; cachedRenderer = NULL; + return true; } } + return false; } Geometry RendererFactoryCache::CreateQuadGeometry() diff --git a/dali-toolkit/internal/controls/renderers/renderer-factory-cache.h b/dali-toolkit/internal/controls/renderers/renderer-factory-cache.h index 947fd47..d1c26ee 100644 --- a/dali-toolkit/internal/controls/renderers/renderer-factory-cache.h +++ b/dali-toolkit/internal/controls/renderers/renderer-factory-cache.h @@ -136,8 +136,10 @@ public: * @brief Cleans the renderer cache by removing the renderer from the cache based on the given key if there are no longer any references to it * * @param[in] key The key used for caching + * + * @return True if the renderer is no longer used anywhere, false otherwise */ - void CleanRendererCache( const std::string& key ); + bool CleanRendererCache( const std::string& key ); protected: diff --git a/dali-toolkit/internal/controls/renderers/renderer-factory-impl.cpp b/dali-toolkit/internal/controls/renderers/renderer-factory-impl.cpp index ef698f4..0a25ffa 100644 --- a/dali-toolkit/internal/controls/renderers/renderer-factory-impl.cpp +++ b/dali-toolkit/internal/controls/renderers/renderer-factory-impl.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace { @@ -42,6 +43,8 @@ const char * const GRADIENT_RENDERER("gradientRenderer"); const char * const IMAGE_RENDERER("imageRenderer"); const char * const N_PATCH_RENDERER("nPatchRenderer"); +const std::string TEXTURE_UNIFORM_NAME = "sTexture"; + const char * const BROKEN_RENDERER_IMAGE_URL( DALI_IMAGE_DIR "broken.png"); } @@ -100,7 +103,8 @@ Toolkit::ControlRenderer RendererFactory::GetControlRenderer( const Property::Ma } else if( typeValue == IMAGE_RENDERER ) { - rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ) ); + CreateAtlasManager(); + rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ), *( mAtlasManager.Get() ) ); } else if( typeValue == N_PATCH_RENDERER ) { @@ -195,7 +199,8 @@ Toolkit::ControlRenderer RendererFactory::GetControlRenderer( const Image& image } else { - ImageRenderer* rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ) ); + CreateAtlasManager(); + ImageRenderer* rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ), *( mAtlasManager.Get() ) ); Actor actor; rendererPtr->SetImage( actor, image ); @@ -237,7 +242,7 @@ void RendererFactory::ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& } } -Toolkit::ControlRenderer RendererFactory::GetControlRenderer( const std::string& url ) +Toolkit::ControlRenderer RendererFactory::GetControlRenderer( const std::string& url, ImageDimensions size ) { if( !mFactoryCache ) { @@ -253,15 +258,16 @@ Toolkit::ControlRenderer RendererFactory::GetControlRenderer( const std::string& } else { - ImageRenderer* rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ) ); + CreateAtlasManager(); + ImageRenderer* rendererPtr = new ImageRenderer( *( mFactoryCache.Get() ), *( mAtlasManager.Get() ) ); Actor actor; - rendererPtr->SetImage( actor, url ); + rendererPtr->SetImage( actor, url, size ); return Toolkit::ControlRenderer( rendererPtr ); } } -void RendererFactory::ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& actor, const std::string& url ) +void RendererFactory::ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& actor, const std::string& url, ImageDimensions size ) { if( renderer ) { @@ -279,7 +285,7 @@ void RendererFactory::ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& ImageRenderer* rendererPtr = dynamic_cast< ImageRenderer* >( &GetImplementation( renderer ) ); if( rendererPtr ) { - rendererPtr->SetImage( actor, url ); + rendererPtr->SetImage( actor, url, size ); return; } } @@ -287,7 +293,7 @@ void RendererFactory::ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& renderer.RemoveAndReset( actor ); } - renderer = GetControlRenderer( url ); + renderer = GetControlRenderer( url, size ); if( actor && actor.OnStage() ) { renderer.SetOnStage( actor ); @@ -330,6 +336,16 @@ Image RendererFactory::GetBrokenRendererImage() return ResourceImage::New( BROKEN_RENDERER_IMAGE_URL ); } +void RendererFactory::CreateAtlasManager() +{ + if( !mAtlasManager ) + { + Shader shader = ImageRenderer::GetImageShader( *( mFactoryCache.Get() ) ); + mAtlasManager = new ImageAtlasManager(shader, TEXTURE_UNIFORM_NAME); + mAtlasManager->SetBrokenImage( BROKEN_RENDERER_IMAGE_URL ); + } +} + } // namespace Internal } // namespace Toolkit diff --git a/dali-toolkit/internal/controls/renderers/renderer-factory-impl.h b/dali-toolkit/internal/controls/renderers/renderer-factory-impl.h index fed299a..9b55182 100644 --- a/dali-toolkit/internal/controls/renderers/renderer-factory-impl.h +++ b/dali-toolkit/internal/controls/renderers/renderer-factory-impl.h @@ -36,6 +36,9 @@ namespace Internal class RendererFactoryCache; typedef IntrusivePtr RendererFactoryCachePtr; +class ImageAtlasManager; +typedef IntrusivePtr ImageAtlasManagerPtr; + /** * @copydoc Toolkit::RendererFactory */ @@ -85,14 +88,14 @@ public: void ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& actor, const Image& image ); /** - * @copydoc Toolkit::RenderFactory::GetControlRenderer( const std::string& ) + * @copydoc Toolkit::RenderFactory::GetControlRenderer( const std::string&, ImageDimensions ) */ - Toolkit::ControlRenderer GetControlRenderer( const std::string& image ); + Toolkit::ControlRenderer GetControlRenderer( const std::string& image, ImageDimensions size ); /** - * @copydoc Toolkit::RendererFactory::ResetRenderer( Toolkit::ControlRenderer&, Actor& actor, const std::string& ) + * @copydoc Toolkit::RendererFactory::ResetRenderer( Toolkit::ControlRenderer&, Actor& actor, const std::string&, ImageDimensions ) */ - void ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& actor, const std::string& image ); + void ResetRenderer( Toolkit::ControlRenderer& renderer, Actor& actor, const std::string& image, ImageDimensions size ); public: /** @@ -110,6 +113,11 @@ protected: private: /** + * Prepare the atlas manager + */ + void CreateAtlasManager(); + + /** * Undefined copy constructor. */ RendererFactory(const RendererFactory&); @@ -122,6 +130,7 @@ private: private: RendererFactoryCachePtr mFactoryCache; + ImageAtlasManagerPtr mAtlasManager; }; } // namespace Internal diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 34d10cc..a30cf52 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -12,6 +12,7 @@ toolkit_src_files = \ $(toolkit_src_dir)/builder/replacement.cpp \ $(toolkit_src_dir)/controls/renderers/control-renderer-impl.cpp \ $(toolkit_src_dir)/controls/renderers/control-renderer-data-impl.cpp \ + $(toolkit_src_dir)/controls/renderers/image-atlas-manager.cpp \ $(toolkit_src_dir)/controls/renderers/renderer-factory-cache.cpp \ $(toolkit_src_dir)/controls/renderers/renderer-factory-impl.cpp \ $(toolkit_src_dir)/controls/renderers/border/border-renderer.cpp \ @@ -74,6 +75,9 @@ toolkit_src_files = \ $(toolkit_src_dir)/filters/emboss-filter.cpp \ $(toolkit_src_dir)/filters/image-filter.cpp \ $(toolkit_src_dir)/filters/spread-filter.cpp \ + $(toolkit_src_dir)/image-atlas/atlas-packer.cpp \ + $(toolkit_src_dir)/image-atlas/image-atlas-impl.cpp \ + $(toolkit_src_dir)/image-atlas/image-load-thread.cpp \ $(toolkit_src_dir)/styling/style-manager-impl.cpp \ $(toolkit_src_dir)/text/bidirectional-support.cpp \ $(toolkit_src_dir)/text/character-set-conversion.cpp \ diff --git a/dali-toolkit/internal/image-atlas/atlas-packer.cpp b/dali-toolkit/internal/image-atlas/atlas-packer.cpp new file mode 100644 index 0000000..7178741 --- /dev/null +++ b/dali-toolkit/internal/image-atlas/atlas-packer.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2015 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 "atlas-packer.h" + +// EXTERNAL HEADER +#include // For abs() + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +namespace +{ + +bool ApproximatelyEqual( uint32_t a, uint32_t b ) +{ + return abs( a-b ) <= 1; +} + +} + +AtlasPacker::Node::Node( Node* parent, SizeType x, SizeType y, SizeType width, SizeType height ) +: rectArea( x, y, width, height ), + parent(parent), + occupied( false ) +{ + child[0] = NULL; + child[1] = NULL; +} + +AtlasPacker:: AtlasPacker( SizeType atlasWidth, SizeType atlasHeight ) +: mAvailableArea( atlasWidth * atlasHeight ) +{ + mRoot = new Node( NULL, 0u, 0u, atlasWidth, atlasHeight ); +} + +AtlasPacker::~AtlasPacker() +{ + DeleteNode( mRoot ); +} + +bool AtlasPacker::Pack( SizeType blockWidth, SizeType blockHeight, + SizeType& packPositionX, SizeType& packPositionY) +{ + Node* firstFit = InsertNode( mRoot, blockWidth, blockHeight ); + if( firstFit != NULL ) + { + firstFit->occupied = true; + packPositionX = firstFit->rectArea.x; + packPositionY = firstFit->rectArea.y; + mAvailableArea -= blockWidth*blockHeight; + return true; + } + return false; +} + +void AtlasPacker::DeleteBlock( SizeType packPositionX, SizeType packPositionY, SizeType blockWidth, SizeType blockHeight ) +{ + Node* node = SearchNode( mRoot, packPositionX, packPositionY, blockWidth, blockHeight ); + if( node != NULL ) + { + mAvailableArea += blockWidth*blockHeight; + MergeToNonOccupied( node ); + } +} + +unsigned int AtlasPacker::GetAvailableArea() const +{ + return mAvailableArea; +} + +AtlasPacker::Node* AtlasPacker::InsertNode( Node* root, SizeType blockWidth, SizeType blockHeight ) +{ + if( root == NULL ) + { + return NULL; + } + + if( root->occupied ) + { + // if not the leaf, then try insert into the first child. + Node* newNode = InsertNode(root->child[0], blockWidth, blockHeight); + if( newNode == NULL )// no room, try insert into the second child. + { + newNode = InsertNode(root->child[1], blockWidth, blockHeight); + } + return newNode; + } + + // too small, return + if( root->rectArea.width < blockWidth || root->rectArea.height < blockHeight ) + { + return NULL; + } + + // right size, accept + if( root->rectArea.width == blockWidth && root->rectArea.height == blockHeight ) + { + return root; + } + + //too much room, need to split + SplitNode( root, blockWidth, blockHeight ); + // insert into the first child created. + return InsertNode( root->child[0], blockWidth, blockHeight); +} + +void AtlasPacker::SplitNode( Node* node, SizeType blockWidth, SizeType blockHeight ) +{ + node->occupied = true; + + // decide which way to split + SizeType remainingWidth = node->rectArea.width - blockWidth; + SizeType remainingHeight = node->rectArea.height - blockHeight; + + if( remainingWidth > remainingHeight ) // split vertically + { + node->child[0] = new Node( node, node->rectArea.x, node->rectArea.y, blockWidth, node->rectArea.height ); + node->child[1] = new Node( node, node->rectArea.x+blockWidth, node->rectArea.y, node->rectArea.width-blockWidth, node->rectArea.height ); + } + else // split horizontally + { + node->child[0] = new Node( node, node->rectArea.x, node->rectArea.y, node->rectArea.width, blockHeight ); + node->child[1] = new Node( node, node->rectArea.x, node->rectArea.y+blockHeight, node->rectArea.width, node->rectArea.height-blockHeight ); + } +} + +AtlasPacker::Node* AtlasPacker::SearchNode( Node* node, SizeType packPositionX, SizeType packPositionY, SizeType blockWidth, SizeType blockHeight ) +{ + if( node == NULL ) + { + return NULL; + } + + if( node->child[0] != NULL) //not a leaf + { + Node* newNode = SearchNode(node->child[0], packPositionX, packPositionY, blockWidth, blockHeight); + if( newNode == NULL )// try search from the second child. + { + newNode = SearchNode(node->child[1], packPositionX, packPositionY, blockWidth, blockHeight); + } + return newNode; + } + else if( ApproximatelyEqual(node->rectArea.x, packPositionX) && ApproximatelyEqual(node->rectArea.y, packPositionY ) + && ApproximatelyEqual(node->rectArea.width, blockWidth) && ApproximatelyEqual( node->rectArea.height, blockHeight) ) + { + return node; + } + + return NULL; +} + +void AtlasPacker::MergeToNonOccupied( Node* node ) +{ + node->occupied = false; + Node* parent = node->parent; + // both child are not occupied, merge the space to parent + if( parent !=NULL && parent->child[0]->occupied == false && parent->child[1]->occupied == false) + { + delete parent->child[0]; + parent->child[0] = NULL; + delete parent->child[1]; + parent->child[0] = NULL; + + MergeToNonOccupied( parent ); + } +} + +void AtlasPacker::DeleteNode( Node *node ) +{ + if( node != NULL ) + { + DeleteNode( node->child[0] ); + DeleteNode( node->child[1] ); + delete node; + } +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/image-atlas/atlas-packer.h b/dali-toolkit/internal/image-atlas/atlas-packer.h new file mode 100644 index 0000000..bab993f --- /dev/null +++ b/dali-toolkit/internal/image-atlas/atlas-packer.h @@ -0,0 +1,169 @@ +#ifndef __DALI_TOOLKIT_ATLAS_PACKER_H__ +#define __DALI_TOOLKIT_ATLAS_PACKER_H__ + +/* + * Copyright (c) 2015 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. + */ + +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +/** + * Binary space tree based bin packing algorithm. + * It is initialised with a fixed width and height and will fit each block into the first node where it fits + * and then split that node into 2 parts (down and right) to track the remaining empty space. + */ +class AtlasPacker +{ +public: + + /** + * rectangular area (x,y,width,height) + */ + typedef uint32_t SizeType; + typedef Rect RectArea; + + /** + * Tree node. + */ + struct Node + { + Node( Node* parent, SizeType x, SizeType y, SizeType width, SizeType height ); + + RectArea rectArea; + Node* parent; + Node* child[2]; + bool occupied; + }; + + /** + * Constructor. + * + * @param[in] atlasWidth The width of the atlas. + * @param[in] atlasHeight The height of the atlas. + */ + AtlasPacker( SizeType atlasWidth, SizeType atlasHeight ); + + /** + * Destructor + */ + ~AtlasPacker(); + + /** + * Pack a block into the atlas. + * + * @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. + * @return True if there are room for this block, false otherwise. + */ + bool Pack( SizeType blockWidth, SizeType blockHeight, + SizeType& packPositionX, SizeType& packPositionY); + + /** + * Delete the block. + * + * @param[in] packPositionX The x coordinate of the pack position. + * @param[in] packPositionY The y coordinate of the pack position. + * @param[in] blockWidth The width of the block to delete. + * @param[in] blockHeight The height of the block to delete. + */ + void DeleteBlock( SizeType packPositionX, SizeType packPositionY, SizeType blockWidth, SizeType blockHeight ); + + /** + * Query how much empty space left. + * + * @return The area available for packing. + */ + unsigned int GetAvailableArea() const; + +private: + + /* + * Search the node which can pack the block with given size. + * + * @param[in] root The root node of the subtree to be searched. + * @param[in] blockWidth The width of the block to pack. + * @param[in] blockHeight The height of the block to pack. + * @return The poniter pointing to node that can pack the block. + * If it is NULL, there are no room in the subtree to pack the block. + */ + Node* InsertNode( Node* root, SizeType blockWidth, SizeType blockHeight ); + + /** + * Split the node into two to fit the block width/size. + * + * @parm[in] node The node to split. + * @param[in] blockWidth The width of the block to pack. + * @param[in] blockHeight The height of the block to pack. + */ + void SplitNode( Node* node, SizeType blockWidth, SizeType blockHeight ); + + /** + * Search the node at the given position and with the given size. + + * @param[in] node The root node of the subtree to be searched. + * @param[in] packPositionX The x coordinate of the pack position. + * @param[in] packPositionY The y coordinate of the pack position. + * @param[in] blockWidth The width of the block. + * @param[in] blockHeight The height of the block. + */ + Node* SearchNode( Node* node, SizeType packPositionX, SizeType packPositionY, SizeType blockWidth, SizeType blockHeight ); + + /** + * Merge the rect of the node to non-occupied area. + * + * @param[in] node The node to me merged to the non-occupied area + */ + void MergeToNonOccupied( Node* node ); + + /** + * Delete a node and its subtree. + * + * @parm[in] node The node to delete. + */ + void DeleteNode( Node* node ); + + // Undefined + AtlasPacker( const AtlasPacker& imageAtlas); + + // Undefined + AtlasPacker& operator=( const AtlasPacker& imageAtlas ); + +private: + + Node* mRoot; ///< The root of the binary space tree + unsigned int mAvailableArea; + +}; + + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali + +#endif /* __DALI_TOOLKIT_ATLAS_PACKER_H__ */ diff --git a/dali-toolkit/internal/image-atlas/image-atlas-impl.cpp b/dali-toolkit/internal/image-atlas/image-atlas-impl.cpp new file mode 100644 index 0000000..a46c6f2 --- /dev/null +++ b/dali-toolkit/internal/image-atlas/image-atlas-impl.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2015 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 "image-atlas-impl.h" + +// EXTERNAL INCLUDES +#include +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +ImageAtlas::ImageAtlas( SizeType width, SizeType height, Pixel::Format pixelFormat ) +: mPacker( width, height ), + mLoadQueue(), + mCompleteQueue( new EventThreadCallback( MakeCallback( this, &ImageAtlas::UploadToAtlas ) ) ), + mLoadingThread( mLoadQueue, mCompleteQueue ), + mBrokenImageUrl(""), + mBrokenImageSize(), + mPixelFormat( pixelFormat ), + mLoadingThreadStarted( false ) +{ + mAtlas = Atlas::New( width, height, pixelFormat ); + mWidth = static_cast(width); + mHeight = static_cast( height ); +} + +ImageAtlas::~ImageAtlas() +{ + if( mLoadingThreadStarted ) + { + // add an empty task would stop the loading thread from contional wait. + mLoadQueue.AddTask( NULL ); + // stop the loading thread + mLoadingThread.Join(); + // The atlas can still be used as texture after ImageAtlas has been thrown away, + // so make sure all the loaded bitmap been uploaded to atlas + UploadToAtlas(); + } +} + +IntrusivePtr ImageAtlas::New( SizeType width, SizeType height, Pixel::Format pixelFormat ) +{ + IntrusivePtr internal = new ImageAtlas( width, height, pixelFormat ); + return internal; +} + +Image ImageAtlas::GetAtlas() +{ + return mAtlas; +} + +void ImageAtlas::SetBrokenImage( const std::string& brokenImageUrl ) +{ + mBrokenImageSize = ResourceImage::GetImageSize( brokenImageUrl ); + if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0 ) // check the url is valid + { + mBrokenImageUrl = brokenImageUrl; + } +} + +bool ImageAtlas::Upload( Vector4& textureRect, + const std::string& url, + ImageDimensions size, + FittingMode::Type fittingMode, + bool orientationCorrection ) +{ + ImageDimensions dimensions = size; + ImageDimensions zero; + if( size == zero ) // image size not provided + { + dimensions = ResourceImage::GetImageSize( url ); + if( dimensions == zero ) // Fail to read the image & broken image file exists + { + if( !mBrokenImageUrl.empty() ) + { + return Upload( textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true ); + } + else + { + textureRect = Vector4::ZERO; + return true; + } + } + } + + if( static_cast(dimensions.GetWidth() * dimensions.GetHeight()) > mPacker.GetAvailableArea() ) + { + return false; + } + + unsigned int packPositionX = 0; + unsigned int packPositionY = 0; + if( mPacker.Pack( dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY ) ) + { + if( !mLoadingThreadStarted ) + { + mLoadingThread.Start(); + mLoadingThreadStarted = true; + } + + LoadingTask* newTask = new LoadingTask(BitmapLoader::New(url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection ), + packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight()); + mLoadQueue.AddTask( newTask ); + + // apply the half pixel correction + textureRect.x = ( static_cast( packPositionX ) +0.5f ) / mWidth; // left + textureRect.y = ( static_cast( packPositionY ) +0.5f ) / mHeight; // right + textureRect.z = ( static_cast( packPositionX + dimensions.GetX() )-0.5f ) / mWidth; // right + textureRect.w = ( static_cast( packPositionY + dimensions.GetY() )-0.5f ) / mHeight;// bottom + + return true; + } + + return false; +} + +void ImageAtlas::Remove( const Vector4& textureRect ) +{ + mPacker.DeleteBlock( static_cast(textureRect.x*mWidth), + static_cast(textureRect.y*mHeight), + static_cast((textureRect.z-textureRect.x)*mWidth+1.f), + static_cast((textureRect.w-textureRect.y)*mHeight+1.f) ); +} + +void ImageAtlas::UploadToAtlas() +{ + while( LoadingTask* next = mCompleteQueue.NextTask() ) + { + if( ! next->loader.IsLoaded() ) + { + if(!mBrokenImageUrl.empty()) // replace with the broken image + { + UploadBrokenImage( next->packRect ); + } + + DALI_LOG_ERROR( "Failed to load the image: %s\n", (next->loader.GetUrl()).c_str()); + } + else + { + if( next->loader.GetPixelData()->GetWidth() < next->packRect.width || next->loader.GetPixelData()->GetHeight() < next->packRect.height ) + { + DALI_LOG_ERROR( "Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n", + next->loader.GetPixelData()->GetWidth(), + next->loader.GetPixelData()->GetHeight(), + next->packRect.width, + next->packRect.height ); + } + + mAtlas.Upload( next->loader.GetPixelData(), next->packRect.x, next->packRect.y ); + } + + delete next; + } +} + +void ImageAtlas::UploadBrokenImage( const Rect& area ) +{ + BitmapLoader loader = BitmapLoader::New(mBrokenImageUrl, ImageDimensions( area.width, area.height ) ); + loader.Load(); + SizeType loadedWidth = loader.GetPixelData()->GetWidth(); + SizeType loadedHeight = loader.GetPixelData()->GetHeight(); + + bool needBackgroundClear = false; + SizeType packX = area.x; + SizeType packY = area.y; + // locate the broken image in the middle. + if( area.width > loadedWidth) + { + packX += (area.width - loadedWidth)/2; + needBackgroundClear = true; + } + if( area.height > loadedHeight) + { + packY += (area.height - loadedHeight)/2; + needBackgroundClear = true; + } + + if( needBackgroundClear ) + { + SizeType size = area.width * area.height * Pixel::GetBytesPerPixel( mPixelFormat ); + PixelBuffer* buffer = new PixelBuffer [size]; + PixelDataPtr background = PixelData::New( buffer, area.width, area.height, mPixelFormat, PixelData::DELETE_ARRAY ); + for( SizeType idx = 0; idx < size; idx++ ) + { + buffer[idx] = 0x00; + } + mAtlas.Upload( background, area.x, area.y ); + } + + mAtlas.Upload( loader.GetPixelData(), packX, packY ); +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/image-atlas/image-atlas-impl.h b/dali-toolkit/internal/image-atlas/image-atlas-impl.h new file mode 100644 index 0000000..c3e621c --- /dev/null +++ b/dali-toolkit/internal/image-atlas/image-atlas-impl.h @@ -0,0 +1,152 @@ +#ifndef __DALI_TOOLKIT_IMAGE_ATLAS_IMPL_H__ +#define __DALI_TOOLKIT_IMAGE_ATLAS_IMPL_H__ + +/* + * Copyright (c) 2015 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 +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ +class EventThreadCallback; + +namespace Toolkit +{ + +namespace Internal +{ + +class ImageAtlas : public BaseObject +{ +public: + + typedef Toolkit::ImageAtlas::SizeType SizeType; + + /** + * Constructor + * @param [in] width The atlas width in pixels. + * @param [in] height The atlas height in pixels. + * @param [in] pixelFormat The pixel format. + */ + ImageAtlas( SizeType width, SizeType height, Pixel::Format pixelFormat ); + + /** + * @copydoc Toolkit::ImageAtlas::New + */ + static IntrusivePtr New( SizeType width, SizeType height, Pixel::Format pixelFormat ); + + /** + * @copydoc Toolkit::ImageAtlas::GetAtlas + */ + Image GetAtlas(); + + /** + * @copydoc Toolkit::ImageAtlas::SetBrokenImage + */ + void SetBrokenImage( const std::string& brokenImageUrl ); + + /** + * @copydoc Toolkit::ImageAtlas::Upload + */ + bool Upload( Vector4& textureRect, + const std::string& url, + ImageDimensions size, + FittingMode::Type fittingMode, + bool orientationCorrection); + + /** + * @copydoc Toolkit::ImageAtlas::Remove + */ + void Remove( const Vector4& textureRect ); + +protected: + + /** + * Destructor + */ + ~ImageAtlas(); + +private: + + /** + * Upload the bitmap to atlas when the image is loaded in the worker thread. + */ + void UploadToAtlas(); + + /** + * Upload broken image + * + * @param[in] area The pixel area for uploading. + */ + void UploadBrokenImage( const Rect& area ); + + // Undefined + ImageAtlas( const ImageAtlas& imageAtlas); + + // Undefined + ImageAtlas& operator=( const ImageAtlas& imageAtlas ); + +private: + + Atlas mAtlas; + AtlasPacker mPacker; + + LoadQueue mLoadQueue; + CompleteQueue mCompleteQueue; + ImageLoadThread mLoadingThread; + + std::string mBrokenImageUrl; + ImageDimensions mBrokenImageSize; + float mWidth; + float mHeight; + Pixel::Format mPixelFormat; + bool mLoadingThreadStarted; + +}; + +} // namespace Internal + +inline const Internal::ImageAtlas& GetImplementation( const Toolkit::ImageAtlas& imageAtlas ) +{ + DALI_ASSERT_ALWAYS( imageAtlas && "ImageAtlas handle is empty" ); + + const BaseObject& handle = imageAtlas.GetBaseObject(); + + return static_cast(handle); +} + +inline Internal::ImageAtlas& GetImplementation( Toolkit::ImageAtlas& imageAtlas ) +{ + DALI_ASSERT_ALWAYS( imageAtlas && "ImageAtlas handle is empty" ); + + BaseObject& handle = imageAtlas.GetBaseObject(); + + return static_cast(handle); +} + +} // namespace Toolkit + +} // namespace Dali + +#endif /* __DALI_TOOLKIT_IMAGE_ATLAS_IMPL_H__ */ diff --git a/dali-toolkit/internal/image-atlas/image-load-thread.cpp b/dali-toolkit/internal/image-atlas/image-load-thread.cpp new file mode 100644 index 0000000..d106690 --- /dev/null +++ b/dali-toolkit/internal/image-atlas/image-load-thread.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015 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 "image-load-thread.h" + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +LoadingTask::LoadingTask(BitmapLoader loader, uint32_t packPositionX, uint32_t packPositionY, uint32_t width, uint32_t height ) +: loader( loader ), + packRect( packPositionX, packPositionY, width, height ) +{ +} + +LoadQueue::LoadQueue() +{ +} + +LoadQueue::~LoadQueue() +{ +} + +LoadingTask* LoadQueue::NextTask() +{ + // Lock while popping task out from the queue + ConditionalWait::ScopedLock lock( mConditionalWait ); + + while( mTasks.Empty() ) + { + mConditionalWait.Wait( lock ); + } + + Vector< LoadingTask* >::Iterator next = mTasks.Begin(); + LoadingTask* nextTask = *next; + mTasks.Erase( next ); + + return nextTask; +} + +void LoadQueue::AddTask( LoadingTask* task ) +{ + bool wasEmpty = false; + + { + // Lock while adding task to the queue + ConditionalWait::ScopedLock lock( mConditionalWait ); + wasEmpty = mTasks.Empty(); + mTasks.PushBack( task ); + } + + if( wasEmpty) + { + // wake up the image loading thread + mConditionalWait.Notify(); + } +} + +CompleteQueue::CompleteQueue(EventThreadCallback* trigger) +: mTrigger( trigger ) +{} + +CompleteQueue::~CompleteQueue() +{ + delete mTrigger; +} + +LoadingTask* CompleteQueue::NextTask() +{ + while( mTasks.Empty() ) + { + return NULL; + } + + // Lock while popping task out from the queue + Mutex::ScopedLock lock( mMutex ); + + Vector< LoadingTask* >::Iterator next = mTasks.Begin(); + LoadingTask* nextTask = *next; + mTasks.Erase( next ); + + return nextTask; +} + +void CompleteQueue::AddTask( LoadingTask* task ) +{ + // Lock while adding task to the queue + Mutex::ScopedLock lock( mMutex ); + mTasks.PushBack( task ); + + // wake up the main thread + mTrigger->Trigger(); +} + + +ImageLoadThread::ImageLoadThread( LoadQueue& loadQueue, CompleteQueue& completeQueue ) +: mLoadQueue( loadQueue ), + mCompleteQueue( completeQueue ) +{ +} + +ImageLoadThread::~ImageLoadThread() +{ +} + +void ImageLoadThread::Run() +{ + while( LoadingTask* task = mLoadQueue.NextTask() ) + { + task->loader.Load(); + mCompleteQueue.AddTask( task ); + } +} + +} // namespace Internal + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/image-atlas/image-load-thread.h b/dali-toolkit/internal/image-atlas/image-load-thread.h new file mode 100644 index 0000000..700815c --- /dev/null +++ b/dali-toolkit/internal/image-atlas/image-load-thread.h @@ -0,0 +1,204 @@ +#ifndef __DALI_TOOLKIT_IMAGE_LOAD_THREAD_H__ +#define __DALI_TOOLKIT_IMAGE_LOAD_THREAD_H__ + +/* + * Copyright (c) 2015 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 +#include +#include +#include +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Internal +{ + +/** + * The task of loading and packing an image into the atlas. + */ +struct LoadingTask +{ + /** + * Constructor. + */ + LoadingTask( BitmapLoader loader, uint32_t packPositionX, uint32_t packPositionY, uint32_t width, uint32_t height ); + +private: + + // Undefined + LoadingTask( const LoadingTask& queue ); + + // Undefined + LoadingTask& operator=( const LoadingTask& queue ); + +public: + + BitmapLoader loader; ///< The loader used to load the bitmap from URL + Rect packRect; ///< The x coordinate of the position to pack the image. + +}; + +/** + * The queue of the tasks waiting to load the bitmap from the URL in the worker thread/ + */ +class LoadQueue //: public TaskQueue +{ +public: + + /** + * Constructor + */ + LoadQueue(); + + /** + * Destructor. + */ + ~LoadQueue(); + + /** + * Pop the next task out from the queue. + * + * @return The next task to be processed. + */ + LoadingTask* NextTask(); + + /** + * Add a task in to the queue + * + * @param[in] task The task added to the queue. + */ + void AddTask( LoadingTask* task ); + +private: + + // Undefined + LoadQueue( const LoadQueue& queue ); + + // Undefined + LoadQueue& operator=( const LoadQueue& queue ); + +private: + + Vector< LoadingTask* > mTasks; + ConditionalWait mConditionalWait; +}; + +/** + * The queue of the tasks, with the image loaded, waiting for the main thread to upload the bitmap. + */ +class CompleteQueue //: public TaskQueue +{ +public: + + /** + * Constructor + * + * @param[in] mTrigger The trigger to wake up the main thread. + */ + CompleteQueue( EventThreadCallback* mTrigger ); + + /** + * Destructor. + */ + ~CompleteQueue(); + + /** + * Pop the next task out from the queue. + * + * @return The next task to be processed. + */ + LoadingTask* NextTask(); + + /** + * Add a task in to the queue + * + * @param[in] task The task added to the queue. + */ + void AddTask( LoadingTask* task ); + +private: + + // Undefined + CompleteQueue( const CompleteQueue& queue ); + + // Undefined + CompleteQueue& operator=( const CompleteQueue& queue ); + +private: + + Vector< LoadingTask* > mTasks; + Dali::Mutex mMutex; + EventThreadCallback* mTrigger; +}; + +/** + * The worker thread for image loading. + */ +class ImageLoadThread : public Thread +{ +public: + + /** + * Constructor. + * + * @param[in] loadQueue The task queue with images for loading. + * @param[in] completeQurue The task queue with images loaded. + */ + ImageLoadThread( LoadQueue& loadQueue, CompleteQueue& completeQueue ); + + /** + * Destructor. + */ + virtual ~ImageLoadThread(); + +protected: + + /** + * The entry function of the worker thread. + * It fetches loading task from the loadQueue, loads the image and adds to the completeQueue. + */ + virtual void Run(); + +private: + + // Undefined + ImageLoadThread( const ImageLoadThread& thread ); + + // Undefined + ImageLoadThread& operator=( const ImageLoadThread& thread ); + +private: + + LoadQueue& mLoadQueue; /// +// EXTERNAL INCLUDES +#include + namespace Dali { @@ -89,11 +92,17 @@ public: /** * @brief Create an initialized ImageView from an Image resource url * + * @note A valid size is preferable for efficiency. + * However, do not set size that is bigger than the actual image size, as the up-scaling is not available, + * the content of the area not covered by actual image is undefined, it will not be cleared. + * * If the string is empty, ImageView will display nothing * @param[in] url The url of the image resource to display. + * @param [in] size The width and height to fit the loaded image to. * @return A handle to a newly allocated ImageView. */ - static ImageView New( const std::string& url ); + static ImageView New( const std::string& url, + ImageDimensions size = ImageDimensions() ); /** * @brief Destructor @@ -144,8 +153,10 @@ public: * @since DALi 1.1.4 * * @param[in] url The Image resource to display. + * @param [in] size The width and height to fit the loaded image to. */ - void SetImage( const std::string& url ); + void SetImage( const std::string& url, + ImageDimensions size = ImageDimensions() ); /** * @deprecated Gets the Image