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),
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 *req )
79 ResourceTicketPtr ticket;
80 DALI_ASSERT_DEBUG( req );
82 ResourceId resId = req->resourceId;
85 // not yet associated with a resource, load
86 std::size_t urlHash(0);
87 RequestPathHashMap::const_iterator it;
88 for( it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
90 if( it->second == req->GetId() )
96 DALI_ASSERT_DEBUG( it!=mUrlCache.end() );
98 ticket = FindCompatibleResource( req->url, urlHash, req->attributes );
102 // didn't find compatible resource
103 ticket = IssueLoadRequest( req->url, req->attributes );
106 req->resourceId = ticket->GetId();
110 ticket = mResourceClient.RequestResourceTicket( resId );
113 // resource has been discarded since
114 ticket = IssueLoadRequest( req->url, req->attributes );
115 req->resourceId = ticket->GetId();
117 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
118 ticket->GetTypePath().type->id == ResourceNativeImage ||
119 ticket->GetTypePath().type->id == ResourceTargetImage );
125 // File can change on fs, but still requesting same attributes.
126 // Returning the ticket is important, because if two different requests mapped to the same resource
127 // before, it is not guaranteed that they will still map to the same resource after reloading.
129 // Image size (40, 40), Req1(img, 40, 40), Req2(img, 256, 256)
130 // In this case both requests will be associated with the resource of size (40, 40)
131 // If image changes on filesystem to size (96, 96) -> now after reloading Req2 would load a
132 // new resource of size (96, 96), but reloading Req1 would load a scaled down version
133 ResourceTicketPtr ImageFactory::Reload( Request* request )
135 DALI_ASSERT_ALWAYS( request );
137 // go through requests, check real size and attributes again. If different, update related ticket.
138 ResourceTicketPtr ticket;
140 if( !request->resourceId )
142 // in case of OnDemand loading, just return
146 ticket = mResourceClient.RequestResourceTicket( request->resourceId );
148 // ticket might have been deleted, eg. Image::Disconnect
151 ticket = IssueLoadRequest( request->url, request->attributes );
152 request->resourceId = ticket->GetId();
154 else // ticket still alive
156 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
157 ticket->GetTypePath().type->id == ResourceNativeImage ||
158 ticket->GetTypePath().type->id == ResourceTargetImage );
160 // do not reload if still loading
161 if ( ticket->GetLoadingState() == ResourceLoading )
167 Internal::ThreadLocalStorage::Get().GetPlatformAbstraction().GetClosestImageSize( request->url, *request->attributes, size );
169 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
171 if( size == attrib.GetSize() )
173 mResourceClient.ReloadResource( ticket->GetId(), false );
177 // if not, return a different ticket
178 ticket = IssueLoadRequest( request->url, request->attributes );
179 request->resourceId = ticket->GetId();
185 void ImageFactory::RecoverFromContextLoss()
187 for( RequestIdMap::iterator it = mRequestCache.begin(); it != mRequestCache.end(); ++it )
189 // go through requests, reload with resource ticket's attributes.
190 Request* request = (*it).second;
191 if( request->resourceId )
193 ResourceTicketPtr ticket = mResourceClient.RequestResourceTicket( request->resourceId );
195 // do not reload if still loading
196 // check ticket is not NULL as the resource could have already been destroyed
197 if ( ticket && ticket->GetLoadingState() != ResourceLoading )
199 // Ensure the finished status is reset
200 mResourceClient.ReloadResource( ticket->GetId(), true );
206 const std::string& ImageFactory::GetRequestPath( const ImageFactoryCache::RequestPtr& request ) const
213 return String::EMPTY;
216 const ImageAttributes& ImageFactory::GetActualAttributes( const ResourceTicketPtr& ticket ) const
220 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
221 ticket->GetTypePath().type->id == ResourceNativeImage ||
222 ticket->GetTypePath().type->id == ResourceTargetImage );
223 const ImageAttributes& attrib = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
226 return ImageAttributes::DEFAULT_ATTRIBUTES;
229 const ImageAttributes& ImageFactory::GetRequestAttributes( const ImageFactoryCache::RequestPtr& request ) const
231 if( request && request->attributes )
233 return *(request->attributes);
236 return ImageAttributes::DEFAULT_ATTRIBUTES;
239 void ImageFactory::GetImageSize( const ImageFactoryCache::RequestPtr& request, const ResourceTicketPtr& ticket, Size& size )
241 if( ticket && ticket->GetLoadingState() != ResourceLoading )
243 // it is loaded so get the size from actual attributes
244 size = GetActualAttributes( ticket ).GetSize();
248 // not loaded so either loading or not yet loaded, ask platform abstraction
249 Integration::PlatformAbstraction& platformAbstraction = Internal::ThreadLocalStorage::Get().GetPlatformAbstraction();
250 platformAbstraction.GetClosestImageSize( GetRequestPath( request ), GetRequestAttributes( request ), size );
254 void ImageFactory::ReleaseTicket( ResourceTicket* ticket )
256 ResourceTicketPtr ticketPtr(ticket);
257 mTicketsToRelease.push_back(ticketPtr);
260 void ImageFactory::FlushReleaseQueue()
262 mTicketsToRelease.clear();
265 bool ImageFactory::CompareAttributes( const Dali::ImageAttributes& requested,
266 const Dali::ImageAttributes& actual ) const
268 // do not load image resource again if there is a similar resource loaded
269 // eg. if size is less than 50% different of what we have
270 // see explanation in image.h of what is deemed compatible
271 return (requested.GetScalingMode() == actual.GetScalingMode()) &&
272 (requested.GetPixelFormat() == actual.GetPixelFormat()) &&
273 (requested.GetFieldBorder() == actual.GetFieldBorder()) &&
274 (fabs(actual.GetFieldRadius() - requested.GetFieldRadius()) <= FLT_EPSILON) &&
275 (requested.IsDistanceField() == actual.IsDistanceField()) &&
276 (fabsf(requested.GetWidth() - actual.GetWidth()) < actual.GetWidth() * mMaxScale) &&
277 (fabsf(requested.GetHeight() - actual.GetHeight()) < actual.GetHeight() * mMaxScale);
280 Request* ImageFactory::InsertNewRequest( ResourceId resourceId, const std::string& filename, std::size_t urlHash, const ImageAttributes* attr )
283 Request* request = new Request( *this, mReqIdCurrent, resourceId, filename, attr );
284 mRequestCache.insert( RequestIdPair( mReqIdCurrent, request ) );
285 mUrlCache.insert( RequestPathHashPair( urlHash, mReqIdCurrent ) );
289 Request* ImageFactory::FindRequest( const std::string& filename, size_t hash, const ImageAttributes* attributes )
291 // Search for a matching resource
293 // check whether the url has been used before
294 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
296 // look for exact matches first
297 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
299 RequestId cachedReqId = it->second;
301 // get cached request
302 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
303 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
304 if( foundRequestIter != mRequestCache.end() )
306 const Request& cachedRequest = *(foundRequestIter->second);
307 const ImageAttributes* storedAttributes = cachedRequest.attributes;
309 // compare attributes: NULL means default attributes
312 attributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
314 if( !storedAttributes )
316 storedAttributes = &ImageAttributes::DEFAULT_ATTRIBUTES;
319 if( *attributes != *storedAttributes )
324 if( filename.compare( cachedRequest.url ) )
326 // hash collision, filenames don't match
330 // we've found an exact match
331 return foundRequestIter->second;
338 ResourceTicketPtr ImageFactory::FindCompatibleResource( const std::string& filename, size_t hash, const ImageAttributes* attr )
340 ResourceTicketPtr ticket;
341 // check whether the url has been used before
342 RequestPathHashRange foundRequests = mUrlCache.equal_range( hash );
344 bool foundCompatible = false;
345 if( foundRequests.first != mUrlCache.end() )
347 // check if we have a compatible resource already loaded
348 for( RequestPathHashMap::iterator it = foundRequests.first; it != foundRequests.second; ++it )
350 RequestId cachedReqId = it->second;
352 // get cached request
353 RequestIdMap::iterator foundRequestIter = mRequestCache.find( cachedReqId );
354 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
355 if( foundRequestIter != mRequestCache.end() )
357 Request& cachedRequest = *(foundRequestIter->second);
358 if( filename.compare( cachedRequest.url ) )
360 // hash collision, filenames don't match
364 if( !cachedRequest.resourceId )
369 ticket = mResourceClient.RequestResourceTicket( cachedRequest.resourceId );
372 cachedRequest.resourceId = 0;
376 DALI_ASSERT_DEBUG( ticket->GetTypePath().type->id == ResourceBitmap ||
377 ticket->GetTypePath().type->id == ResourceNativeImage ||
378 ticket->GetTypePath().type->id == ResourceTargetImage );
380 // check for compatible ImageAttributes
381 const ImageAttributes& storedAttributes = static_cast<ImageTicket*>(ticket.Get())->GetAttributes();
384 attr = &ImageAttributes::DEFAULT_ATTRIBUTES;
387 // in case both attributes are default or they are matching custom ones
388 if( CompareAttributes( *attr, storedAttributes ) )
390 // found compatible resource
391 foundCompatible = true;
396 } // foundRequests.first
398 if( !foundCompatible )
406 ResourceTicketPtr ImageFactory::IssueLoadRequest( const std::string& filename, const ImageAttributes* attr )
408 ImageAttributes attributes;
416 // query image size from file if NULL was provided
417 Vector2 size = Dali::Image::GetImageSize( filename );
418 attributes.SetSize( size.width, size.height );
421 BitmapResourceType resourceType( attributes );
422 ResourceTicketPtr ticket = mResourceClient.RequestResource( resourceType, filename );
426 void ImageFactory::RequestDiscarded( const Request& req )
428 RequestId id( req.GetId() );
429 // find in mRequestCache
430 RequestIdMap::iterator foundRequestIter = mRequestCache.find( id );
431 DALI_ASSERT_DEBUG( foundRequestIter != mRequestCache.end() );
433 // memory is freed up by intrusive_ptr
435 mRequestCache.erase( foundRequestIter );
438 for( RequestPathHashMap::iterator it = mUrlCache.begin(); it != mUrlCache.end(); ++it )
440 if( id == it->second )
442 mUrlCache.erase( it );
448 } // namespace Internal