DALi Version 2.1.5
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / image-loader / image-atlas-impl.cpp
1 /*
2  * Copyright (c) 2021 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 <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/signals/callback.h>
25 #include <string.h>
26
27 // INTERNAL HEADERS
28 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
29
30 namespace Dali
31 {
32 namespace Toolkit
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, packPositionX, packPositionY, pixelData[index].GetWidth(), pixelData[index].GetHeight());
68
69     // Apply the half pixel correction to avoid the color bleeding between neighbour blocks
70     textureRects[index].x = (static_cast<float>(packPositionX) + 0.5f) / atlasWidth;                                 // left
71     textureRects[index].y = (static_cast<float>(packPositionY) + 0.5f) / atlasHeight;                                // right
72     textureRects[index].z = (static_cast<float>(packPositionX + pixelData[index].GetWidth()) - 0.5f) / atlasWidth;   // right
73     textureRects[index].w = (static_cast<float>(packPositionY + pixelData[index].GetHeight()) - 0.5f) / atlasHeight; // bottom
74   }
75
76   return atlasTexture;
77 }
78
79 ImageAtlas::ImageAtlas(SizeType width, SizeType height, Pixel::Format pixelFormat)
80 : mAtlas(Texture::New(Dali::TextureType::TEXTURE_2D, pixelFormat, width, height)),
81   mPacker(width, height),
82   mAsyncLoader(Toolkit::AsyncImageLoader::New()),
83   mBrokenImageUrl(""),
84   mBrokenImageSize(),
85   mWidth(static_cast<float>(width)),
86   mHeight(static_cast<float>(height)),
87   mPixelFormat(pixelFormat)
88 {
89   mAsyncLoader.ImageLoadedSignal().Connect(this, &ImageAtlas::UploadToAtlas);
90 }
91
92 ImageAtlas::~ImageAtlas()
93 {
94   const std::size_t count = mLoadingTaskInfoContainer.Count();
95   for(std::size_t i = 0; i < count; ++i)
96   {
97     // Call unregister to every observer in the list.
98     // Note that, the Atlas can be registered to same observer multiple times, and the Unregister method only remove one item each time.
99     // 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.
100     if(mLoadingTaskInfoContainer[i]->observer)
101     {
102       mLoadingTaskInfoContainer[i]->observer->Unregister(*this);
103     }
104   }
105
106   mLoadingTaskInfoContainer.Clear();
107 }
108
109 IntrusivePtr<ImageAtlas> ImageAtlas::New(SizeType width, SizeType height, Pixel::Format pixelFormat)
110 {
111   IntrusivePtr<ImageAtlas> internal = new ImageAtlas(width, height, pixelFormat);
112
113   return internal;
114 }
115
116 Texture ImageAtlas::GetAtlas()
117 {
118   return mAtlas;
119 }
120
121 float ImageAtlas::GetOccupancyRate() const
122 {
123   return 1.f - static_cast<float>(mPacker.GetAvailableArea()) / (mWidth * mHeight);
124 }
125
126 void ImageAtlas::SetBrokenImage(const std::string& brokenImageUrl)
127 {
128   mBrokenImageSize = Dali::GetClosestImageSize(brokenImageUrl);
129   if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0) // check the url is valid
130   {
131     mBrokenImageUrl = brokenImageUrl;
132   }
133 }
134
135 bool ImageAtlas::Upload(Vector4&             textureRect,
136                         const VisualUrl&     url,
137                         ImageDimensions      size,
138                         FittingMode::Type    fittingMode,
139                         bool                 orientationCorrection,
140                         AtlasUploadObserver* atlasUploadObserver)
141 {
142   ImageDimensions dimensions = size;
143   ImageDimensions zero;
144   if(size == zero) // image size not provided
145   {
146     dimensions = Dali::GetClosestImageSize(url.GetUrl());
147     if(dimensions == zero) // Fail to read the image & broken image file exists
148     {
149       if(!mBrokenImageUrl.empty())
150       {
151         return Upload(textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver);
152       }
153       else
154       {
155         textureRect = Vector4::ZERO;
156         return true;
157       }
158     }
159   }
160
161   unsigned int packPositionX = 0;
162   unsigned int packPositionY = 0;
163   if(mPacker.Pack(dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY))
164   {
165     unsigned short loadId = GetImplementation(mAsyncLoader).Load(url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad::OFF);
166     mLoadingTaskInfoContainer.PushBack(new LoadingTaskInfo(loadId, packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight(), atlasUploadObserver));
167     // apply the half pixel correction
168     textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth;                      // left
169     textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight;                     // right
170     textureRect.z = (static_cast<float>(packPositionX + dimensions.GetX()) - 0.5f) / mWidth;  // right
171     textureRect.w = (static_cast<float>(packPositionY + dimensions.GetY()) - 0.5f) / mHeight; // bottom
172
173     if(atlasUploadObserver)
174     {
175       // register to the observer,
176       // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
177       atlasUploadObserver->Register(*this);
178     }
179
180     return true;
181   }
182
183   return false;
184 }
185
186 bool ImageAtlas::Upload(Vector4& textureRect, PixelData pixelData)
187 {
188   unsigned int packPositionX = 0;
189   unsigned int packPositionY = 0;
190   if(mPacker.Pack(pixelData.GetWidth(), pixelData.GetHeight(), packPositionX, packPositionY))
191   {
192     mAtlas.Upload(pixelData, 0u, 0u, packPositionX, packPositionY, pixelData.GetWidth(), pixelData.GetHeight());
193
194     // apply the half pixel correction
195     textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth;                          // left
196     textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight;                         // right
197     textureRect.z = (static_cast<float>(packPositionX + pixelData.GetWidth()) - 0.5f) / mWidth;   // right
198     textureRect.w = (static_cast<float>(packPositionY + pixelData.GetHeight()) - 0.5f) / mHeight; // bottom
199
200     return true;
201   }
202
203   return false;
204 }
205
206 void ImageAtlas::Remove(const Vector4& textureRect)
207 {
208   mPacker.DeleteBlock(static_cast<SizeType>(textureRect.x * mWidth),
209                       static_cast<SizeType>(textureRect.y * mHeight),
210                       static_cast<SizeType>((textureRect.z - textureRect.x) * mWidth + 1.f),
211                       static_cast<SizeType>((textureRect.w - textureRect.y) * mHeight + 1.f));
212 }
213
214 void ImageAtlas::ObserverDestroyed(AtlasUploadObserver* observer)
215 {
216   const std::size_t count = mLoadingTaskInfoContainer.Count();
217   for(std::size_t i = 0; i < count; ++i)
218   {
219     if(mLoadingTaskInfoContainer[i]->observer == observer)
220     {
221       // the observer is destructing, so its member function should not be called anymore
222       mLoadingTaskInfoContainer[i]->observer = NULL;
223     }
224   }
225 }
226
227 void ImageAtlas::UploadToAtlas(uint32_t id, PixelData pixelData)
228 {
229   if(mLoadingTaskInfoContainer[0]->loadTaskId == id)
230   {
231     Rect<unsigned int> packRect(mLoadingTaskInfoContainer[0]->packRect);
232     if(!pixelData || (pixelData.GetWidth() == 0 && pixelData.GetHeight() == 0))
233     {
234       if(!mBrokenImageUrl.empty()) // replace with the broken image
235       {
236         UploadBrokenImage(packRect);
237       }
238     }
239     else
240     {
241       if(pixelData.GetWidth() < packRect.width || pixelData.GetHeight() < packRect.height)
242       {
243         DALI_LOG_ERROR("Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n",
244                        pixelData.GetWidth(),
245                        pixelData.GetHeight(),
246                        packRect.width,
247                        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   Devel::PixelBuffer brokenBuffer = LoadImageFromFile(mBrokenImageUrl, ImageDimensions(area.width, area.height));
266   SizeType           loadedWidth  = brokenBuffer.GetWidth();
267   SizeType           loadedHeight = brokenBuffer.GetHeight();
268
269   bool     needBackgroundClear = false;
270   SizeType packX               = area.x;
271   SizeType packY               = area.y;
272   // locate the broken image in the middle.
273   if(area.width > loadedWidth)
274   {
275     packX += (area.width - loadedWidth) / 2;
276     needBackgroundClear = true;
277   }
278   if(area.height > loadedHeight)
279   {
280     packY += (area.height - loadedHeight) / 2;
281     needBackgroundClear = true;
282   }
283
284   if(needBackgroundClear)
285   {
286     SizeType           size       = area.width * area.height * Pixel::GetBytesPerPixel(mPixelFormat);
287     Devel::PixelBuffer background = Devel::PixelBuffer::New(area.width, area.height, mPixelFormat);
288     unsigned char*     buffer     = background.GetBuffer();
289     for(SizeType idx = 0; idx < size; idx++)
290     {
291       buffer[idx] = 0x00;
292     }
293     PixelData pixelData = Devel::PixelBuffer::Convert(background);
294     mAtlas.Upload(pixelData, 0u, 0u, area.x, area.y, area.width, area.height);
295   }
296
297   PixelData brokenPixelData = Devel::PixelBuffer::Convert(brokenBuffer);
298   mAtlas.Upload(brokenPixelData, 0u, 0u, packX, packY, loadedWidth, loadedHeight);
299 }
300
301 } // namespace Internal
302
303 } // namespace Toolkit
304
305 } // namespace Dali