Check for NULL resource ticket pointer
[platform/core/uifw/dali-core.git] / dali / internal / event / images / image-factory.cpp
1 /*
2  * Copyright (c) 2014 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/internal/event/images/image-factory.h>
20
21 // INTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <dali/integration-api/platform-abstraction.h>
24 #include <dali/public-api/common/dali-common.h>
25 #include <dali/public-api/common/constants.h>
26 #include <dali/internal/event/common/thread-local-storage.h>
27 #include <dali/internal/event/common/notification-manager.h>
28 #include <dali/internal/common/event-to-update.h>
29 #include <dali/internal/event/resources/resource-client.h>
30 #include <dali/internal/update/resources/resource-manager.h>
31 #include <dali/internal/common/dali-hash.h>
32
33 // EXTERNAL INCLUDES
34 #include <float.h>
35
36 using namespace Dali::Integration;
37 using namespace Dali::Internal::ImageFactoryCache;
38
39 namespace Dali
40 {
41
42 namespace Internal
43 {
44
45 ImageFactory::ImageFactory( ResourceClient& resourceClient )
46 : mResourceClient(resourceClient),
47   mMaxScale(0.5f),
48   mReqIdCurrent(0)
49 {
50 }
51
52 ImageFactory::~ImageFactory()
53 {
54   // Request memory is freed up by intrusive_ptr
55
56   mRequestCache.clear();
57 }
58
59 Request* ImageFactory::RegisterRequest( const std::string &filename, const ImageAttributes *attr )
60 {
61   // check url cache
62   // check if same request exists
63   std::size_t urlHash = CalculateHash( filename );
64
65   Request* foundReq( NULL );
66   foundReq = FindRequest(filename, urlHash, attr);
67
68   if( !foundReq )
69   {
70     // the same request hasn't been made before
71     foundReq = InsertNewRequest( 0, filename, urlHash, attr );
72   }
73
74   return foundReq;
75 }
76
77 ResourceTicketPtr ImageFactory::Load( Request *req )
78 {
79   ResourceTicketPtr ticket;
80   DALI_ASSERT_DEBUG( req );
81
82   ResourceId resId = req->resourceId;
83   if( resId == 0 )
84   {
85     // not yet associated with a resource, load
86     std::size_t urlHash(0);
87     RequestPathHashMap::const_iterator it;
88     for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
89     {
90       if( it->second == req->GetId() )
91       {
92         urlHash = it->first;
93         break;
94       }
95     }
96     DALI_ASSERT_DEBUG( it!=mUrlCache.end() );
97
98     ticket = FindCompatibleResource( req->url, urlHash, req->attributes );
99
100     if( !ticket )
101     {
102       // didn't find compatible resource
103       ticket = IssueLoadRequest( req->url, req->attributes );
104     }
105
106     req->resourceId = ticket->GetId();
107   }
108   else
109   {
110     ticket = mResourceClient.RequestResourceTicket( resId );
111     if( !ticket )
112     {
113       // resource has been discarded since
114       ticket = IssueLoadRequest( req->url, req->attributes );
115       req->resourceId = ticket->GetId();
116     }
117     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
118                        ticket->GetTypePath().type->id == ResourceNativeImage ||
119                        ticket->GetTypePath().type->id == ResourceTargetImage );
120   }
121
122   return ticket;
123 }
124
125 // File can change on fs, but still requesting same attributes.
126 // Returning the ticket is important, because if two different requests mapped to the same resource
127 // before, it is not guaranteed that they will still map to the same resource after reloading.
128 // Example:
129 // Image size (40, 40), Req1(img, 40, 40), Req2(img, 256, 256)
130 // In this case both requests will be associated with the resource of size (40, 40)
131 // If image changes on filesystem to size (96, 96) -> now after reloading Req2 would load a
132 // new resource of size (96, 96), but reloading Req1 would load a scaled down version
133 ResourceTicketPtr ImageFactory::Reload( Request* request )
134 {
135   DALI_ASSERT_ALWAYS( request );
136
137   // go through requests, check real size and attributes again. If different, update related ticket.
138   ResourceTicketPtr ticket;
139
140   if( !request->resourceId )
141   {
142     // in case of OnDemand loading, just return
143     return NULL;
144   }
145
146   ticket = mResourceClient.RequestResourceTicket( request->resourceId );
147
148   // ticket might have been deleted, eg. Image::Disconnect
149   if( !ticket )
150   {
151     ticket = IssueLoadRequest( request->url, request->attributes );
152     request->resourceId = ticket->GetId();
153   }
154   else // ticket still alive
155   {
156     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
157                        ticket->GetTypePath().type->id == ResourceNativeImage ||
158                        ticket->GetTypePath().type->id == ResourceTargetImage );
159
160     // do not reload if still loading
161     if ( ticket->GetLoadingState() == ResourceLoading )
162     {
163       return ticket;
164     }
165
166     Vector2 size;
167     Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request->url, *request->attributes, size );
168
169     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
170
171     if( size == attrib.GetSize() )
172     {
173       mResourceClient.ReloadResource( ticket->GetId(), false );
174     }
175     else
176     {
177       // if not, return a different ticket
178       ticket = IssueLoadRequest( request->url, request->attributes );
179       request->resourceId = ticket->GetId();
180     }
181   }
182   return ticket;
183 }
184
185 void ImageFactory::RecoverFromContextLoss()
186 {
187   for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
188   {
189     // go through requests, reload with resource ticket's attributes.
190     Request* request = (*it).second;
191     if( request->resourceId )
192     {
193       ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
194
195       // do not reload if still loading
196       // check ticket is not NULL as the resource could have already been destroyed
197       if ( ticket && ticket->GetLoadingState() != ResourceLoading )
198       {
199         // Ensure the finished status is reset
200         mResourceClient.ReloadResource( ticket->GetId(), true );
201       }
202     }
203   }
204 }
205
206 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
207 {
208   if( request )
209   {
210     return request->url;
211   }
212
213   return String::EMPTY;
214 }
215
216 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
217 {
218   if( ticket )
219   {
220     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
221                        ticket->GetTypePath().type->id == ResourceNativeImage ||
222                        ticket->GetTypePath().type->id == ResourceTargetImage );
223     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
224     return attrib;
225   }
226   return ImageAttributes::DEFAULT_ATTRIBUTES;
227 }
228
229 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
230 {
231   if( request && request->attributes )
232   {
233     return *(request->attributes);
234   }
235
236   return ImageAttributes::DEFAULT_ATTRIBUTES;
237 }
238
239 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
240 {
241   if( ticket && ticket->GetLoadingState() != ResourceLoading )
242   {
243     // it is loaded so get the size from actual attributes
244     size = GetActualAttributes( ticket ).GetSize();
245   }
246   else
247   {
248     // not loaded so either loading or not yet loaded, ask platform abstraction
249     Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
250     platformAbstraction.GetClosestImageSize( GetRequestPath( request ), GetRequestAttributes( request ), size );
251   }
252 }
253
254 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
255 {
256   ResourceTicketPtr ticketPtr(ticket);
257   mTicketsToRelease.push_back(ticketPtr);
258 }
259
260 void ImageFactory::FlushReleaseQueue()
261 {
262   mTicketsToRelease.clear();
263 }
264
265 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
266                                       const Dali::ImageAttributes& actual ) const
267 {
268   // do not load image resource again if there is a similar resource loaded
269   // eg. if size is less than 50% different of what we have
270   // see explanation in image.h of what is deemed compatible
271   return (requested.GetScalingMode() ==  actual.GetScalingMode()) &&
272           (requested.GetPixelFormat() ==  actual.GetPixelFormat()) &&
273           (requested.GetFieldBorder() ==  actual.GetFieldBorder()) &&
274           (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) &&
275           (requested.IsDistanceField() == actual.IsDistanceField()) &&
276           (fabsf(requested.GetWidth()  -  actual.GetWidth())  < actual.GetWidth()  * mMaxScale) &&
277           (fabsf(requested.GetHeight() -  actual.GetHeight()) < actual.GetHeight() * mMaxScale);
278 }
279
280 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
281 {
282   ++mReqIdCurrent;
283   Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
284   mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
285   mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
286   return request;
287 }
288
289 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
290 {
291   // Search for a matching resource
292
293   // check whether the url has been used before
294   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
295
296   // look for exact matches first
297   for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
298   {
299     RequestId cachedReqId = it->second;
300
301     // get cached request
302     RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
303     DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
304     if( foundRequestIter != mRequestCache.end() )
305     {
306       const Request& cachedRequest = *(foundRequestIter->second);
307       const ImageAttributes* storedAttributes = cachedRequest.attributes;
308
309       // compare attributes: NULL means default attributes
310       if( !attributes )
311       {
312         attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
313       }
314       if( !storedAttributes )
315       {
316         storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
317       }
318
319       if( *attributes != *storedAttributes )
320       {
321         continue;
322       }
323
324       if( filename.compare( cachedRequest.url ) )
325       {
326         // hash collision, filenames don't match
327         continue;
328       }
329
330       // we've found an exact match
331       return foundRequestIter->second;
332     }
333   }
334
335   return NULL;
336 }
337
338 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
339 {
340   ResourceTicketPtr ticket;
341   // check whether the url has been used before
342   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
343
344   bool foundCompatible = false;
345   if( foundRequests.first != mUrlCache.end() )
346   {
347     // check if we have a compatible resource already loaded
348     for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
349     {
350       RequestId cachedReqId = it->second;
351
352       // get cached request
353       RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
354       DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
355       if( foundRequestIter != mRequestCache.end() )
356       {
357         Request& cachedRequest = *(foundRequestIter->second);
358         if( filename.compare( cachedRequest.url ) )
359         {
360           // hash collision, filenames don't match
361           continue;
362         }
363
364         if( !cachedRequest.resourceId )
365         {
366           continue;
367         }
368
369         ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
370         if( !ticket )
371         {
372           cachedRequest.resourceId = 0;
373           continue;
374         }
375
376         DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
377                            ticket->GetTypePath().type->id == ResourceNativeImage ||
378                            ticket->GetTypePath().type->id == ResourceTargetImage );
379
380         // check for compatible ImageAttributes
381         const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
382         if( !attr )
383         {
384           attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
385         }
386
387         // in case both attributes are default or they are matching custom ones
388         if( CompareAttributes( *attr, storedAttributes ) )
389         {
390           // found compatible resource
391           foundCompatible = true;
392           break;
393         }
394       }
395     } // for( it ...
396   } // foundRequests.first
397
398   if( !foundCompatible )
399   {
400     ticket.Reset();
401   }
402
403   return ticket;
404 }
405
406 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
407 {
408   ImageAttributes attributes;
409
410   if( attr )
411   {
412     attributes = *attr;
413   }
414   else
415   {
416     // query image size from file if NULL was provided
417     Vector2 size = Dali::Image::GetImageSize( filename );
418     attributes.SetSize( size.width, size.height );
419   }
420
421   BitmapResourceType resourceType( attributes );
422   ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
423   return ticket;
424 }
425
426 void ImageFactory::RequestDiscarded( const Request& req )
427 {
428   RequestId id( req.GetId() );
429   // find in mRequestCache
430   RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
431   DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
432
433   // memory is freed up by intrusive_ptr
434
435   mRequestCache.erase( foundRequestIter );
436
437   // find in mUrlCache
438   for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
439   {
440     if( id == it->second )
441     {
442       mUrlCache.erase( it );
443       break;
444     }
445   }
446 }
447
448 } // namespace Internal
449
450 } // namespace Dali