Merge "Fix GetHeightForWidth for text controller" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / npatch-loader.cpp
1 /*
2  * Copyright (c) 2023 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 <dali-toolkit/internal/visuals/npatch-loader.h>
20
21 // INTERNAL HEADERS
22 #include <dali-toolkit/internal/visuals/rendering-addon.h>
23
24 // EXTERNAL HEADERS
25 #include <dali/devel-api/common/hash.h>
26 #include <dali/integration-api/adaptor-framework/adaptor.h>
27 #include <dali/integration-api/debug.h>
28
29 namespace Dali
30 {
31 namespace Toolkit
32 {
33 namespace Internal
34 {
35 namespace
36 {
37 constexpr auto INVALID_CACHE_INDEX = int32_t{-1}; ///< Invalid Cache index
38 constexpr auto UNINITIALIZED_ID    = int32_t{0};  ///< uninitialised id, use to initialize ids
39
40 } // Anonymous namespace
41
42 NPatchLoader::NPatchLoader()
43 : mCurrentNPatchDataId(0),
44   mRemoveProcessorRegistered(false)
45 {
46 }
47
48 NPatchLoader::~NPatchLoader()
49 {
50   if(mRemoveProcessorRegistered && Adaptor::IsAvailable())
51   {
52     Adaptor::Get().UnregisterProcessor(*this, true);
53     mRemoveProcessorRegistered = false;
54   }
55 }
56
57 NPatchData::NPatchDataId NPatchLoader::GenerateUniqueNPatchDataId()
58 {
59   // Skip invalid id generation.
60   if(DALI_UNLIKELY(mCurrentNPatchDataId == NPatchData::INVALID_NPATCH_DATA_ID))
61   {
62     mCurrentNPatchDataId = 0;
63   }
64   return mCurrentNPatchDataId++;
65 }
66
67 NPatchData::NPatchDataId NPatchLoader::Load(TextureManager& textureManager, TextureUploadObserver* textureObserver, const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad, bool synchronousLoading)
68 {
69   std::shared_ptr<NPatchData> data = GetNPatchData(url, border, preMultiplyOnLoad);
70
71   DALI_ASSERT_ALWAYS(data.get() && "NPatchData creation failed!");
72
73   if(data->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
74   {
75     if(!synchronousLoading)
76     {
77       // NotifyObserver already done, so
78       // data will not iterate observer list.
79       // We need to call LoadComplete directly.
80       data->NotifyObserver(textureObserver, true);
81     }
82   }
83   else // if NOT_STARTED or LOADING or LOAD_FAILED, try to reload.
84   {
85     if(!synchronousLoading)
86     {
87       data->AddObserver(textureObserver);
88       // If still LOADING and async, don't need to request reload. Fast return.
89       if(data->GetLoadingState() == NPatchData::LoadingState::LOADING)
90       {
91         return data->GetId();
92       }
93     }
94
95     data->SetLoadingState(NPatchData::LoadingState::LOADING);
96
97     auto preMultiplyOnLoading = preMultiplyOnLoad ? TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD
98                                                   : TextureManager::MultiplyOnLoad::LOAD_WITHOUT_MULTIPLY;
99
100     Devel::PixelBuffer pixelBuffer = textureManager.LoadPixelBuffer(url, Dali::ImageDimensions(), FittingMode::DEFAULT, SamplingMode::BOX_THEN_LINEAR, synchronousLoading, data.get(), true, preMultiplyOnLoading);
101
102     if(pixelBuffer)
103     {
104       preMultiplyOnLoad = (preMultiplyOnLoading == TextureManager::MultiplyOnLoad::MULTIPLY_ON_LOAD) ? true : false;
105       data->SetLoadedNPatchData(pixelBuffer, preMultiplyOnLoad);
106     }
107     else if(synchronousLoading)
108     {
109       data->SetLoadingState(NPatchData::LoadingState::LOAD_FAILED);
110     }
111   }
112   return data->GetId();
113 }
114
115 int32_t NPatchLoader::GetCacheIndexFromId(const NPatchData::NPatchDataId id)
116 {
117   const unsigned int size = mCache.size();
118
119   for(unsigned int i = 0; i < size; ++i)
120   {
121     if(mCache[i].mData->GetId() == id)
122     {
123       return i;
124     }
125   }
126
127   return INVALID_CACHE_INDEX;
128 }
129
130 bool NPatchLoader::GetNPatchData(const NPatchData::NPatchDataId id, const NPatchData*& data)
131 {
132   int32_t cacheIndex = GetCacheIndexFromId(id);
133   if(cacheIndex != INVALID_CACHE_INDEX)
134   {
135     data = mCache[cacheIndex].mData.get();
136     return true;
137   }
138   data = nullptr;
139   return false;
140 }
141
142 void NPatchLoader::RequestRemove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
143 {
144   // Remove observer first
145   if(textureObserver)
146   {
147     int32_t cacheIndex = GetCacheIndexFromId(id);
148     if(cacheIndex != INVALID_CACHE_INDEX)
149     {
150       NPatchInfo& info(mCache[cacheIndex]);
151
152       info.mData->RemoveObserver(textureObserver);
153     }
154   }
155
156   mRemoveQueue.push_back({id, nullptr});
157
158   if(!mRemoveProcessorRegistered && Adaptor::IsAvailable())
159   {
160     mRemoveProcessorRegistered = true;
161     Adaptor::Get().RegisterProcessor(*this, true);
162   }
163 }
164
165 void NPatchLoader::Remove(NPatchData::NPatchDataId id, TextureUploadObserver* textureObserver)
166 {
167   int32_t cacheIndex = GetCacheIndexFromId(id);
168   if(cacheIndex == INVALID_CACHE_INDEX)
169   {
170     return;
171   }
172
173   NPatchInfo& info(mCache[cacheIndex]);
174
175   info.mData->RemoveObserver(textureObserver);
176
177   if(--info.mReferenceCount <= 0)
178   {
179     mCache.erase(mCache.begin() + cacheIndex);
180   }
181 }
182
183 void NPatchLoader::Process(bool postProcessor)
184 {
185   for(auto& iter : mRemoveQueue)
186   {
187     Remove(iter.first, iter.second);
188   }
189   mRemoveQueue.clear();
190
191   if(Adaptor::IsAvailable())
192   {
193     Adaptor::Get().UnregisterProcessor(*this, true);
194     mRemoveProcessorRegistered = false;
195   }
196 }
197
198 std::shared_ptr<NPatchData> NPatchLoader::GetNPatchData(const VisualUrl& url, const Rect<int>& border, bool& preMultiplyOnLoad)
199 {
200   std::size_t                              hash  = CalculateHash(url.GetUrl());
201   std::vector<NPatchInfo>::size_type       index = UNINITIALIZED_ID;
202   const std::vector<NPatchInfo>::size_type count = mCache.size();
203
204   NPatchInfo* infoPtr = nullptr;
205
206   for(; index < count; ++index)
207   {
208     if(mCache[index].mData->GetHash() == hash)
209     {
210       // hash match, check url as well in case of hash collision
211       if(mCache[index].mData->GetUrl().GetUrl() == url.GetUrl())
212       {
213         // Use cached data. Need to fast-out return.
214         if(mCache[index].mData->GetBorder() == border)
215         {
216           mCache[index].mReferenceCount++;
217           return mCache[index].mData;
218         }
219         else
220         {
221           if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
222           {
223             // If we only found LOAD_FAILED case, replace current data. We can reuse texture
224             if(infoPtr == nullptr || infoPtr->mData->GetLoadingState() != NPatchData::LoadingState::LOAD_COMPLETE)
225             {
226               infoPtr = &mCache[index];
227             }
228           }
229           // Still loading pixel buffer. We cannot reuse cached texture yet. Skip checking
230           else if(mCache[index].mData->GetLoadingState() == NPatchData::LoadingState::LOADING)
231           {
232             continue;
233           }
234           // if LOAD_FAILED, reuse this cached NPatchData, and try to load again.
235           else
236           {
237             if(infoPtr == nullptr)
238             {
239               infoPtr = &mCache[index];
240             }
241           }
242         }
243       }
244     }
245   }
246
247   // If this is new image loading, make new cache data
248   if(infoPtr == nullptr)
249   {
250     NPatchInfo info(std::make_shared<NPatchData>());
251     info.mData->SetId(GenerateUniqueNPatchDataId());
252     info.mData->SetHash(hash);
253     info.mData->SetUrl(url);
254     info.mData->SetBorder(border);
255     info.mData->SetPreMultiplyOnLoad(preMultiplyOnLoad);
256
257     mCache.emplace_back(std::move(info));
258     infoPtr = &mCache.back();
259   }
260   // Else if LOAD_COMPLETE, Same url but border is different - use the existing texture
261   else if(infoPtr->mData->GetLoadingState() == NPatchData::LoadingState::LOAD_COMPLETE)
262   {
263     NPatchInfo info(std::make_shared<NPatchData>());
264
265     info.mData->SetId(GenerateUniqueNPatchDataId());
266     info.mData->SetHash(hash);
267     info.mData->SetUrl(url);
268     info.mData->SetCroppedWidth(infoPtr->mData->GetCroppedWidth());
269     info.mData->SetCroppedHeight(infoPtr->mData->GetCroppedHeight());
270
271     info.mData->SetTextures(infoPtr->mData->GetTextures());
272
273     NPatchUtility::StretchRanges stretchRangesX;
274     stretchRangesX.PushBack(Uint16Pair(border.left, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.right)) ? info.mData->GetCroppedHeight() - border.right : 0)));
275
276     NPatchUtility::StretchRanges stretchRangesY;
277     stretchRangesY.PushBack(Uint16Pair(border.top, ((info.mData->GetCroppedWidth() >= static_cast<unsigned int>(border.bottom)) ? info.mData->GetCroppedHeight() - border.bottom : 0)));
278
279     info.mData->SetStretchPixelsX(stretchRangesX);
280     info.mData->SetStretchPixelsY(stretchRangesY);
281     info.mData->SetBorder(border);
282
283     info.mData->SetPreMultiplyOnLoad(infoPtr->mData->IsPreMultiplied());
284
285     info.mData->SetLoadingState(NPatchData::LoadingState::LOAD_COMPLETE);
286
287     mCache.emplace_back(std::move(info));
288     infoPtr = &mCache.back();
289   }
290   // Else, LOAD_FAILED. just increase reference so we can reuse it.
291   else
292   {
293     infoPtr->mReferenceCount++;
294   }
295
296   DALI_ASSERT_ALWAYS(infoPtr && "NPatchInfo creation failed!");
297
298   return infoPtr->mData;
299 }
300
301 } // namespace Internal
302
303 } // namespace Toolkit
304
305 } // namespace Dali