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/internal/event/common/thread-local-storage.h>
27 #include <dali/internal/event/common/notification-manager.h>
28 #include <dali/internal/common/event-to-update.h>
29 #include <dali/internal/event/resources/resource-client.h>
30 #include <dali/internal/update/resources/resource-manager.h>
31 #include <dali/internal/common/dali-hash.h>
36 using namespace Dali::Integration;
37 using namespace Dali::Internal::ImageFactoryCache;
45 ImageFactory::ImageFactory( ResourceClient& resourceClient )
46 : mResourceClient(resourceClient),
47 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, ...
52 ImageFactory::~ImageFactory()
54 // Request memory is freed up by intrusive_ptr
56 mRequestCache.clear();
59 Request* ImageFactory::RegisterRequest( const std::string &filename, const ImageAttributes *attr )
62 // check if same request exists
63 std::size_t urlHash = CalculateHash( filename );
65 Request* foundReq( NULL );
66 foundReq = FindRequest( filename, urlHash, attr );
70 // the same request hasn't been made before
71 foundReq = InsertNewRequest( 0, filename, urlHash, attr );
77 ResourceTicketPtr ImageFactory::Load( Request& request )
79 ResourceTicketPtr ticket;
81 // See if any resource transaction has already been associated with this request:
82 const ResourceId resId = request.resourceId;
85 // An IO operation has been started at some time for the request so recover the
86 // ticket that was created for that:
87 ticket = mResourceClient.RequestResourceTicket( resId ); ///@note Always succeeds in normal use.
91 // Request not yet associated with a ticketed async resource transaction, so
92 // attempt to find a compatible cached one:
93 const std::size_t urlHash = GetHashForCachedRequest( request );
94 ticket = FindCompatibleResource( request.url, urlHash, request.attributes );
97 // Start a new resource IO transaction for the request if none is already happening:
100 ticket = IssueLoadRequest( request.url, request.attributes );
102 request.resourceId = ticket->GetId();
104 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
105 ticket->GetTypePath().type->id == ResourceNativeImage ||
106 ticket->GetTypePath().type->id == ResourceTargetImage );
110 // File can change on fs, but still requesting same attributes.
111 // Returning the ticket is important, because if two different requests mapped to the same resource
112 // before, it is not guaranteed that they will still map to the same resource after reloading.
114 // Image size (40, 40), Req1(img, 40, 40), Req2(img, 256, 256)
115 // In this case both requests will be associated with the resource of size (40, 40)
116 // If image changes on filesystem to size (96, 96) -> now after reloading Req2 would load a
117 // new resource of size (96, 96), but reloading Req1 would load a scaled down version
118 ResourceTicketPtr ImageFactory::Reload( Request& request )
120 DALI_ASSERT_ALWAYS( &request );
122 // go through requests, check real size and attributes again. If different, update related ticket.
123 ResourceTicketPtr ticket;
125 if( !request.resourceId )
127 // in case of OnDemand loading, just return
131 ticket = mResourceClient.RequestResourceTicket( request.resourceId );
133 // ticket might have been deleted, eg. Image::Disconnect
136 ticket = IssueLoadRequest( request.url, request.attributes );
137 request.resourceId = ticket->GetId();
139 else // ticket still alive
141 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
142 ticket->GetTypePath().type->id == ResourceNativeImage ||
143 ticket->GetTypePath().type->id == ResourceTargetImage );
145 // do not reload if still loading
146 if ( ticket->GetLoadingState() == ResourceLoading )
152 Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request.url, *request.attributes, size );
154 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
156 if( size == attrib.GetSize() )
158 mResourceClient.ReloadResource( ticket->GetId(), false );
162 // if not, return a different ticket
163 ticket = IssueLoadRequest( request.url, request.attributes );
164 request.resourceId = ticket->GetId();
170 void ImageFactory::RecoverFromContextLoss()
172 for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
174 // go through requests, reload with resource ticket's attributes.
175 Request* request = (*it).second;
176 if( request->resourceId )
178 ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
180 // do not reload if still loading
181 // check ticket is not NULL as the resource could have already been destroyed
182 if ( ticket && ticket->GetLoadingState() != ResourceLoading )
184 // Ensure the finished status is reset
185 mResourceClient.ReloadResource( ticket->GetId(), true );
191 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
198 return String::EMPTY;
201 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
205 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
206 ticket->GetTypePath().type->id == ResourceNativeImage ||
207 ticket->GetTypePath().type->id == ResourceTargetImage );
208 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
211 return ImageAttributes::DEFAULT_ATTRIBUTES;
214 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
216 if( request && request->attributes )
218 return *(request->attributes);
221 return ImageAttributes::DEFAULT_ATTRIBUTES;
224 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
226 if( ticket && ticket->GetLoadingState() != ResourceLoading )
228 // it is loaded so get the size from actual attributes
229 size = GetActualAttributes( ticket ).GetSize();
233 // not loaded so either loading or not yet loaded, ask platform abstraction
234 Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
235 platformAbstraction.GetClosestImageSize( GetRequestPath( request ), GetRequestAttributes( request ), size );
239 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
241 ResourceTicketPtr ticketPtr(ticket);
242 mTicketsToRelease.push_back(ticketPtr);
245 void ImageFactory::FlushReleaseQueue()
247 mTicketsToRelease.clear();
250 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
251 const Dali::ImageAttributes& actual ) const
253 // do not load image resource again if there is a similar resource loaded:
254 // see explanation in image.h of what is deemed compatible
255 return (requested.GetScalingMode() == actual.GetScalingMode()) &&
257 (requested.GetFilterMode() == actual.GetFilterMode()) ||
258 (requested.GetFilterMode() == ImageAttributes::DontCare)
260 (requested.GetPixelFormat() == actual.GetPixelFormat()) &&
261 (requested.GetFieldBorder() == actual.GetFieldBorder()) &&
262 (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) &&
263 (requested.IsDistanceField() == actual.IsDistanceField()) &&
264 (fabsf(requested.GetWidth() - actual.GetWidth()) <= actual.GetWidth() * mMaxScale) &&
265 (fabsf(requested.GetHeight() - actual.GetHeight()) <= actual.GetHeight() * mMaxScale);
268 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
271 Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
272 mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
273 mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
277 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
279 // Search for a matching resource
281 // check whether the url has been used before
282 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
284 // look for exact matches first
285 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
287 RequestId cachedReqId = it->second;
289 // get cached request
290 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
291 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.");
292 if( foundRequestIter != mRequestCache.end() )
294 const Request& cachedRequest = *(foundRequestIter->second);
295 const ImageAttributes* storedAttributes = cachedRequest.attributes;
297 // compare attributes: NULL means default attributes
300 attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
302 if( !storedAttributes )
304 storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
307 if( *attributes != *storedAttributes )
312 if( filename.compare( cachedRequest.url ) )
314 // hash collision, filenames don't match
318 // we've found an exact match
319 return foundRequestIter->second;
326 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
328 ResourceTicketPtr ticket;
329 // check whether the url has been used before
330 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
332 bool foundCompatible = false;
333 if( foundRequests.first != mUrlCache.end() )
335 // check if we have a compatible resource already loaded
336 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
338 RequestId cachedReqId = it->second;
340 // get cached request
341 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
342 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
343 if( foundRequestIter != mRequestCache.end() )
345 Request& cachedRequest = *(foundRequestIter->second);
346 if( filename.compare( cachedRequest.url ) )
348 // hash collision, filenames don't match
352 if( !cachedRequest.resourceId )
357 ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
360 cachedRequest.resourceId = 0;
364 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
365 ticket->GetTypePath().type->id == ResourceNativeImage ||
366 ticket->GetTypePath().type->id == ResourceTargetImage );
368 // check for compatible ImageAttributes
369 const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
372 attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
375 // in case both attributes are default or they are matching custom ones
376 if( CompareAttributes( *attr, storedAttributes ) )
378 // found compatible resource
379 foundCompatible = true;
384 } // foundRequests.first
386 if( !foundCompatible )
394 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
396 ImageAttributes attributes;
404 // query image size from file if NULL was provided
405 Vector2 size = Dali::Image::GetImageSize( filename );
406 attributes.SetSize( size.width, size.height );
409 BitmapResourceType resourceType( attributes );
410 ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
414 void ImageFactory::RequestDiscarded( const Request& req )
416 RequestId id( req.GetId() );
417 // find in mRequestCache
418 RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
419 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
421 // memory is freed up by intrusive_ptr
423 mRequestCache.erase( foundRequestIter );
426 for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
428 if( id == it->second )
430 mUrlCache.erase( it );
436 std::size_t ImageFactory::GetHashForCachedRequest( const Request& request )
438 const RequestId requestId = request.GetId();
439 std::size_t locatorHash(0);
440 RequestPathHashMap::const_iterator it;
442 for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
444 if( it->second == requestId )
446 locatorHash = it->first;
450 DALI_ASSERT_DEBUG( it!=mUrlCache.end() && "Only already-cached requests can have their locator hashes looked-up." );
454 } // namespace Internal