a46c6f2792db72f62a6fcc303187ef380c1c7bba
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / image-atlas / image-atlas-impl.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include "image-atlas-impl.h"
20
21 // EXTERNAL INCLUDES
22 #include <string.h>
23 #include <dali/public-api/signals/callback.h>
24 #include <dali/public-api/images/resource-image.h>
25 #include <dali/integration-api/debug.h>
26
27 namespace Dali
28 {
29
30 namespace Toolkit
31 {
32
33 namespace Internal
34 {
35
36 ImageAtlas::ImageAtlas( SizeType width, SizeType height, Pixel::Format pixelFormat )
37 : mPacker( width, height ),
38   mLoadQueue(),
39   mCompleteQueue( new EventThreadCallback( MakeCallback( this, &ImageAtlas::UploadToAtlas ) ) ),
40   mLoadingThread( mLoadQueue, mCompleteQueue ),
41   mBrokenImageUrl(""),
42   mBrokenImageSize(),
43   mPixelFormat( pixelFormat ),
44   mLoadingThreadStarted( false )
45 {
46   mAtlas = Atlas::New( width, height, pixelFormat );
47   mWidth = static_cast<float>(width);
48   mHeight = static_cast<float>( height );
49 }
50
51 ImageAtlas::~ImageAtlas()
52 {
53   if( mLoadingThreadStarted )
54   {
55     // add an empty task would stop the loading thread from contional wait.
56     mLoadQueue.AddTask( NULL );
57     // stop the loading thread
58     mLoadingThread.Join();
59     // The atlas can still be used as texture after ImageAtlas has been thrown away,
60     // so make sure all the loaded bitmap been uploaded to atlas
61     UploadToAtlas();
62   }
63 }
64
65 IntrusivePtr<ImageAtlas> ImageAtlas::New( SizeType width, SizeType height, Pixel::Format pixelFormat )
66 {
67   IntrusivePtr<ImageAtlas> internal = new ImageAtlas( width, height, pixelFormat );
68   return internal;
69 }
70
71 Image ImageAtlas::GetAtlas()
72 {
73   return mAtlas;
74 }
75
76 void ImageAtlas::SetBrokenImage( const std::string& brokenImageUrl )
77 {
78   mBrokenImageSize = ResourceImage::GetImageSize( brokenImageUrl );
79   if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0 ) // check the url is valid
80   {
81     mBrokenImageUrl = brokenImageUrl;
82   }
83 }
84
85 bool ImageAtlas::Upload( Vector4& textureRect,
86                          const std::string& url,
87                          ImageDimensions size,
88                          FittingMode::Type fittingMode,
89                          bool orientationCorrection )
90 {
91   ImageDimensions dimensions = size;
92   ImageDimensions zero;
93   if( size == zero ) // image size not provided
94   {
95     dimensions = ResourceImage::GetImageSize( url );
96     if( dimensions == zero ) // Fail to read the image & broken image file exists
97     {
98       if( !mBrokenImageUrl.empty() )
99       {
100         return Upload( textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true );
101       }
102       else
103       {
104         textureRect = Vector4::ZERO;
105         return true;
106       }
107     }
108   }
109
110   if( static_cast<unsigned int>(dimensions.GetWidth() * dimensions.GetHeight()) > mPacker.GetAvailableArea() )
111   {
112     return false;
113   }
114
115   unsigned int packPositionX = 0;
116   unsigned int packPositionY = 0;
117   if( mPacker.Pack( dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY ) )
118   {
119     if( !mLoadingThreadStarted )
120     {
121       mLoadingThread.Start();
122       mLoadingThreadStarted = true;
123     }
124
125     LoadingTask* newTask = new LoadingTask(BitmapLoader::New(url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection ),
126                                            packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight());
127     mLoadQueue.AddTask( newTask );
128
129     // apply the half pixel correction
130     textureRect.x = ( static_cast<float>( packPositionX ) +0.5f ) / mWidth; // left
131     textureRect.y = ( static_cast<float>( packPositionY ) +0.5f ) / mHeight; // right
132     textureRect.z = ( static_cast<float>( packPositionX + dimensions.GetX() )-0.5f ) / mWidth; // right
133     textureRect.w = ( static_cast<float>( packPositionY + dimensions.GetY() )-0.5f ) / mHeight;// bottom
134
135     return true;
136   }
137
138   return false;
139 }
140
141 void ImageAtlas::Remove( const Vector4& textureRect )
142 {
143   mPacker.DeleteBlock( static_cast<SizeType>(textureRect.x*mWidth),
144                        static_cast<SizeType>(textureRect.y*mHeight),
145                        static_cast<SizeType>((textureRect.z-textureRect.x)*mWidth+1.f),
146                        static_cast<SizeType>((textureRect.w-textureRect.y)*mHeight+1.f) );
147 }
148
149 void ImageAtlas::UploadToAtlas()
150 {
151   while( LoadingTask* next = mCompleteQueue.NextTask() )
152   {
153     if( ! next->loader.IsLoaded() )
154     {
155       if(!mBrokenImageUrl.empty()) // replace with the broken image
156       {
157         UploadBrokenImage( next->packRect );
158       }
159
160       DALI_LOG_ERROR( "Failed to load the image: %s\n", (next->loader.GetUrl()).c_str());
161     }
162     else
163     {
164       if( next->loader.GetPixelData()->GetWidth() < next->packRect.width || next->loader.GetPixelData()->GetHeight() < next->packRect.height  )
165       {
166         DALI_LOG_ERROR( "Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n",
167                         next->loader.GetPixelData()->GetWidth(),
168                         next->loader.GetPixelData()->GetHeight(),
169                         next->packRect.width,
170                         next->packRect.height );
171       }
172
173       mAtlas.Upload( next->loader.GetPixelData(), next->packRect.x, next->packRect.y );
174     }
175
176     delete next;
177   }
178 }
179
180 void ImageAtlas::UploadBrokenImage( const Rect<SizeType>& area )
181 {
182   BitmapLoader loader = BitmapLoader::New(mBrokenImageUrl, ImageDimensions( area.width, area.height ) );
183   loader.Load();
184   SizeType loadedWidth = loader.GetPixelData()->GetWidth();
185   SizeType loadedHeight = loader.GetPixelData()->GetHeight();
186
187   bool needBackgroundClear = false;
188   SizeType packX = area.x;
189   SizeType packY = area.y;
190   // locate the broken image in the middle.
191   if( area.width > loadedWidth)
192   {
193     packX += (area.width - loadedWidth)/2;
194     needBackgroundClear = true;
195   }
196   if( area.height > loadedHeight)
197   {
198     packY += (area.height - loadedHeight)/2;
199     needBackgroundClear = true;
200   }
201
202   if( needBackgroundClear )
203   {
204     SizeType size = area.width * area.height * Pixel::GetBytesPerPixel( mPixelFormat );
205     PixelBuffer* buffer = new PixelBuffer [size];
206     PixelDataPtr background = PixelData::New( buffer, area.width, area.height, mPixelFormat, PixelData::DELETE_ARRAY );
207     for( SizeType idx = 0; idx < size; idx++ )
208     {
209       buffer[idx] = 0x00;
210     }
211     mAtlas.Upload( background, area.x, area.y );
212   }
213
214   mAtlas.Upload( loader.GetPixelData(), packX, packY );
215 }
216
217 } // namespace Internal
218
219 } // namespace Toolkit
220
221 } // namespace Dali