[dali_2.3.24] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / image-loader / image-atlas-impl.cpp
1 /*
2  * Copyright (c) 2022 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 Texture ImageAtlas::PackToAtlas(const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects)
37 {
38   // Record each block size
39   Dali::Vector<Uint16Pair> blockSizes;
40   SizeType                 count = pixelData.size();
41   for(SizeType index = 0; index < count; index++)
42   {
43     blockSizes.PushBack(ImageDimensions(pixelData[index].GetWidth(), pixelData[index].GetHeight()));
44   }
45
46   // Ask atlasPacker for packing position of each block
47   Dali::Vector<Uint16Pair> packPositions;
48   ImageDimensions          atlasSize = AtlasPacker::GroupPack(blockSizes, packPositions);
49
50   // Prepare for outout texture rect array
51   textureRects.Clear();
52   textureRects.Resize(count);
53
54   // create the texture for uploading the multiple pixel data
55   Texture atlasTexture = Texture::New(Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, atlasSize.GetWidth(), atlasSize.GetHeight());
56
57   float atlasWidth  = static_cast<float>(atlasTexture.GetWidth());
58   float atlasHeight = static_cast<float>(atlasTexture.GetHeight());
59   int   packPositionX, packPositionY;
60   // Upload the pixel data one by one to its packing position, and record the texture rects
61   for(SizeType index = 0; index < count; index++)
62   {
63     packPositionX = packPositions[index].GetX();
64     packPositionY = packPositions[index].GetY();
65     atlasTexture.Upload(pixelData[index], 0u, 0u, packPositionX, packPositionY, pixelData[index].GetWidth(), pixelData[index].GetHeight());
66
67     // Apply the half pixel correction to avoid the color bleeding between neighbour blocks
68     textureRects[index].x = (static_cast<float>(packPositionX) + 0.5f) / atlasWidth;                                 // left
69     textureRects[index].y = (static_cast<float>(packPositionY) + 0.5f) / atlasHeight;                                // top
70     textureRects[index].z = (static_cast<float>(packPositionX + pixelData[index].GetWidth()) - 0.5f) / atlasWidth;   // right
71     textureRects[index].w = (static_cast<float>(packPositionY + pixelData[index].GetHeight()) - 0.5f) / atlasHeight; // bottom
72   }
73
74   return atlasTexture;
75 }
76
77 ImageAtlas::ImageAtlas(SizeType width, SizeType height, Pixel::Format pixelFormat)
78 : mAtlas(Texture::New(Dali::TextureType::TEXTURE_2D, pixelFormat, width, height)),
79   mPacker(width, height),
80   mAsyncLoader(Toolkit::AsyncImageLoader::New()),
81   mBrokenImageUrl(""),
82   mBrokenImageSize(),
83   mWidth(static_cast<float>(width)),
84   mHeight(static_cast<float>(height)),
85   mPixelFormat(pixelFormat)
86 {
87   mAsyncLoader.ImageLoadedSignal().Connect(this, &ImageAtlas::UploadToAtlas);
88 }
89
90 ImageAtlas::~ImageAtlas()
91 {
92   if(mAsyncLoader)
93   {
94     mAsyncLoader.CancelAll();
95     mAsyncLoader.ImageLoadedSignal().Disconnect(this, &ImageAtlas::UploadToAtlas);
96   }
97
98   const std::size_t count = mLoadingTaskInfoContainer.Count();
99   for(std::size_t i = 0; i < count; ++i)
100   {
101     // Call unregister to every observer in the list.
102     // Note that, the Atlas can be registered to same observer multiple times, and the Unregister method only remove one item each time.
103     // 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.
104     if(mLoadingTaskInfoContainer[i]->observer)
105     {
106       mLoadingTaskInfoContainer[i]->observer->Unregister(*this);
107     }
108   }
109
110   mLoadingTaskInfoContainer.Clear();
111 }
112
113 IntrusivePtr<ImageAtlas> ImageAtlas::New(SizeType width, SizeType height, Pixel::Format pixelFormat)
114 {
115   IntrusivePtr<ImageAtlas> internal = new ImageAtlas(width, height, pixelFormat);
116
117   return internal;
118 }
119
120 Texture ImageAtlas::GetAtlas()
121 {
122   return mAtlas;
123 }
124
125 float ImageAtlas::GetOccupancyRate() const
126 {
127   return 1.f - static_cast<float>(mPacker.GetAvailableArea()) / (mWidth * mHeight);
128 }
129
130 void ImageAtlas::SetBrokenImage(const std::string& brokenImageUrl)
131 {
132   mBrokenImageSize = Dali::GetClosestImageSize(brokenImageUrl);
133   if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0) // check the url is valid
134   {
135     mBrokenImageUrl = brokenImageUrl;
136   }
137 }
138
139 bool ImageAtlas::Upload(Vector4&             textureRect,
140                         const VisualUrl&     url,
141                         ImageDimensions      size,
142                         FittingMode::Type    fittingMode,
143                         bool                 orientationCorrection,
144                         AtlasUploadObserver* atlasUploadObserver)
145 {
146   ImageDimensions dimensions = size;
147   ImageDimensions zero;
148   if(size == zero) // image size not provided
149   {
150     dimensions = Dali::GetClosestImageSize(url.GetUrl());
151     if(dimensions == zero) // Fail to read the image & broken image file exists
152     {
153       if(!mBrokenImageUrl.empty())
154       {
155         return Upload(textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver);
156       }
157       else
158       {
159         textureRect = Vector4::ZERO;
160         return true;
161       }
162     }
163   }
164
165   uint32_t packPositionX = 0;
166   uint32_t packPositionY = 0;
167   if(mPacker.Pack(dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY))
168   {
169     uint32_t loadId = GetImplementation(mAsyncLoader).Load(url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad::OFF, false);
170     mLoadingTaskInfoContainer.PushBack(new LoadingTaskInfo(loadId, packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight(), atlasUploadObserver));
171     // apply the half pixel correction
172     textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth;                      // left
173     textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight;                     // top
174     textureRect.z = (static_cast<float>(packPositionX + dimensions.GetX()) - 0.5f) / mWidth;  // right
175     textureRect.w = (static_cast<float>(packPositionY + dimensions.GetY()) - 0.5f) / mHeight; // bottom
176
177     if(atlasUploadObserver)
178     {
179       // register to the observer,
180       // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
181       atlasUploadObserver->Register(*this);
182     }
183
184     return true;
185   }
186
187   return false;
188 }
189
190 bool ImageAtlas::Upload(Vector4&                  textureRect,
191                         const EncodedImageBuffer& encodedImageBuffer,
192                         ImageDimensions           size,
193                         FittingMode::Type         fittingMode,
194                         bool                      orientationCorrection,
195                         AtlasUploadObserver*      atlasUploadObserver)
196 {
197   ImageDimensions zero;
198   if(size == zero) // image size not provided
199   {
200     DALI_LOG_ERROR("Desired size is zero! We need to setup desired size for Atlas.\n");
201     // EncodedImageBuffer didn't support to get closest image size.
202     // Just draw broken image.
203     if(!mBrokenImageUrl.empty())
204     {
205       return Upload(textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver);
206     }
207     else
208     {
209       textureRect = Vector4::ZERO;
210       return true;
211     }
212   }
213
214   uint32_t packPositionX = 0;
215   uint32_t packPositionY = 0;
216   if(mPacker.Pack(size.GetWidth(), size.GetHeight(), packPositionX, packPositionY))
217   {
218     uint32_t loadId = GetImplementation(mAsyncLoader).LoadEncodedImageBuffer(encodedImageBuffer, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad::OFF);
219     mLoadingTaskInfoContainer.PushBack(new LoadingTaskInfo(loadId, packPositionX, packPositionY, size.GetWidth(), size.GetHeight(), atlasUploadObserver));
220
221     // apply the half pixel correction
222     textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth;                // left
223     textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight;               // top
224     textureRect.z = (static_cast<float>(packPositionX + size.GetX()) - 0.5f) / mWidth;  // right
225     textureRect.w = (static_cast<float>(packPositionY + size.GetY()) - 0.5f) / mHeight; // bottom
226
227     if(atlasUploadObserver)
228     {
229       // register to the observer,
230       // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
231       atlasUploadObserver->Register(*this);
232     }
233
234     return true;
235   }
236
237   return false;
238 }
239
240 bool ImageAtlas::Upload(Vector4& textureRect, PixelData pixelData)
241 {
242   uint32_t packPositionX = 0;
243   uint32_t packPositionY = 0;
244   if(mPacker.Pack(pixelData.GetWidth(), pixelData.GetHeight(), packPositionX, packPositionY))
245   {
246     mAtlas.Upload(pixelData, 0u, 0u, packPositionX, packPositionY, pixelData.GetWidth(), pixelData.GetHeight());
247
248     // apply the half pixel correction
249     textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth;                          // left
250     textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight;                         // top
251     textureRect.z = (static_cast<float>(packPositionX + pixelData.GetWidth()) - 0.5f) / mWidth;   // right
252     textureRect.w = (static_cast<float>(packPositionY + pixelData.GetHeight()) - 0.5f) / mHeight; // bottom
253
254     return true;
255   }
256
257   return false;
258 }
259
260 void ImageAtlas::Remove(const Vector4& textureRect)
261 {
262   mPacker.DeleteBlock(static_cast<SizeType>(textureRect.x * mWidth),
263                       static_cast<SizeType>(textureRect.y * mHeight),
264                       static_cast<SizeType>((textureRect.z - textureRect.x) * mWidth + 1.f),
265                       static_cast<SizeType>((textureRect.w - textureRect.y) * mHeight + 1.f));
266 }
267
268 void ImageAtlas::ObserverDestroyed(AtlasUploadObserver* observer)
269 {
270   const std::size_t count = mLoadingTaskInfoContainer.Count();
271   for(std::size_t i = 0; i < count; ++i)
272   {
273     if(mLoadingTaskInfoContainer[i]->observer == observer)
274     {
275       // the observer is destructing, so its member function should not be called anymore
276       mLoadingTaskInfoContainer[i]->observer = NULL;
277     }
278   }
279 }
280
281 void ImageAtlas::UploadToAtlas(uint32_t id, PixelData pixelData)
282 {
283   auto end = mLoadingTaskInfoContainer.End();
284   for(auto loadingTaskIterator = mLoadingTaskInfoContainer.Begin(); loadingTaskIterator != end; ++loadingTaskIterator)
285   {
286     if((*loadingTaskIterator) && (*loadingTaskIterator)->loadTaskId == id)
287     {
288       Rect<uint32_t> packRect((*loadingTaskIterator)->packRect);
289       if(!pixelData || (pixelData.GetWidth() == 0 && pixelData.GetHeight() == 0))
290       {
291         if(!mBrokenImageUrl.empty()) // replace with the broken image
292         {
293           UploadBrokenImage(packRect);
294         }
295       }
296       else
297       {
298         if(pixelData.GetWidth() < packRect.width || pixelData.GetHeight() < packRect.height)
299         {
300           DALI_LOG_ERROR("Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n",
301                         pixelData.GetWidth(),
302                         pixelData.GetHeight(),
303                         packRect.width,
304                         packRect.height);
305         }
306
307         mAtlas.Upload(pixelData, 0u, 0u, packRect.x, packRect.y, packRect.width, packRect.height);
308       }
309
310       if((*loadingTaskIterator)->observer)
311       {
312         (*loadingTaskIterator)->observer->UploadCompleted();
313         (*loadingTaskIterator)->observer->Unregister(*this);
314       }
315
316       mLoadingTaskInfoContainer.Erase(loadingTaskIterator);
317       break;
318     }
319   }
320 }
321
322 void ImageAtlas::UploadBrokenImage(const Rect<uint32_t>& area)
323 {
324   Devel::PixelBuffer brokenBuffer = LoadImageFromFile(mBrokenImageUrl, ImageDimensions(area.width, area.height));
325   SizeType           loadedWidth  = brokenBuffer.GetWidth();
326   SizeType           loadedHeight = brokenBuffer.GetHeight();
327
328   bool     needBackgroundClear = false;
329   SizeType packX               = area.x;
330   SizeType packY               = area.y;
331   // locate the broken image in the middle.
332   if(area.width > loadedWidth)
333   {
334     packX += (area.width - loadedWidth) / 2;
335     needBackgroundClear = true;
336   }
337   if(area.height > loadedHeight)
338   {
339     packY += (area.height - loadedHeight) / 2;
340     needBackgroundClear = true;
341   }
342
343   if(needBackgroundClear)
344   {
345     SizeType           size       = area.width * area.height * Pixel::GetBytesPerPixel(mPixelFormat);
346     Devel::PixelBuffer background = Devel::PixelBuffer::New(area.width, area.height, mPixelFormat);
347     unsigned char*     buffer     = background.GetBuffer();
348     for(SizeType idx = 0; idx < size; idx++)
349     {
350       buffer[idx] = 0x00;
351     }
352     PixelData pixelData = Devel::PixelBuffer::Convert(background);
353     mAtlas.Upload(pixelData, 0u, 0u, area.x, area.y, area.width, area.height);
354   }
355
356   PixelData brokenPixelData = Devel::PixelBuffer::Convert(brokenBuffer);
357   mAtlas.Upload(brokenPixelData, 0u, 0u, packX, packY, loadedWidth, loadedHeight);
358 }
359
360 } // namespace Internal
361
362 } // namespace Toolkit
363
364 } // namespace Dali