ResourceImage/Image split
[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/public-api/images/resource-image.h>
27 #include <dali/internal/event/common/thread-local-storage.h>
28 #include <dali/internal/event/common/notification-manager.h>
29 #include <dali/internal/common/event-to-update.h>
30 #include <dali/internal/event/resources/resource-client.h>
31 #include <dali/internal/update/resources/resource-manager.h>
32 #include <dali/internal/common/dali-hash.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     Vector2 size;
153     Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url, *request.attributes, size );
154
155     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
156
157     if( size == attrib.GetSize() )
158     {
159       mResourceClient.ReloadResource( ticket->GetId(), false );
160     }
161     else
162     {
163       // if not, return a different ticket
164       ticket = IssueLoadRequest( request.url, request.attributes );
165       request.resourceId = ticket->GetId();
166     }
167   }
168   return ticket;
169 }
170
171 void ImageFactory::RecoverFromContextLoss()
172 {
173   for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
174   {
175     // go through requests, reload with resource ticket's attributes.
176     Request* request = (*it).second;
177     if( request->resourceId )
178     {
179       ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
180
181       // do not reload if still loading
182       // check ticket is not NULL as the resource could have already been destroyed
183       if ( ticket && ticket->GetLoadingState() != ResourceLoading )
184       {
185         // Ensure the finished status is reset
186         mResourceClient.ReloadResource( ticket->GetId(), true );
187       }
188     }
189   }
190 }
191
192 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
193 {
194   if( request )
195   {
196     return request->url;
197   }
198
199   return String::EMPTY;
200 }
201
202 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
203 {
204   if( ticket )
205   {
206     DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
207                        ticket->GetTypePath().type->id == ResourceNativeImage ||
208                        ticket->GetTypePath().type->id == ResourceTargetImage );
209     const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
210     return attrib;
211   }
212   return ImageAttributes::DEFAULT_ATTRIBUTES;
213 }
214
215 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
216 {
217   if( request && request->attributes )
218   {
219     return *(request->attributes);
220   }
221
222   return ImageAttributes::DEFAULT_ATTRIBUTES;
223 }
224
225 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
226 {
227   if( ticket && ticket->GetLoadingState() != ResourceLoading )
228   {
229     // it is loaded so get the size from actual attributes
230     size = GetActualAttributes( ticket ).GetSize();
231   }
232   else
233   {
234     // not loaded so either loading or not yet loaded, ask platform abstraction
235     Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
236     platformAbstraction.GetClosestImageSize( GetRequestPath( request ), GetRequestAttributes( request ), size );
237   }
238 }
239
240 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
241 {
242   ResourceTicketPtr ticketPtr(ticket);
243   mTicketsToRelease.push_back(ticketPtr);
244 }
245
246 void ImageFactory::FlushReleaseQueue()
247 {
248   mTicketsToRelease.clear();
249 }
250
251 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
252                                       const Dali::ImageAttributes& actual ) const
253 {
254   // do not load image resource again if there is a similar resource loaded:
255   // see explanation in image.h of what is deemed compatible
256   return (requested.GetScalingMode() ==  actual.GetScalingMode()) &&
257           (
258             (requested.GetFilterMode() == actual.GetFilterMode()) ||
259             (requested.GetFilterMode() == ImageAttributes::DontCare)
260           ) &&
261           (requested.GetPixelFormat() ==  actual.GetPixelFormat()) &&
262           (requested.GetFieldBorder() ==  actual.GetFieldBorder()) &&
263           (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) &&
264           (requested.IsDistanceField() == actual.IsDistanceField()) &&
265           (fabsf(requested.GetWidth()  -  actual.GetWidth())  <= actual.GetWidth()  * mMaxScale) &&
266           (fabsf(requested.GetHeight() -  actual.GetHeight()) <= actual.GetHeight() * mMaxScale);
267 }
268
269 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
270 {
271   ++mReqIdCurrent;
272   Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
273   mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
274   mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
275   return request;
276 }
277
278 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
279 {
280   // Search for a matching resource
281
282   // check whether the url has been used before
283   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
284
285   // look for exact matches first
286   for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
287   {
288     RequestId cachedReqId = it->second;
289
290     // get cached request
291     RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
292     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.");
293     if( foundRequestIter != mRequestCache.end() )
294     {
295       const Request& cachedRequest = *(foundRequestIter->second);
296       const ImageAttributes* storedAttributes = cachedRequest.attributes;
297
298       // compare attributes: NULL means default attributes
299       if( !attributes )
300       {
301         attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
302       }
303       if( !storedAttributes )
304       {
305         storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
306       }
307
308       if( *attributes != *storedAttributes )
309       {
310         continue;
311       }
312
313       if( filename.compare( cachedRequest.url ) )
314       {
315         // hash collision, filenames don't match
316         continue;
317       }
318
319       // we've found an exact match
320       return foundRequestIter->second;
321     }
322   }
323
324   return NULL;
325 }
326
327 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
328 {
329   ResourceTicketPtr ticket;
330   // check whether the url has been used before
331   RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
332
333   bool foundCompatible = false;
334   if( foundRequests.first != mUrlCache.end() )
335   {
336     // check if we have a compatible resource already loaded
337     for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
338     {
339       RequestId cachedReqId = it->second;
340
341       // get cached request
342       RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
343       DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
344       if( foundRequestIter != mRequestCache.end() )
345       {
346         Request& cachedRequest = *(foundRequestIter->second);
347         if( filename.compare( cachedRequest.url ) )
348         {
349           // hash collision, filenames don't match
350           continue;
351         }
352
353         if( !cachedRequest.resourceId )
354         {
355           continue;
356         }
357
358         ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
359         if( !ticket )
360         {
361           cachedRequest.resourceId = 0;
362           continue;
363         }
364
365         DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap      ||
366                            ticket->GetTypePath().type->id == ResourceNativeImage ||
367                            ticket->GetTypePath().type->id == ResourceTargetImage );
368
369         // check for compatible ImageAttributes
370         const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
371         if( !attr )
372         {
373           attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
374         }
375
376         // in case both attributes are default or they are matching custom ones
377         if( CompareAttributes( *attr, storedAttributes ) )
378         {
379           // found compatible resource
380           foundCompatible = true;
381           break;
382         }
383       }
384     } // for( it ...
385   } // foundRequests.first
386
387   if( !foundCompatible )
388   {
389     ticket.Reset();
390   }
391
392   return ticket;
393 }
394
395 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
396 {
397   ImageAttributes attributes;
398
399   if( attr )
400   {
401     attributes = *attr;
402   }
403   else
404   {
405     // query image size from file if NULL was provided
406     Vector2 size = Dali::ResourceImage::GetImageSize( filename );
407     attributes.SetSize( size.width, size.height );
408   }
409
410   BitmapResourceType resourceType( attributes );
411   ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
412   return ticket;
413 }
414
415 void ImageFactory::RequestDiscarded( const Request& req )
416 {
417   RequestId id( req.GetId() );
418   // find in mRequestCache
419   RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
420   DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
421
422   // memory is freed up by intrusive_ptr
423
424   mRequestCache.erase( foundRequestIter );
425
426   // find in mUrlCache
427   for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
428   {
429     if( id == it->second )
430     {
431       mUrlCache.erase( it );
432       break;
433     }
434   }
435 }
436
437 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
438 {
439   const RequestId requestId = request.GetId();
440   std::size_t locatorHash(0);
441   RequestPathHashMap::const_iterator it;
442
443   for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
444   {
445     if( it->second == requestId )
446     {
447       locatorHash = it->first;
448       break;
449     }
450   }
451   DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
452   return locatorHash;
453 }
454
455 } // namespace Internal
456
457 } // namespace Dali