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/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>
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 )
153 Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url, *request.attributes, size );
155 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
157 if( size == attrib.GetSize() )
159 mResourceClient.ReloadResource( ticket->GetId(), false );
163 // if not, return a different ticket
164 ticket = IssueLoadRequest( request.url, request.attributes );
165 request.resourceId = ticket->GetId();
171 void ImageFactory::RecoverFromContextLoss()
173 for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
175 // go through requests, reload with resource ticket's attributes.
176 Request* request = (*it).second;
177 if( request->resourceId )
179 ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
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 )
185 // Ensure the finished status is reset
186 mResourceClient.ReloadResource( ticket->GetId(), true );
192 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
199 return String::EMPTY;
202 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
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();
212 return ImageAttributes::DEFAULT_ATTRIBUTES;
215 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
217 if( request && request->attributes )
219 return *(request->attributes);
222 return ImageAttributes::DEFAULT_ATTRIBUTES;
225 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
227 if( ticket && ticket->GetLoadingState() != ResourceLoading )
229 // it is loaded so get the size from actual attributes
230 size = GetActualAttributes( ticket ).GetSize();
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 );
240 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
242 ResourceTicketPtr ticketPtr(ticket);
243 mTicketsToRelease.push_back(ticketPtr);
246 void ImageFactory::FlushReleaseQueue()
248 mTicketsToRelease.clear();
251 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
252 const Dali::ImageAttributes& actual ) const
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()) &&
258 (requested.GetFilterMode() == actual.GetFilterMode()) ||
259 (requested.GetFilterMode() == ImageAttributes::DontCare)
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);
269 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
272 Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
273 mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
274 mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
278 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
280 // Search for a matching resource
282 // check whether the url has been used before
283 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
285 // look for exact matches first
286 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
288 RequestId cachedReqId = it->second;
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() )
295 const Request& cachedRequest = *(foundRequestIter->second);
296 const ImageAttributes* storedAttributes = cachedRequest.attributes;
298 // compare attributes: NULL means default attributes
301 attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
303 if( !storedAttributes )
305 storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
308 if( *attributes != *storedAttributes )
313 if( filename.compare( cachedRequest.url ) )
315 // hash collision, filenames don't match
319 // we've found an exact match
320 return foundRequestIter->second;
327 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
329 ResourceTicketPtr ticket;
330 // check whether the url has been used before
331 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
333 bool foundCompatible = false;
334 if( foundRequests.first != mUrlCache.end() )
336 // check if we have a compatible resource already loaded
337 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
339 RequestId cachedReqId = it->second;
341 // get cached request
342 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
343 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
344 if( foundRequestIter != mRequestCache.end() )
346 Request& cachedRequest = *(foundRequestIter->second);
347 if( filename.compare( cachedRequest.url ) )
349 // hash collision, filenames don't match
353 if( !cachedRequest.resourceId )
358 ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
361 cachedRequest.resourceId = 0;
365 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
366 ticket->GetTypePath().type->id == ResourceNativeImage ||
367 ticket->GetTypePath().type->id == ResourceTargetImage );
369 // check for compatible ImageAttributes
370 const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
373 attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
376 // in case both attributes are default or they are matching custom ones
377 if( CompareAttributes( *attr, storedAttributes ) )
379 // found compatible resource
380 foundCompatible = true;
385 } // foundRequests.first
387 if( !foundCompatible )
395 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
397 ImageAttributes attributes;
405 // query image size from file if NULL was provided
406 Vector2 size = Dali::ResourceImage::GetImageSize( filename );
407 attributes.SetSize( size.width, size.height );
410 BitmapResourceType resourceType( attributes );
411 ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
415 void ImageFactory::RequestDiscarded( const Request& req )
417 RequestId id( req.GetId() );
418 // find in mRequestCache
419 RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
420 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
422 // memory is freed up by intrusive_ptr
424 mRequestCache.erase( foundRequestIter );
427 for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
429 if( id == it->second )
431 mUrlCache.erase( it );
437 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
439 const RequestId requestId = request.GetId();
440 std::size_t locatorHash(0);
441 RequestPathHashMap::const_iterator it;
443 for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
445 if( it->second == requestId )
447 locatorHash = it->first;
451 DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
455 } // namespace Internal