66a1c64a126ed5c086cb38dcaa6c4d8842527152
[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( 4 / 1024.0f ), ///< Only allow a very tiny fudge factor in matching new requests to existing resource transactions: 4 pixels at a dimension of 1024, 2 at 512, ...
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& request )
78 {
79   ResourceTicketPtr ticket;
80
81   // See if any resource transaction has already been associated with this request:
82   const ResourceId resId = request.resourceId;
83   if( resId != 0 )
84   {
85     // An IO operation has been started at some time for the request so recover the
86     // ticket that was created for that:
87     ticket = mResourceClient.RequestResourceTicket( resId );  ///@note Always succeeds in normal use.
88   }
89   else
90   {
91     // Request not yet associated with a ticketed async resource transaction, so
92     // attempt to find a compatible cached one:
93     const std::size_t urlHash = GetHashForCachedRequest( request );
94     ticket = FindCompatibleResource( request.url, urlHash, request.attributes );
95   }
96
97   // Start a new resource IO transaction for the request if none is already happening:
98   if( !ticket )
99   {
100     ticket = IssueLoadRequest( request.url, request.attributes );
101   }
102   request.resourceId = ticket->GetId();
103
104   DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
105                      ticket->GetTypePath().type->id == ResourceNativeImage ||
106                      ticket->GetTypePath().type->id == ResourceTargetImage );
107   return ticket;
108 }
109
110 // File can change on fs, but still requesting same attributes.
111 // Returning the ticket is important, because if two different requests mapped to the same resource
112 // before, it is not guaranteed that they will still map to the same resource after reloading.
113 // Example:
114 // Image size (40, 40), Req1(img, 40, 40), Req2(img, 256, 256)
115 // In this case both requests will be associated with the resource of size (40, 40)
116 // If image changes on filesystem to size (96, 96) -> now after reloading Req2 would load a
117 // new resource of size (96, 96), but reloading Req1 would load a scaled down version
118 ResourceTicketPtr ImageFactory::Reload( Request& request )
119 {
120   DALI_ASSERT_ALWAYS( &request );
121
122   // go through requests, check real size and attributes again. If different, update related ticket.
123   ResourceTicketPtr ticket;
124
125   if( !request.resourceId )
126   {
127     // in case of OnDemand loading, just return
128     return NULL;
129   }
130
131   ticket = mResourceClient.RequestResourceTicket( request.resourceId );
132
133   // ticket might have been deleted, eg. Image::Disconnect
134   if( !ticket )
135   {
136     ticket = IssueLoadRequest( request.url, request.attributes );
137     request.resourceId = ticket->GetId();
138   }
139   else // ticket still alive
140   {
141     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
142                        ticket->GetTypePath().type->id == ResourceNativeImage ||
143                        ticket->GetTypePath().type->id == ResourceTargetImage );
144
145     // do not reload if still loading
146     if ( ticket->GetLoadingState() == ResourceLoading )
147     {
148       return ticket;
149     }
150
151     Vector2 size;
152     Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url, *request.attributes, size );
153
154     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
155
156     if( size == attrib.GetSize() )
157     {
158       mResourceClient.ReloadResource( ticket->GetId(), false );
159     }
160     else
161     {
162       // if not, return a different ticket
163       ticket = IssueLoadRequest( request.url, request.attributes );
164       request.resourceId = ticket->GetId();
165     }
166   }
167   return ticket;
168 }
169
170 void ImageFactory::RecoverFromContextLoss()
171 {
172   for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
173   {
174     // go through requests, reload with resource ticket's attributes.
175     Request* request = (*it).second;
176     if( request->resourceId )
177     {
178       ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
179
180       // do not reload if still loading
181       // check ticket is not NULL as the resource could have already been destroyed
182       if ( ticket && ticket->GetLoadingState() != ResourceLoading )
183       {
184         // Ensure the finished status is reset
185         mResourceClient.ReloadResource( ticket->GetId(), true );
186       }
187     }
188   }
189 }
190
191 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
192 {
193   if( request )
194   {
195     return request->url;
196   }
197
198   return String::EMPTY;
199 }
200
201 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
202 {
203   if( ticket )
204   {
205     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
206                        ticket->GetTypePath().type->id == ResourceNativeImage ||
207                        ticket->GetTypePath().type->id == ResourceTargetImage );
208     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
209     return attrib;
210   }
211   return ImageAttributes::DEFAULT_ATTRIBUTES;
212 }
213
214 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
215 {
216   if( request && request->attributes )
217   {
218     return *(request->attributes);
219   }
220
221   return ImageAttributes::DEFAULT_ATTRIBUTES;
222 }
223
224 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
225 {
226   if( ticket && ticket->GetLoadingState() != ResourceLoading )
227   {
228     // it is loaded so get the size from actual attributes
229     size = GetActualAttributes( ticket ).GetSize();
230   }
231   else
232   {
233     // not loaded so either loading or not yet loaded, ask platform abstraction
234     Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
235     platformAbstraction.GetClosestImageSize( GetRequestPath( request ), GetRequestAttributes( request ), size );
236   }
237 }
238
239 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
240 {
241   ResourceTicketPtr ticketPtr(ticket);
242   mTicketsToRelease.push_back(ticketPtr);
243 }
244
245 void ImageFactory::FlushReleaseQueue()
246 {
247   mTicketsToRelease.clear();
248 }
249
250 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
251                                       const Dali::ImageAttributes& actual ) const
252 {
253   // do not load image resource again if there is a similar resource loaded:
254   // see explanation in image.h of what is deemed compatible
255   return (requested.GetScalingMode() ==  actual.GetScalingMode()) &&
256           (
257             (requested.GetFilterMode() == actual.GetFilterMode()) ||
258             (requested.GetFilterMode() == ImageAttributes::DontCare)
259           ) &&
260           (requested.GetPixelFormat() ==  actual.GetPixelFormat()) &&
261           (requested.GetFieldBorder() ==  actual.GetFieldBorder()) &&
262           (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) &&
263           (requested.IsDistanceField() == actual.IsDistanceField()) &&
264           (fabsf(requested.GetWidth()  -  actual.GetWidth())  <= actual.GetWidth()  * mMaxScale) &&
265           (fabsf(requested.GetHeight() -  actual.GetHeight()) <= actual.GetHeight() * mMaxScale);
266 }
267
268 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
269 {
270   ++mReqIdCurrent;
271   Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
272   mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
273   mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
274   return request;
275 }
276
277 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
278 {
279   // Search for a matching resource
280
281   // check whether the url has been used before
282   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
283
284   // look for exact matches first
285   for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
286   {
287     RequestId cachedReqId = it->second;
288
289     // get cached request
290     RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
291     DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() && "Only requests that are live in mRequestCache should appear in mUrlCache which is an index to speed-up lookups into it.");
292     if( foundRequestIter != mRequestCache.end() )
293     {
294       const Request& cachedRequest = *(foundRequestIter->second);
295       const ImageAttributes* storedAttributes = cachedRequest.attributes;
296
297       // compare attributes: NULL means default attributes
298       if( !attributes )
299       {
300         attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
301       }
302       if( !storedAttributes )
303       {
304         storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
305       }
306
307       if( *attributes != *storedAttributes )
308       {
309         continue;
310       }
311
312       if( filename.compare( cachedRequest.url ) )
313       {
314         // hash collision, filenames don't match
315         continue;
316       }
317
318       // we've found an exact match
319       return foundRequestIter->second;
320     }
321   }
322
323   return NULL;
324 }
325
326 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
327 {
328   ResourceTicketPtr ticket;
329   // check whether the url has been used before
330   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
331
332   bool foundCompatible = false;
333   if( foundRequests.first != mUrlCache.end() )
334   {
335     // check if we have a compatible resource already loaded
336     for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
337     {
338       RequestId cachedReqId = it->second;
339
340       // get cached request
341       RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
342       DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
343       if( foundRequestIter != mRequestCache.end() )
344       {
345         Request& cachedRequest = *(foundRequestIter->second);
346         if( filename.compare( cachedRequest.url ) )
347         {
348           // hash collision, filenames don't match
349           continue;
350         }
351
352         if( !cachedRequest.resourceId )
353         {
354           continue;
355         }
356
357         ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
358         if( !ticket )
359         {
360           cachedRequest.resourceId = 0;
361           continue;
362         }
363
364         DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
365                            ticket->GetTypePath().type->id == ResourceNativeImage ||
366                            ticket->GetTypePath().type->id == ResourceTargetImage );
367
368         // check for compatible ImageAttributes
369         const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
370         if( !attr )
371         {
372           attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
373         }
374
375         // in case both attributes are default or they are matching custom ones
376         if( CompareAttributes( *attr, storedAttributes ) )
377         {
378           // found compatible resource
379           foundCompatible = true;
380           break;
381         }
382       }
383     } // for( it ...
384   } // foundRequests.first
385
386   if( !foundCompatible )
387   {
388     ticket.Reset();
389   }
390
391   return ticket;
392 }
393
394 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
395 {
396   ImageAttributes attributes;
397
398   if( attr )
399   {
400     attributes = *attr;
401   }
402   else
403   {
404     // query image size from file if NULL was provided
405     Vector2 size = Dali::Image::GetImageSize( filename );
406     attributes.SetSize( size.width, size.height );
407   }
408
409   BitmapResourceType resourceType( attributes );
410   ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
411   return ticket;
412 }
413
414 void ImageFactory::RequestDiscarded( const Request& req )
415 {
416   RequestId id( req.GetId() );
417   // find in mRequestCache
418   RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
419   DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
420
421   // memory is freed up by intrusive_ptr
422
423   mRequestCache.erase( foundRequestIter );
424
425   // find in mUrlCache
426   for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
427   {
428     if( id == it->second )
429     {
430       mUrlCache.erase( it );
431       break;
432     }
433   }
434 }
435
436 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
437 {
438   const RequestId requestId = request.GetId();
439   std::size_t locatorHash(0);
440   RequestPathHashMap::const_iterator it;
441
442   for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
443   {
444     if( it->second == requestId )
445     {
446       locatorHash = it->first;
447       break;
448     }
449   }
450   DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
451   return locatorHash;
452 }
453
454 } // namespace Internal
455
456 } // namespace Dali