(GCC 6.2) Remove unused functions from automated tests
[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   // go through requests, check real size and attributes again. If different, update related ticket.
122   ResourceTicketPtr ticket;
123
124   if( !request.resourceId )
125   {
126     // in case of OnDemand loading, just return
127     return NULL;
128   }
129
130   ticket = mResourceClient.RequestResourceTicket( request.resourceId );
131
132   // ticket might have been deleted, eg. Image::Disconnect
133   if( !ticket )
134   {
135     ticket = IssueLoadRequest( request.url, request.attributes );
136     request.resourceId = ticket->GetId();
137   }
138   else // ticket still alive
139   {
140     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
141                        ticket->GetTypePath().type->id == ResourceNativeImage ||
142                        ticket->GetTypePath().type->id == ResourceTargetImage );
143
144     // do not reload if still loading
145     if ( ticket->GetLoadingState() == ResourceLoading )
146     {
147       return ticket;
148     }
149
150     ImageDimensions closestSize;
151     if( request.attributes )
152     {
153       closestSize = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().
154           GetClosestImageSize( request.url, ImageDimensions( request.attributes->GetSize().width, request.attributes->GetSize().width ),
155                                request.attributes->GetScalingMode(), request.attributes->GetFilterMode(), request.attributes->GetOrientationCorrection() );
156     }
157     else
158     {
159       closestSize = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url );
160     }
161     Vector2 size( closestSize.GetX(), closestSize.GetY() );
162
163     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
164
165     if( size == attrib.GetSize() )
166     {
167       mResourceClient.ReloadResource( ticket->GetId(), false );
168     }
169     else
170     {
171       // if not, return a different ticket
172       ticket = IssueLoadRequest( request.url, request.attributes );
173       request.resourceId = ticket->GetId();
174     }
175   }
176   return ticket;
177 }
178
179 void ImageFactory::RecoverFromContextLoss()
180 {
181   for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
182   {
183     // go through requests, reload with resource ticket's attributes.
184     Request* request = (*it).second;
185     if( request->resourceId )
186     {
187       ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
188
189       // do not reload if still loading
190       // check ticket is not NULL as the resource could have already been destroyed
191       if ( ticket && ticket->GetLoadingState() != ResourceLoading )
192       {
193         // Ensure the finished status is reset
194         mResourceClient.ReloadResource( ticket->GetId(), true );
195       }
196     }
197   }
198
199   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
200   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
201       iter != end; iter++)
202   {
203     (*iter)->RecoverFromContextLoss();
204   }
205 }
206
207
208 void ImageFactory::RegisterForContextRecovery( ContextRecoveryInterface* object  )
209 {
210   bool exist( false );
211   // To avoid registering the same object again
212   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
213   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
214           iter != end; iter++)
215   {
216     if( object == *(iter) )
217     {
218       exist = true;
219       break;
220     }
221   }
222   if( !exist )
223   {
224     mContextRecoveryList.PushBack( object );
225   }
226 }
227 void ImageFactory::UnregisterFromContextRecovery( ContextRecoveryInterface* object  )
228 {
229   Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
230   for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
231         iter != end; iter++ )
232   {
233     if( object == *(iter) )
234     {
235       iter = mContextRecoveryList.Erase( iter );
236       break;
237     }
238   }
239 }
240
241 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
242 {
243   if( request )
244   {
245     return request->url;
246   }
247
248   // Only create empty string if required
249   static std::string empty;
250   return empty;
251 }
252
253 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
254 {
255   if( ticket )
256   {
257     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
258                        ticket->GetTypePath().type->id == ResourceNativeImage ||
259                        ticket->GetTypePath().type->id == ResourceTargetImage );
260     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
261     return attrib;
262   }
263   return ImageAttributes::DEFAULT_ATTRIBUTES;
264 }
265
266 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
267 {
268   if( request && request->attributes )
269   {
270     return *(request->attributes);
271   }
272
273   return ImageAttributes::DEFAULT_ATTRIBUTES;
274 }
275
276 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
277 {
278   if( ticket && ticket->GetLoadingState() != ResourceLoading )
279   {
280     // it is loaded so get the size from actual attributes
281     size = GetActualAttributes( ticket ).GetSize();
282   }
283   else
284   {
285     // not loaded so either loading or not yet loaded, ask platform abstraction
286     Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
287
288     const ImageAttributes& attributes = GetRequestAttributes( request );
289     const ImageDimensions closestSize = platformAbstraction.GetClosestImageSize( GetRequestPath( request ),
290                                                                                    ImageDimensions( attributes.GetSize().width, attributes.GetSize().width ),
291                                                                                    attributes.GetScalingMode(), attributes.GetFilterMode(), attributes.GetOrientationCorrection() );
292     size[0] = closestSize.GetX();
293     size[1] = closestSize.GetY();
294   }
295 }
296
297 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
298 {
299   ResourceTicketPtr ticketPtr(ticket);
300   mTicketsToRelease.push_back(ticketPtr);
301 }
302
303 void ImageFactory::FlushReleaseQueue()
304 {
305   mTicketsToRelease.clear();
306 }
307
308 bool ImageFactory::CompareAttributes( const ImageAttributes& requested,
309                                       const ImageAttributes& actual ) const
310 {
311   // do not load image resource again if there is a similar resource loaded:
312   // see explanation in image.h of what is deemed compatible
313   return (requested.GetScalingMode() ==  actual.GetScalingMode()) &&
314     (
315       (requested.GetFilterMode() == actual.GetFilterMode()) ||
316       (requested.GetFilterMode() == SamplingMode::DONT_CARE)
317       ) &&
318     (fabsf(static_cast<float>(requested.GetWidth())  -  static_cast<float>(actual.GetWidth()))  <= actual.GetWidth()  * mMaxScale) &&
319     (fabsf(static_cast<float>(requested.GetHeight()) -  static_cast<float>(actual.GetHeight())) <= actual.GetHeight() * mMaxScale);
320 }
321
322 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
323 {
324   ++mReqIdCurrent;
325   Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
326   mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
327   mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
328   return request;
329 }
330
331 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
332 {
333   // Search for a matching resource
334
335   // check whether the url has been used before
336   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
337
338   // look for exact matches first
339   for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
340   {
341     RequestId cachedReqId = it->second;
342
343     // get cached request
344     RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
345     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.");
346     if( foundRequestIter != mRequestCache.end() )
347     {
348       const Request& cachedRequest = *(foundRequestIter->second);
349       const ImageAttributes* storedAttributes = cachedRequest.attributes;
350
351       // compare attributes: NULL means default attributes
352       if( !attributes )
353       {
354         attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
355       }
356       if( !storedAttributes )
357       {
358         storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
359       }
360
361       if( *attributes != *storedAttributes )
362       {
363         continue;
364       }
365
366       if( filename.compare( cachedRequest.url ) )
367       {
368         // hash collision, filenames don't match
369         continue;
370       }
371
372       // we've found an exact match
373       return foundRequestIter->second;
374     }
375   }
376
377   return NULL;
378 }
379
380 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
381 {
382   ResourceTicketPtr ticket;
383   // check whether the url has been used before
384   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
385
386   bool foundCompatible = false;
387   if( foundRequests.first != mUrlCache.end() )
388   {
389     // check if we have a compatible resource already loaded
390     for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
391     {
392       RequestId cachedReqId = it->second;
393
394       // get cached request
395       RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
396       DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
397       if( foundRequestIter != mRequestCache.end() )
398       {
399         Request& cachedRequest = *(foundRequestIter->second);
400         if( filename.compare( cachedRequest.url ) )
401         {
402           // hash collision, filenames don't match
403           continue;
404         }
405
406         if( !cachedRequest.resourceId )
407         {
408           continue;
409         }
410
411         ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
412         if( !ticket )
413         {
414           cachedRequest.resourceId = 0;
415           continue;
416         }
417
418         DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
419                            ticket->GetTypePath().type->id == ResourceNativeImage ||
420                            ticket->GetTypePath().type->id == ResourceTargetImage );
421
422         // check for compatible ImageAttributes
423         const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
424         if( !attr )
425         {
426           attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
427         }
428
429         // in case both attributes are default or they are matching custom ones
430         if( CompareAttributes( *attr, storedAttributes ) )
431         {
432           // found compatible resource
433           foundCompatible = true;
434           break;
435         }
436       }
437     } // for( it ...
438   } // foundRequests.first
439
440   if( !foundCompatible )
441   {
442     ticket.Reset();
443   }
444
445   return ticket;
446 }
447
448 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
449 {
450   ImageDimensions dimensions;
451   FittingMode::Type fittingMode = FittingMode::DEFAULT;
452   SamplingMode::Type samplingMode = SamplingMode::DEFAULT;
453   bool orientation = true;
454
455   if( attr )
456   {
457     dimensions = ImageDimensions::FromFloatVec2( attr->GetSize() );
458     fittingMode = attr->GetScalingMode();
459     samplingMode = attr->GetFilterMode();
460     orientation = attr->GetOrientationCorrection();
461   }
462   else
463   {
464     // query image size from file if NULL was provided
465     dimensions = Dali::ResourceImage::GetImageSize( filename );
466     ///@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,
467     ///       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.
468   }
469
470   BitmapResourceType resourceType( dimensions, fittingMode, samplingMode, orientation );
471   ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
472   return ticket;
473 }
474
475 void ImageFactory::RequestDiscarded( const Request& req )
476 {
477   RequestId id( req.GetId() );
478   // find in mRequestCache
479   RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
480   DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
481
482   // memory is freed up by intrusive_ptr
483
484   mRequestCache.erase( foundRequestIter );
485
486   // find in mUrlCache
487   for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
488   {
489     if( id == it->second )
490     {
491       mUrlCache.erase( it );
492       break;
493     }
494   }
495 }
496
497 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
498 {
499   const RequestId requestId = request.GetId();
500   std::size_t locatorHash(0);
501   RequestPathHashMap::const_iterator it;
502
503   for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
504   {
505     if( it->second == requestId )
506     {
507       locatorHash = it->first;
508       break;
509     }
510   }
511   DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
512   return locatorHash;
513 }
514
515 } // namespace Internal
516
517 } // namespace Dali