999ba25af9d8e27181113a5ec3eaceac18a781d2
[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/devel-api/common/hash.h>
27 #include <dali/public-api/images/resource-image.h>
28 #include <dali/internal/event/common/thread-local-storage.h>
29 #include <dali/internal/event/common/notification-manager.h>
30 #include <dali/internal/event/resources/resource-client.h>
31 #include <dali/internal/update/resources/resource-manager.h>
32 #include <dali/internal/common/image-attributes.h>
33
34 // EXTERNAL INCLUDES
35 #include <float.h>
36
37 using namespace Dali::Integration;
38 using namespace Dali::Internal::ImageFactoryCache;
39
40 namespace Dali
41 {
42
43 namespace Internal
44 {
45
46 ImageFactory::ImageFactory( ResourceClient& resourceClient )
47 : mResourceClient(resourceClient),
48   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, ...
49   mReqIdCurrent(0)
50 {
51 }
52
53 ImageFactory::~ImageFactory()
54 {
55   // Request memory is freed up by intrusive_ptr
56
57   mRequestCache.clear();
58 }
59
60 Request* ImageFactory::RegisterRequest( const std::string &filename, const ImageAttributes *attr )
61 {
62   // check url cache
63   // check if same request exists
64   std::size_t urlHash = CalculateHash( filename );
65
66   Request* foundReq( NULL );
67   foundReq = FindRequest( filename, urlHash, attr );
68
69   if( !foundReq )
70   {
71     // the same request hasn't been made before
72     foundReq = InsertNewRequest( 0, filename, urlHash, attr );
73   }
74
75   return foundReq;
76 }
77
78 ResourceTicketPtr ImageFactory::Load( Request& request )
79 {
80   ResourceTicketPtr ticket;
81
82   // See if any resource transaction has already been associated with this request:
83   const ResourceId resId = request.resourceId;
84   if( resId != 0 )
85   {
86     // An IO operation has been started at some time for the request so recover the
87     // ticket that was created for that:
88     ticket = mResourceClient.RequestResourceTicket( resId );  ///@note Always succeeds in normal use.
89   }
90   else
91   {
92     // Request not yet associated with a ticketed async resource transaction, so
93     // attempt to find a compatible cached one:
94     const std::size_t urlHash = GetHashForCachedRequest( request );
95     ticket = FindCompatibleResource( request.url, urlHash, request.attributes );
96   }
97
98   // Start a new resource IO transaction for the request if none is already happening:
99   if( !ticket )
100   {
101     ticket = IssueLoadRequest( request.url, request.attributes );
102   }
103   request.resourceId = ticket->GetId();
104
105   DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
106                      ticket->GetTypePath().type->id == ResourceNativeImage ||
107                      ticket->GetTypePath().type->id == ResourceTargetImage );
108   return ticket;
109 }
110
111 // File can change on fs, but still requesting same attributes.
112 // Returning the ticket is important, because if two different requests mapped to the same resource
113 // before, it is not guaranteed that they will still map to the same resource after reloading.
114 // Example:
115 // Image size (40, 40), Req1(img, 40, 40), Req2(img, 256, 256)
116 // In this case both requests will be associated with the resource of size (40, 40)
117 // If image changes on filesystem to size (96, 96) -> now after reloading Req2 would load a
118 // new resource of size (96, 96), but reloading Req1 would load a scaled down version
119 ResourceTicketPtr ImageFactory::Reload( Request& request )
120 {
121   DALI_ASSERT_ALWAYS( &request );
122
123   // go through requests, check real size and attributes again. If different, update related ticket.
124   ResourceTicketPtr ticket;
125
126   if( !request.resourceId )
127   {
128     // in case of OnDemand loading, just return
129     return NULL;
130   }
131
132   ticket = mResourceClient.RequestResourceTicket( request.resourceId );
133
134   // ticket might have been deleted, eg. Image::Disconnect
135   if( !ticket )
136   {
137     ticket = IssueLoadRequest( request.url, request.attributes );
138     request.resourceId = ticket->GetId();
139   }
140   else // ticket still alive
141   {
142     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
143                        ticket->GetTypePath().type->id == ResourceNativeImage ||
144                        ticket->GetTypePath().type->id == ResourceTargetImage );
145
146     // do not reload if still loading
147     if ( ticket->GetLoadingState() == ResourceLoading )
148     {
149       return ticket;
150     }
151
152     ImageDimensions closestSize;
153     if( request.attributes )
154     {
155       closestSize = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().
156           GetClosestImageSize( request.url, ImageDimensions( request.attributes->GetSize().width, request.attributes->GetSize().width ),
157                                request.attributes->GetScalingMode(), request.attributes->GetFilterMode(), request.attributes->GetOrientationCorrection() );
158     }
159     else
160     {
161       closestSize = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url );
162     }
163     Vector2 size( closestSize.GetX(), closestSize.GetY() );
164
165     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
166
167     if( size == attrib.GetSize() )
168     {
169       mResourceClient.ReloadResource( ticket->GetId(), false );
170     }
171     else
172     {
173       // if not, return a different ticket
174       ticket = IssueLoadRequest( request.url, request.attributes );
175       request.resourceId = ticket->GetId();
176     }
177   }
178   return ticket;
179 }
180
181 void ImageFactory::RecoverFromContextLoss()
182 {
183   for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
184   {
185     // go through requests, reload with resource ticket's attributes.
186     Request* request = (*it).second;
187     if( request->resourceId )
188     {
189       ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
190
191       // do not reload if still loading
192       // check ticket is not NULL as the resource could have already been destroyed
193       if ( ticket && ticket->GetLoadingState() != ResourceLoading )
194       {
195         // Ensure the finished status is reset
196         mResourceClient.ReloadResource( ticket->GetId(), true );
197       }
198     }
199   }
200
201   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
202   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
203       iter != end; iter++)
204   {
205     (*iter)->RecoverFromContextLoss();
206   }
207 }
208
209
210 void ImageFactory::RegisterForContextRecovery( ContextRecoveryInterface* object  )
211 {
212   bool exist( false );
213   // To avoid registering the same object again
214   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
215   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
216           iter != end; iter++)
217   {
218     if( object == *(iter) )
219     {
220       exist = true;
221       break;
222     }
223   }
224   if( !exist )
225   {
226     mContextRecoveryList.PushBack( object );
227   }
228 }
229 void ImageFactory::UnregisterFromContextRecovery( ContextRecoveryInterface* object  )
230 {
231   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
232   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
233         iter != end; iter++ )
234   {
235     if( object == *(iter) )
236     {
237       iter = mContextRecoveryList.Erase( iter );
238       break;
239     }
240   }
241 }
242
243 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
244 {
245   if( request )
246   {
247     return request->url;
248   }
249
250   // Only create empty string if required
251   static std::string empty;
252   return empty;
253 }
254
255 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
256 {
257   if( ticket )
258   {
259     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
260                        ticket->GetTypePath().type->id == ResourceNativeImage ||
261                        ticket->GetTypePath().type->id == ResourceTargetImage );
262     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
263     return attrib;
264   }
265   return ImageAttributes::DEFAULT_ATTRIBUTES;
266 }
267
268 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
269 {
270   if( request && request->attributes )
271   {
272     return *(request->attributes);
273   }
274
275   return ImageAttributes::DEFAULT_ATTRIBUTES;
276 }
277
278 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
279 {
280   if( ticket && ticket->GetLoadingState() != ResourceLoading )
281   {
282     // it is loaded so get the size from actual attributes
283     size = GetActualAttributes( ticket ).GetSize();
284   }
285   else
286   {
287     // not loaded so either loading or not yet loaded, ask platform abstraction
288     Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
289
290     const ImageAttributes& attributes = GetRequestAttributes( request );
291     const ImageDimensions closestSize = platformAbstraction.GetClosestImageSize( GetRequestPath( request ),
292                                                                                    ImageDimensions( attributes.GetSize().width, attributes.GetSize().width ),
293                                                                                    attributes.GetScalingMode(), attributes.GetFilterMode(), attributes.GetOrientationCorrection() );
294     size[0] = closestSize.GetX();
295     size[1] = closestSize.GetY();
296   }
297 }
298
299 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
300 {
301   ResourceTicketPtr ticketPtr(ticket);
302   mTicketsToRelease.push_back(ticketPtr);
303 }
304
305 void ImageFactory::FlushReleaseQueue()
306 {
307   mTicketsToRelease.clear();
308 }
309
310 bool ImageFactory::CompareAttributes( const ImageAttributes& requested,
311                                       const ImageAttributes& actual ) const
312 {
313   // do not load image resource again if there is a similar resource loaded:
314   // see explanation in image.h of what is deemed compatible
315   return (requested.GetScalingMode() ==  actual.GetScalingMode()) &&
316     (
317       (requested.GetFilterMode() == actual.GetFilterMode()) ||
318       (requested.GetFilterMode() == SamplingMode::DONT_CARE)
319       ) &&
320     (fabsf(static_cast<float>(requested.GetWidth())  -  static_cast<float>(actual.GetWidth()))  <= actual.GetWidth()  * mMaxScale) &&
321     (fabsf(static_cast<float>(requested.GetHeight()) -  static_cast<float>(actual.GetHeight())) <= actual.GetHeight() * mMaxScale);
322 }
323
324 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
325 {
326   ++mReqIdCurrent;
327   Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
328   mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
329   mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
330   return request;
331 }
332
333 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
334 {
335   // Search for a matching resource
336
337   // check whether the url has been used before
338   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
339
340   // look for exact matches first
341   for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
342   {
343     RequestId cachedReqId = it->second;
344
345     // get cached request
346     RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
347     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.");
348     if( foundRequestIter != mRequestCache.end() )
349     {
350       const Request& cachedRequest = *(foundRequestIter->second);
351       const ImageAttributes* storedAttributes = cachedRequest.attributes;
352
353       // compare attributes: NULL means default attributes
354       if( !attributes )
355       {
356         attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
357       }
358       if( !storedAttributes )
359       {
360         storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
361       }
362
363       if( *attributes != *storedAttributes )
364       {
365         continue;
366       }
367
368       if( filename.compare( cachedRequest.url ) )
369       {
370         // hash collision, filenames don't match
371         continue;
372       }
373
374       // we've found an exact match
375       return foundRequestIter->second;
376     }
377   }
378
379   return NULL;
380 }
381
382 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
383 {
384   ResourceTicketPtr ticket;
385   // check whether the url has been used before
386   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
387
388   bool foundCompatible = false;
389   if( foundRequests.first != mUrlCache.end() )
390   {
391     // check if we have a compatible resource already loaded
392     for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
393     {
394       RequestId cachedReqId = it->second;
395
396       // get cached request
397       RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
398       DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
399       if( foundRequestIter != mRequestCache.end() )
400       {
401         Request& cachedRequest = *(foundRequestIter->second);
402         if( filename.compare( cachedRequest.url ) )
403         {
404           // hash collision, filenames don't match
405           continue;
406         }
407
408         if( !cachedRequest.resourceId )
409         {
410           continue;
411         }
412
413         ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
414         if( !ticket )
415         {
416           cachedRequest.resourceId = 0;
417           continue;
418         }
419
420         DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
421                            ticket->GetTypePath().type->id == ResourceNativeImage ||
422                            ticket->GetTypePath().type->id == ResourceTargetImage );
423
424         // check for compatible ImageAttributes
425         const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
426         if( !attr )
427         {
428           attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
429         }
430
431         // in case both attributes are default or they are matching custom ones
432         if( CompareAttributes( *attr, storedAttributes ) )
433         {
434           // found compatible resource
435           foundCompatible = true;
436           break;
437         }
438       }
439     } // for( it ...
440   } // foundRequests.first
441
442   if( !foundCompatible )
443   {
444     ticket.Reset();
445   }
446
447   return ticket;
448 }
449
450 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
451 {
452   ImageDimensions dimensions;
453   FittingMode::Type fittingMode = FittingMode::DEFAULT;
454   SamplingMode::Type samplingMode = SamplingMode::DEFAULT;
455   bool orientation = true;
456
457   if( attr )
458   {
459     dimensions = ImageDimensions::FromFloatVec2( attr->GetSize() );
460     fittingMode = attr->GetScalingMode();
461     samplingMode = attr->GetFilterMode();
462     orientation = attr->GetOrientationCorrection();
463   }
464   else
465   {
466     // query image size from file if NULL was provided
467     dimensions = Dali::ResourceImage::GetImageSize( filename );
468     ///@ToDo: This is weird and pointless: we introduce a synchronous load of the image header on the event thread here to learn the image's on-disk dimensions to pass on to the resource system,
469     ///       but the default behaviour of the resource system when no dimensions are provided is to use exactly these on-disk dimensions when it eventually does the full load and decode.
470   }
471
472   BitmapResourceType resourceType( dimensions, fittingMode, samplingMode, orientation );
473   ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
474   return ticket;
475 }
476
477 void ImageFactory::RequestDiscarded( const Request& req )
478 {
479   RequestId id( req.GetId() );
480   // find in mRequestCache
481   RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
482   DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
483
484   // memory is freed up by intrusive_ptr
485
486   mRequestCache.erase( foundRequestIter );
487
488   // find in mUrlCache
489   for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
490   {
491     if( id == it->second )
492     {
493       mUrlCache.erase( it );
494       break;
495     }
496   }
497 }
498
499 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
500 {
501   const RequestId requestId = request.GetId();
502   std::size_t locatorHash(0);
503   RequestPathHashMap::const_iterator it;
504
505   for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
506   {
507     if( it->second == requestId )
508     {
509       locatorHash = it->first;
510       break;
511     }
512   }
513   DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
514   return locatorHash;
515 }
516
517 } // namespace Internal
518
519 } // namespace Dali