From 4c6d34154c81cf86b4c3662b81e1223f3326ceb5 Mon Sep 17 00:00:00 2001 From: Xiangyin Ma Date: Wed, 21 Oct 2015 16:52:35 +0100 Subject: [PATCH] Automatic image atlasing Change-Id: Iccff3a5aad466a696211b95f29d1411009cbf102 --- automated-tests/resources/gallery-small-1.jpg | Bin 0 -> 12797 bytes automated-tests/resources/icon-delete.png | Bin 0 -> 1773 bytes automated-tests/resources/icon-edit.png | Bin 0 -> 2356 bytes automated-tests/src/dali-toolkit/CMakeLists.txt | 5 + .../toolkit-event-thread-callback.cpp | 85 ++++++ .../toolkit-event-thread-callback.h | 61 +++++ .../src/dali-toolkit/utc-Dali-ImageAtlas.cpp | 294 +++++++++++++++++++++ build/tizen/dali-toolkit/Makefile.am | 2 + .../controls/renderer-factory/renderer-factory.cpp | 8 +- .../controls/renderer-factory/renderer-factory.h | 9 +- dali-toolkit/devel-api/file.list | 4 + dali-toolkit/devel-api/image-atlas/image-atlas.cpp | 87 ++++++ dali-toolkit/devel-api/image-atlas/image-atlas.h | 148 +++++++++++ .../controls/image-view/image-view-impl.cpp | 17 +- .../internal/controls/image-view/image-view-impl.h | 2 +- .../controls/renderers/control-renderer-impl.cpp | 4 +- .../controls/renderers/image-atlas-manager.cpp | 123 +++++++++ .../controls/renderers/image-atlas-manager.h | 127 +++++++++ .../controls/renderers/image/image-renderer.cpp | 119 ++++++--- .../controls/renderers/image/image-renderer.h | 37 ++- .../controls/renderers/npatch/npatch-renderer.cpp | 2 + .../controls/renderers/renderer-factory-cache.cpp | 4 +- .../controls/renderers/renderer-factory-cache.h | 4 +- .../controls/renderers/renderer-factory-impl.cpp | 32 ++- .../controls/renderers/renderer-factory-impl.h | 17 +- dali-toolkit/internal/file.list | 4 + dali-toolkit/internal/image-atlas/atlas-packer.cpp | 204 ++++++++++++++ dali-toolkit/internal/image-atlas/atlas-packer.h | 169 ++++++++++++ .../internal/image-atlas/image-atlas-impl.cpp | 221 ++++++++++++++++ .../internal/image-atlas/image-atlas-impl.h | 152 +++++++++++ .../internal/image-atlas/image-load-thread.cpp | 139 ++++++++++ .../internal/image-atlas/image-load-thread.h | 204 ++++++++++++++ .../public-api/controls/image-view/image-view.cpp | 10 +- .../public-api/controls/image-view/image-view.h | 15 +- 34 files changed, 2232 insertions(+), 77 deletions(-) create mode 100644 automated-tests/resources/gallery-small-1.jpg create mode 100644 automated-tests/resources/icon-delete.png create mode 100644 automated-tests/resources/icon-edit.png create mode 100644 automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.cpp create mode 100644 automated-tests/src/dali-toolkit/dali-toolkit-test-utils/toolkit-event-thread-callback.h create mode 100644 automated-tests/src/dali-toolkit/utc-Dali-ImageAtlas.cpp create mode 100644 dali-toolkit/devel-api/image-atlas/image-atlas.cpp create mode 100644 dali-toolkit/devel-api/image-atlas/image-atlas.h create mode 100644 dali-toolkit/internal/controls/renderers/image-atlas-manager.cpp create mode 100644 dali-toolkit/internal/controls/renderers/image-atlas-manager.h create mode 100644 dali-toolkit/internal/image-atlas/atlas-packer.cpp create mode 100644 dali-toolkit/internal/image-atlas/atlas-packer.h create mode 100644 dali-toolkit/internal/image-atlas/image-atlas-impl.cpp create mode 100644 dali-toolkit/internal/image-atlas/image-atlas-impl.h create mode 100644 dali-toolkit/internal/image-atlas/image-load-thread.cpp create mode 100644 dali-toolkit/internal/image-atlas/image-load-thread.h diff --git a/automated-tests/resources/gallery-small-1.jpg b/automated-tests/resources/gallery-small-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..92923109320188ba92173b32afe3b3a3da41a279 GIT binary patch literal 12797 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 literal 0 HcmV?d00001 diff --git a/automated-tests/resources/icon-delete.png b/automated-tests/resources/icon-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..f509cc0aa99783469cbc466ae99e887be9e4ceee GIT binary patch literal 1773 zcmZuw3p7+|9KRmrRi~Lyp}5^TrN_*eK{0tOW;_-tj3!cDGk45A^SIO8%Z%NqrM989 zvesF8Nm0ln#W}I`*4czYa-vX@+ES++X_VSKMs1zlbI*6b`~Uxc-|znZk1u`0`heNy zw&nl;%nl0Vgp)fS7cXwwvu^22CjYQD!Bsl@AX>xptjzZ7DL2!W*RS=|PP{#?!VF`pM1d(y@vp$*P z3%MMhPMS=gp@tOT|sG=(%F9+l%X7TuMswobF|FYE|RM5tgT^1mou>Y#^Ez*eI1 zVgjEs^|kPk5_(H-N>N<_*iu;6J~d5~$Z}%3fDy0^Pmsrh zTqzm#^iicTI3U5BLTwB#x4vt|#*HyP=nUp1FJqxX?RL(U-M%Mf~*%c2Th$?-M7 z!vlPQ%C?2iNMmMPZ~zDRqXrd;@bCiCX+i{ra!vY-sB`Gc zU)G4K0Kh0Gh~pC}?i=BUins8rKDN1!KWNb2Xo>6zVt%~6Va1+nh}|`cW5UpP)cT0q z8ms6Fw)gt*=_{tLpRe*CZ#U z$(D@-4IkD>V{J$ z-ki`@G!!K9IZEyQrFL_!J=c^Rzb9V!S2VuhQun&!P}#)n-64NGbmkCAy%d{6JL7#h zC-c!)6UBKpamFnT+1rmP+Ah1R!irr?E8T{-Uii>t(rsT?lW47+51#GTJbM-x{hP%8 zj%d&su*^CfxNT)^zuKN(6tKv!&Gn&eW?FS#nYaZsc&P6>s z?EU0z3-43!44}z3@wGO4^3&Fdv7W)Mp13I9j)C+?nc2x{qnlDbNd^k+YImMSs>W)v zj=EkB*}jb8)6ZrzdA4>H4U-)Ys{mdXkShof1q)8(Hdik>S7rb-z3zAyiArnUJZVq2 z=^ALWt&ZB0A2a8>OGB(uRl1nVyEvYgRpoj}6jpdKgyYI&~@o z$+c)I{BzA(AI_?g=i#SvC>tvlou&eY4;C#iqdq&5k&=5W)(UVe_|wrG68URC{4!(r8?8-mzMb{{$&qebvph3U6W=%|1eb3mejtdSpmhh~{&RyNx+*~Lz-0S=Hi7EoGI;V#81S~$}vuxz5v*9h% zv(K!|CpQZ_ODmaIU+5r$3Qd`p=Prr1(;?+OUQ~Z3?_zCuSU)D;)l8p1dT;ORhsm2% z!>lT5`xYsuN0?}oXY6D?aKj{U((V!iIH@Vgn7$M~*!kHrexx2*(K3OMHx>xuuIE(x HZcqIOKPbe# literal 0 HcmV?d00001 diff --git a/automated-tests/resources/icon-edit.png b/automated-tests/resources/icon-edit.png new file mode 100644 index 0000000000000000000000000000000000000000..ce3e3279c6c7acb9dc741d61201c93dd82cab427 GIT binary patch literal 2356 zcmZuzdpuNY7v3~UG}4()3TZd$xQ>}&+@^7hA)(wx>LkX@#xVAbxiC6)T#_itJqm?V zh;m6Z@s(>AQZ6~UgpN`PA*o1bhU)A5zVDB{-@V@VSQ2r002na z9ahew6)k?Gr9^L{jcgqNNIqn^y7Sx}?1?m1pbnMJ@`iK-fo#z^0FcZCflM$M;&2%( z*aVH&!J$D2rm^S@*w+M2aka)6q0LNXNCKiLBM{#LZj0l0@hQd6m00VPX#S}{%RuGrQVX%23$)K2%>doTwOwef2Irv>) zAo~Y7%>7ohsC=w|%EpSS#QqHCF?{|j()LK8Lxenoh$)Oehe7d0Y`5 zzeF!;8PSphQF$zmD~rW6LEACd3=Ese_hrCbjINHpsM})A;=d4GSU$WEDhK+%C~@f^ ziB2PO7`}cy?qayq3kxQ1DOw7d3~>X9OsaT%0t_AUq4JqLw5cVF9lDqcs?#(;T^ybO zx=;hTeApKxGa(v}!-5$!t~SVr=@19x`9Yusn@STc_6!;Xb0KZeZOJ2`t`3d}{xmk~ zB5tt>%q1)rYj0~w-sM6Tm53u@#S>yOzDZt;zH267|5ecv^rzv9a+dZ)rV)7%`@@8$ zGJ$S6`Xc*o;E~;V-#Z6bh^-CU22yJvEORYoZPskY z1T5?g)=1f{KI9?`|E@H=$FFpLrPK*?hEpQpQIn?oyJi077_zi~;pK2^ty~Fh`M6&; zSF|gdN;Lx3$x^g^u>^xo@Y&v@t7o2~t?ANE%N@v@KGCOo1mdv0i< zt-HFda)Zrr`Oj^CuKm+4+ENM;VRUu$bL*huQOZdRi}jb}w`<~?l7}))-gtKLDIFEb zXA0p?UX6TT-wuZ(yR07QQOsZk5cl zcHkbS|2^JOQ9t44My1RE%~|^V)LiP3GW1yxCs$0fH+Di*^}TnBjkcCvo^e}g>jlg6 z?@{O4d><*Mst;j>A79V`wqw$Zk0oGBPf*|aKPL@Ow>_x#1E`*$y#22o*;gJV6pUZ? z3V)?XCl{vDx2mMqq7b*fQjd)qM*75zmxXmA08g6ba_mAGvbTxCqmCXI92wemvvJiP!)ur#G znUhYNmHV*&Q*0o?AWEQx5fwkM+;<;Qqp=P(CO;Ic-asxOO+l1+1$lt^6R(@DDf;#N>`s{*RF1c;$bmj{pzc-~u$ zj8_{wuDip9U!tHKSMcyK+^8r0(K55%%_%HrC^6SKxt7zYnLRjj#N%W@k;?s&4X-%s z#!L_P*ey5Rn@G0O7&q86>y1p&_(du5cAI(BZVEr;^XHM~kYcU$!&ZZ@dSnOql&gy>{C{rOqXeSxvnZlAS4CPmUdukukpr>D{Cv} zzZ7lU=$`=pw%x8;L+%j%RbhI_DM zT0HGH?tSQB>C<$u*Gp=~d0h*1F;qa^(B?{{7(@>M(>Hej~X-v@lVHxPM?og zO_kz$^k2WxquuK~gYxC&!ay=PXj{r?kZ3+bK8@=$5|a7|toa(H4GGUUE5Z@#TYTJu zC~m+LB;Fvj6qIR7+hpoks)6P2_^K7Um+?YtV7}sv!~`b9^$+A~bFWiFPy2d*ZFncK zGEApsXIS2Fam}W}Gtb5rILF-aZH0N3c>e2+#y6Fj`p-`oXEcDqBr|M z@@qSaH8F~-2*974e5)ld33WzzTMd@`Z`g6WBB8psQo(0nmK>*@mRegr&^$BA0Ltmh zP|C$EH5P|PkwTlh`@B#w=y$dA_g*(=23PaCgk36Y33{??BqiDjdYZg zy6_;$Ps1vrTcXM!Y(AZZ%KGrUYn$Buj%}x8)~LEFBKHMYB?WLF$4%DzV;A1Q3!^uu zt%bYGT=ztFEl+NLxXK|;EA-$<&bn!dHD8KW5bpONlL(YFZT0;rscwjo_RUG!2L|Ly m^O++#1Ak3(2)hfvO5*_e6 +#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 -- 2.7.4