2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/images/image-factory.h>
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>
37 using namespace Dali::Integration;
38 using namespace Dali::Internal::ImageFactoryCache;
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, ...
53 ImageFactory::~ImageFactory()
55 // Request memory is freed up by intrusive_ptr
57 mRequestCache.clear();
60 Request* ImageFactory::RegisterRequest( const std::string &filename, const ImageAttributes *attr )
63 // check if same request exists
64 std::size_t urlHash = CalculateHash( filename );
66 Request* foundReq( NULL );
67 foundReq = FindRequest( filename, urlHash, attr );
71 // the same request hasn't been made before
72 foundReq = InsertNewRequest( 0, filename, urlHash, attr );
78 ResourceTicketPtr ImageFactory::Load( Request& request )
80 ResourceTicketPtr ticket;
82 // See if any resource transaction has already been associated with this request:
83 const ResourceId resId = request.resourceId;
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.
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 );
98 // Start a new resource IO transaction for the request if none is already happening:
101 ticket = IssueLoadRequest( request.url, request.attributes );
103 request.resourceId = ticket->GetId();
105 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
106 ticket->GetTypePath().type->id == ResourceNativeImage ||
107 ticket->GetTypePath().type->id == ResourceTargetImage );
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.
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 )
121 DALI_ASSERT_ALWAYS( &request );
123 // go through requests, check real size and attributes again. If different, update related ticket.
124 ResourceTicketPtr ticket;
126 if( !request.resourceId )
128 // in case of OnDemand loading, just return
132 ticket = mResourceClient.RequestResourceTicket( request.resourceId );
134 // ticket might have been deleted, eg. Image::Disconnect
137 ticket = IssueLoadRequest( request.url, request.attributes );
138 request.resourceId = ticket->GetId();
140 else // ticket still alive
142 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
143 ticket->GetTypePath().type->id == ResourceNativeImage ||
144 ticket->GetTypePath().type->id == ResourceTargetImage );
146 // do not reload if still loading
147 if ( ticket->GetLoadingState() == ResourceLoading )
152 ImageDimensions closestSize;
153 if( request.attributes )
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() );
161 closestSize = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url );
163 Vector2 size( closestSize.GetX(), closestSize.GetY() );
165 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
167 if( size == attrib.GetSize() )
169 mResourceClient.ReloadResource( ticket->GetId(), false );
173 // if not, return a different ticket
174 ticket = IssueLoadRequest( request.url, request.attributes );
175 request.resourceId = ticket->GetId();
181 void ImageFactory::RecoverFromContextLoss()
183 for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
185 // go through requests, reload with resource ticket's attributes.
186 Request* request = (*it).second;
187 if( request->resourceId )
189 ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
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 )
195 // Ensure the finished status is reset
196 mResourceClient.ReloadResource( ticket->GetId(), true );
201 Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
202 for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
205 (*iter)->RecoverFromContextLoss();
210 void ImageFactory::RegisterForContextRecovery( ContextRecoveryInterface* object )
213 // To avoid registering the same object again
214 Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
215 for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
218 if( object == *(iter) )
226 mContextRecoveryList.PushBack( object );
229 void ImageFactory::UnregisterFromContextRecovery( ContextRecoveryInterface* object )
231 Vector< ContextRecoveryInterface* >::ConstIterator end = mContextRecoveryList.End();
232 for( Vector< ContextRecoveryInterface* >::Iterator iter = mContextRecoveryList.Begin();
233 iter != end; iter++ )
235 if( object == *(iter) )
237 iter = mContextRecoveryList.Erase( iter );
243 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
250 // Only create empty string if required
251 static std::string empty;
255 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
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();
265 return ImageAttributes::DEFAULT_ATTRIBUTES;
268 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
270 if( request && request->attributes )
272 return *(request->attributes);
275 return ImageAttributes::DEFAULT_ATTRIBUTES;
278 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
280 if( ticket && ticket->GetLoadingState() != ResourceLoading )
282 // it is loaded so get the size from actual attributes
283 size = GetActualAttributes( ticket ).GetSize();
287 // not loaded so either loading or not yet loaded, ask platform abstraction
288 Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
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();
299 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
301 ResourceTicketPtr ticketPtr(ticket);
302 mTicketsToRelease.push_back(ticketPtr);
305 void ImageFactory::FlushReleaseQueue()
307 mTicketsToRelease.clear();
310 bool ImageFactory::CompareAttributes( const ImageAttributes& requested,
311 const ImageAttributes& actual ) const
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()) &&
317 (requested.GetFilterMode() == actual.GetFilterMode()) ||
318 (requested.GetFilterMode() == SamplingMode::DONT_CARE)
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);
324 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
327 Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
328 mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
329 mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
333 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
335 // Search for a matching resource
337 // check whether the url has been used before
338 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
340 // look for exact matches first
341 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
343 RequestId cachedReqId = it->second;
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() )
350 const Request& cachedRequest = *(foundRequestIter->second);
351 const ImageAttributes* storedAttributes = cachedRequest.attributes;
353 // compare attributes: NULL means default attributes
356 attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
358 if( !storedAttributes )
360 storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
363 if( *attributes != *storedAttributes )
368 if( filename.compare( cachedRequest.url ) )
370 // hash collision, filenames don't match
374 // we've found an exact match
375 return foundRequestIter->second;
382 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
384 ResourceTicketPtr ticket;
385 // check whether the url has been used before
386 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
388 bool foundCompatible = false;
389 if( foundRequests.first != mUrlCache.end() )
391 // check if we have a compatible resource already loaded
392 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
394 RequestId cachedReqId = it->second;
396 // get cached request
397 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
398 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
399 if( foundRequestIter != mRequestCache.end() )
401 Request& cachedRequest = *(foundRequestIter->second);
402 if( filename.compare( cachedRequest.url ) )
404 // hash collision, filenames don't match
408 if( !cachedRequest.resourceId )
413 ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
416 cachedRequest.resourceId = 0;
420 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
421 ticket->GetTypePath().type->id == ResourceNativeImage ||
422 ticket->GetTypePath().type->id == ResourceTargetImage );
424 // check for compatible ImageAttributes
425 const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
428 attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
431 // in case both attributes are default or they are matching custom ones
432 if( CompareAttributes( *attr, storedAttributes ) )
434 // found compatible resource
435 foundCompatible = true;
440 } // foundRequests.first
442 if( !foundCompatible )
450 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
452 ImageDimensions dimensions;
453 FittingMode::Type fittingMode = FittingMode::DEFAULT;
454 SamplingMode::Type samplingMode = SamplingMode::DEFAULT;
455 bool orientation = true;
459 dimensions = ImageDimensions::FromFloatVec2( attr->GetSize() );
460 fittingMode = attr->GetScalingMode();
461 samplingMode = attr->GetFilterMode();
462 orientation = attr->GetOrientationCorrection();
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.
472 BitmapResourceType resourceType( dimensions, fittingMode, samplingMode, orientation );
473 ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
477 void ImageFactory::RequestDiscarded( const Request& req )
479 RequestId id( req.GetId() );
480 // find in mRequestCache
481 RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
482 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
484 // memory is freed up by intrusive_ptr
486 mRequestCache.erase( foundRequestIter );
489 for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
491 if( id == it->second )
493 mUrlCache.erase( it );
499 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
501 const RequestId requestId = request.GetId();
502 std::size_t locatorHash(0);
503 RequestPathHashMap::const_iterator it;
505 for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
507 if( it->second == requestId )
509 locatorHash = it->first;
513 DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
517 } // namespace Internal