Provided color-conversion helpers
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / image-loader / image-atlas-impl.cpp
1 /*
2  * Copyright (c) 2016 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/devel-api/adaptor-framework/bitmap-loader.h>
26 #include <dali/integration-api/debug.h>
27
28 namespace Dali
29 {
30
31 namespace Toolkit
32 {
33
34 namespace Internal
35 {
36 typedef unsigned char PixelBuffer;
37
38 Texture ImageAtlas::PackToAtlas( const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects  )
39 {
40   // Record each block size
41   Dali::Vector<Uint16Pair> blockSizes;
42   SizeType count = pixelData.size();
43   for( SizeType index = 0; index < count; index++ )
44   {
45     blockSizes.PushBack( ImageDimensions( pixelData[index].GetWidth(), pixelData[index].GetHeight() ) );
46   }
47
48   // Ask atlasPacker for packing position of each block
49   Dali::Vector<Uint16Pair> packPositions;
50   ImageDimensions atlasSize = AtlasPacker::GroupPack( blockSizes, packPositions );
51
52   // Prepare for outout texture rect array
53   textureRects.Clear();
54   textureRects.Resize( count );
55
56   // create the texture for uploading the multiple pixel data
57   Texture atlasTexture = Texture::New( Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, atlasSize.GetWidth(), atlasSize.GetHeight() );
58
59   float atlasWidth = static_cast<float>( atlasTexture.GetWidth() );
60   float atlasHeight = static_cast<float>( atlasTexture.GetHeight() );
61   int packPositionX, packPositionY;
62   // Upload the pixel data one by one to its packing position, and record the texture rects
63   for( SizeType index = 0; index < count; index++ )
64   {
65     packPositionX = packPositions[index].GetX();
66     packPositionY = packPositions[index].GetY();
67     atlasTexture.Upload( pixelData[index], 0u, 0u,
68                          packPositionX, packPositionY,
69                          pixelData[index].GetWidth(), pixelData[index].GetHeight() );
70
71     // Apply the half pixel correction to avoid the color bleeding between neighbour blocks
72     textureRects[index].x = ( static_cast<float>( packPositionX ) +0.5f ) / atlasWidth; // left
73     textureRects[index].y = ( static_cast<float>( packPositionY ) +0.5f ) / atlasHeight; // right
74     textureRects[index].z = ( static_cast<float>( packPositionX + pixelData[index].GetWidth() )-0.5f ) / atlasWidth; // right
75     textureRects[index].w = ( static_cast<float>( packPositionY + pixelData[index].GetHeight() )-0.5f ) / atlasHeight;// bottom
76   }
77
78   return atlasTexture;
79 }
80
81 ImageAtlas::ImageAtlas( SizeType width, SizeType height, Pixel::Format pixelFormat )
82 : mAtlas( Texture::New( Dali::TextureType::TEXTURE_2D, pixelFormat, width, height ) ),
83   mPacker( width, height ),
84   mAsyncLoader( Toolkit::AsyncImageLoader::New() ),
85   mBrokenImageUrl(""),
86   mBrokenImageSize(),
87   mWidth( static_cast<float>(width) ),
88   mHeight( static_cast<float>( height ) ),
89   mPixelFormat( pixelFormat )
90 {
91   mAsyncLoader.ImageLoadedSignal().Connect( this, &ImageAtlas::UploadToAtlas );
92 }
93
94 ImageAtlas::~ImageAtlas()
95 {
96   const std::size_t count = mLoadingTaskInfoContainer.Count();
97   for( std::size_t i=0; i < count; ++i )
98   {
99     // Call unregister to every observer in the list.
100     // Note that, the Atlas can be registered to same observer multiple times, and the Unregister method only remove one item each time.
101     // In this way, the atlas is actually detached from a observer either every upload call invoked by this observer is completed or atlas is destroyed.
102     if( mLoadingTaskInfoContainer[i]->observer )
103     {
104       mLoadingTaskInfoContainer[i]->observer->Unregister( *this );
105     }
106   }
107
108   mLoadingTaskInfoContainer.Clear();
109 }
110
111 IntrusivePtr<ImageAtlas> ImageAtlas::New( SizeType width, SizeType height, Pixel::Format pixelFormat )
112 {
113   IntrusivePtr<ImageAtlas> internal = new ImageAtlas( width, height, pixelFormat );
114
115   return internal;
116 }
117
118 Texture ImageAtlas::GetAtlas()
119 {
120   return mAtlas;
121 }
122
123 float ImageAtlas::GetOccupancyRate() const
124 {
125   return 1.f - static_cast<float>( mPacker.GetAvailableArea() ) / ( mWidth*mHeight );
126 }
127
128 void ImageAtlas::SetBrokenImage( const std::string& brokenImageUrl )
129 {
130   mBrokenImageSize = ResourceImage::GetImageSize( brokenImageUrl );
131   if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0 ) // check the url is valid
132   {
133     mBrokenImageUrl = brokenImageUrl;
134   }
135 }
136
137 bool ImageAtlas::Upload( Vector4& textureRect,
138                          const std::string& url,
139                          ImageDimensions size,
140                          FittingMode::Type fittingMode,
141                          bool orientationCorrection,
142                          AtlasUploadObserver* atlasUploadObserver )
143 {
144   ImageDimensions dimensions = size;
145   ImageDimensions zero;
146   if( size == zero ) // image size not provided
147   {
148     dimensions = ResourceImage::GetImageSize( url );
149     if( dimensions == zero ) // Fail to read the image & broken image file exists
150     {
151       if( !mBrokenImageUrl.empty() )
152       {
153         return Upload( textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver );
154       }
155       else
156       {
157         textureRect = Vector4::ZERO;
158         return true;
159       }
160     }
161   }
162
163   unsigned int packPositionX = 0;
164   unsigned int packPositionY = 0;
165   if( mPacker.Pack( dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY ) )
166   {
167     unsigned short loadId = mAsyncLoader.Load( url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection );
168     mLoadingTaskInfoContainer.PushBack( new LoadingTaskInfo( loadId, packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight(), atlasUploadObserver ) );
169     // apply the half pixel correction
170     textureRect.x = ( static_cast<float>( packPositionX ) +0.5f ) / mWidth; // left
171     textureRect.y = ( static_cast<float>( packPositionY ) +0.5f ) / mHeight; // right
172     textureRect.z = ( static_cast<float>( packPositionX + dimensions.GetX() )-0.5f ) / mWidth; // right
173     textureRect.w = ( static_cast<float>( packPositionY + dimensions.GetY() )-0.5f ) / mHeight;// bottom
174
175     if( atlasUploadObserver )
176     {
177       // register to the observer,
178       // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
179       atlasUploadObserver->Register( *this );
180     }
181
182     return true;
183   }
184
185   return false;
186 }
187
188 bool ImageAtlas::Upload( Vector4& textureRect, PixelData pixelData )
189 {
190   unsigned int packPositionX = 0;
191   unsigned int packPositionY = 0;
192   if( mPacker.Pack( pixelData.GetWidth(), pixelData.GetHeight(), packPositionX, packPositionY ) )
193   {
194     mAtlas.Upload( pixelData, 0u, 0u, packPositionX, packPositionY, pixelData.GetWidth(), pixelData.GetHeight() );
195
196     // apply the half pixel correction
197     textureRect.x = ( static_cast<float>( packPositionX ) +0.5f ) / mWidth; // left
198     textureRect.y = ( static_cast<float>( packPositionY ) +0.5f ) / mHeight; // right
199     textureRect.z = ( static_cast<float>( packPositionX + pixelData.GetWidth() )-0.5f ) / mWidth; // right
200     textureRect.w = ( static_cast<float>( packPositionY + pixelData.GetHeight() )-0.5f ) / mHeight;// bottom
201
202     return true;
203   }
204
205   return false;
206 }
207
208 void ImageAtlas::Remove( const Vector4& textureRect )
209 {
210   mPacker.DeleteBlock( static_cast<SizeType>(textureRect.x*mWidth),
211                        static_cast<SizeType>(textureRect.y*mHeight),
212                        static_cast<SizeType>((textureRect.z-textureRect.x)*mWidth+1.f),
213                        static_cast<SizeType>((textureRect.w-textureRect.y)*mHeight+1.f) );
214 }
215
216 void ImageAtlas::ObserverDestroyed( AtlasUploadObserver* observer )
217 {
218   const std::size_t count = mLoadingTaskInfoContainer.Count();
219   for( std::size_t i=0; i < count; ++i )
220   {
221     if( mLoadingTaskInfoContainer[i]->observer == observer )
222     {
223       // the observer is destructing, so its member function should not be called anymore
224       mLoadingTaskInfoContainer[i]->observer = NULL;
225     }
226   }
227 }
228
229 void ImageAtlas::UploadToAtlas( uint32_t id, PixelData pixelData )
230 {
231   if(  mLoadingTaskInfoContainer[0]->loadTaskId == id)
232   {
233     Rect<unsigned int> packRect( mLoadingTaskInfoContainer[0]->packRect  );
234     if( !pixelData || ( pixelData.GetWidth() ==0 && pixelData.GetHeight() == 0 ))
235     {
236       if(!mBrokenImageUrl.empty()) // replace with the broken image
237       {
238         UploadBrokenImage( packRect );
239       }
240     }
241     else
242     {
243       if( pixelData.GetWidth() < packRect.width || pixelData.GetHeight() < packRect.height  )
244       {
245         DALI_LOG_ERROR( "Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n",
246             pixelData.GetWidth(), pixelData.GetHeight(),
247             packRect.width, packRect.height );
248       }
249
250       mAtlas.Upload( pixelData, 0u, 0u, packRect.x, packRect.y, packRect.width, packRect.height );
251     }
252
253     if( mLoadingTaskInfoContainer[0]->observer )
254     {
255       mLoadingTaskInfoContainer[0]->observer->UploadCompleted();
256       mLoadingTaskInfoContainer[0]->observer->Unregister( *this );
257     }
258
259     mLoadingTaskInfoContainer.Erase( mLoadingTaskInfoContainer.Begin() );
260   }
261 }
262
263 void ImageAtlas::UploadBrokenImage( const Rect<unsigned int>& area )
264 {
265   BitmapLoader loader = BitmapLoader::New(mBrokenImageUrl, ImageDimensions( area.width, area.height ) );
266   loader.Load();
267   SizeType loadedWidth = loader.GetPixelData().GetWidth();
268   SizeType loadedHeight = loader.GetPixelData().GetHeight();
269
270   bool needBackgroundClear = false;
271   SizeType packX = area.x;
272   SizeType packY = area.y;
273   // locate the broken image in the middle.
274   if( area.width > loadedWidth)
275   {
276     packX += (area.width - loadedWidth)/2;
277     needBackgroundClear = true;
278   }
279   if( area.height > loadedHeight)
280   {
281     packY += (area.height - loadedHeight)/2;
282     needBackgroundClear = true;
283   }
284
285   if( needBackgroundClear )
286   {
287     SizeType size = area.width * area.height * Pixel::GetBytesPerPixel( mPixelFormat );
288     PixelBuffer* buffer = new PixelBuffer [size];
289     PixelData background = PixelData::New( buffer, size, area.width, area.height, mPixelFormat, PixelData::DELETE_ARRAY );
290     for( SizeType idx = 0; idx < size; idx++ )
291     {
292       buffer[idx] = 0x00;
293     }
294     mAtlas.Upload( background, 0u, 0u, area.x, area.y, area.width, area.height );
295   }
296
297   mAtlas.Upload( loader.GetPixelData(), 0u, 0u, packX, packY, loadedWidth, loadedHeight );
298 }
299
300 } // namespace Internal
301
302 } // namespace Toolkit
303
304 } // namespace Dali