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.
18 #include "resource-thread-image.h"
19 #include <dali/public-api/common/ref-counted-dali-vector.h>
20 #include <dali/integration-api/bitmap.h>
21 #include <dali/integration-api/debug.h>
22 #include <dali/integration-api/resource-cache.h>
23 #include <dali/integration-api/resource-types.h>
25 #include "loader-bmp.h"
26 #include "loader-gif.h"
27 #include "loader-jpeg.h"
28 #include "loader-png.h"
29 #include "loader-ico.h"
30 #include "loader-ktx.h"
31 #include "loader-wbmp.h"
33 #include "file-closer.h"
36 using namespace Dali::Integration;
38 using boost::unique_lock;
49 typedef bool (*LoadBitmapFunction)( FILE*, Bitmap&, ImageAttributes&, const ResourceLoadingClient& ); ///@ToDo: Make attributes a const reference?
50 typedef bool (*LoadBitmapHeaderFunction)(FILE*, const ImageAttributes& attrs, unsigned int& width, unsigned int& height );
53 * Stores the magic bytes, and the loader and header functions used for each image loader.
57 unsigned char magicByte1; ///< The first byte in the file should be this
58 unsigned char magicByte2; ///< The second byte in the file should be this
59 LoadBitmapFunction loader; ///< The function which decodes the file
60 LoadBitmapHeaderFunction header; ///< The function which decodes the header of the file
61 Bitmap::Profile profile; ///< The kind of bitmap to be created
62 /// (addressable packed pixels or an opaque compressed blob).
66 * Enum for file formats, has to be in sync with BITMAP_LOADER_LOOKUP_TABLE
70 // Unknown file format
73 // formats that use magic bytes
80 FORMAT_MAGIC_BYTE_COUNT,
82 // formats after this one do not use magic bytes
83 FORMAT_WBMP = FORMAT_MAGIC_BYTE_COUNT,
88 * A lookup table containing all the bitmap loaders with the appropriate information.
89 * Has to be in sync with enum FileFormats
91 const BitmapLoader BITMAP_LOADER_LOOKUP_TABLE[FORMAT_TOTAL_COUNT] =
93 { Png::MAGIC_BYTE_1, Png::MAGIC_BYTE_2, LoadBitmapFromPng, LoadPngHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
94 { Jpeg::MAGIC_BYTE_1, Jpeg::MAGIC_BYTE_2, LoadBitmapFromJpeg, LoadJpegHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
95 { Bmp::MAGIC_BYTE_1, Bmp::MAGIC_BYTE_2, LoadBitmapFromBmp, LoadBmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
96 { Gif::MAGIC_BYTE_1, Gif::MAGIC_BYTE_2, LoadBitmapFromGif, LoadGifHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
97 { Ktx::MAGIC_BYTE_1, Ktx::MAGIC_BYTE_2, LoadBitmapFromKtx, LoadKtxHeader, Bitmap::BITMAP_COMPRESSED },
98 { Ico::MAGIC_BYTE_1, Ico::MAGIC_BYTE_2, LoadBitmapFromIco, LoadIcoHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
99 { 0x0, 0x0, LoadBitmapFromWbmp, LoadWbmpHeader, Bitmap::BITMAP_2D_PACKED_PIXELS },
102 const unsigned int MAGIC_LENGTH = 2;
105 * This code tries to predict the file format from the filename to help with format picking.
107 struct FormatExtension
109 const std::string extension;
113 const FormatExtension FORMAT_EXTENSIONS[] =
115 { ".png", FORMAT_PNG },
116 { ".jpg", FORMAT_JPEG },
117 { ".bmp", FORMAT_BMP },
118 { ".gif", FORMAT_GIF },
119 { ".ktx", FORMAT_KTX },
120 { ".ico", FORMAT_ICO },
121 { ".wbmp", FORMAT_WBMP }
124 const unsigned int FORMAT_EXTENSIONS_COUNT = sizeof(FORMAT_EXTENSIONS) / sizeof(FormatExtension);
126 FileFormats GetFormatHint( const std::string& filename )
128 FileFormats format = FORMAT_UNKNOWN;
130 for ( unsigned int i = 0; i < FORMAT_EXTENSIONS_COUNT; ++i )
132 unsigned int length = FORMAT_EXTENSIONS[i].extension.size();
133 if ( ( filename.size() > length ) &&
134 ( 0 == filename.compare( filename.size() - length, length, FORMAT_EXTENSIONS[i].extension ) ) )
136 format = FORMAT_EXTENSIONS[i].format;
145 * Checks the magic bytes of the file first to determine which Image decoder to use to decode the
147 * @param[in] fp The file to decode
148 * @param[in] format Hint about what format to try first
149 * @param[out] loader Set with the function to use to decode the image
150 * @param[out] header Set with the function to use to decode the header
151 * @param[out] profile The kind of bitmap to hold the bits loaded for the bitmap.
152 * @return true, if we can decode the image, false otherwise
154 bool GetBitmapLoaderFunctions( FILE *fp,
156 LoadBitmapFunction& loader,
157 LoadBitmapHeaderFunction& header,
158 Bitmap::Profile& profile )
160 unsigned char magic[MAGIC_LENGTH];
161 size_t read = fread(magic, sizeof(unsigned char), MAGIC_LENGTH, fp);
163 // Reset to the start of the file.
164 if( fseek(fp, 0, SEEK_SET) )
166 DALI_LOG_ERROR("Error seeking to start of file\n");
169 if (read != MAGIC_LENGTH)
174 bool loaderFound = false;
175 const BitmapLoader *lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
176 ImageAttributes attrs;
178 // try hinted format first
179 if ( format != FORMAT_UNKNOWN )
181 lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + format;
182 if ( format >= FORMAT_MAGIC_BYTE_COUNT ||
183 ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] ) )
185 unsigned int width = 0;
186 unsigned int height = 0;
187 loaderFound = lookupPtr->header(fp, attrs, width, height );
191 // then try to get a match with formats that have magic bytes
192 if ( false == loaderFound )
194 for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE;
195 lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
198 if ( lookupPtr->magicByte1 == magic[0] && lookupPtr->magicByte2 == magic[1] )
200 // to seperate ico file format and wbmp file format
201 unsigned int width = 0;
202 unsigned int height = 0;
203 loaderFound = lookupPtr->header(fp, attrs, width, height);
212 // finally try formats that do not use magic bytes
213 if ( false == loaderFound )
215 for ( lookupPtr = BITMAP_LOADER_LOOKUP_TABLE + FORMAT_MAGIC_BYTE_COUNT;
216 lookupPtr < BITMAP_LOADER_LOOKUP_TABLE + FORMAT_TOTAL_COUNT;
219 // to seperate ico file format and wbmp file format
220 unsigned int width = 0;
221 unsigned int height = 0;
222 loaderFound = lookupPtr->header(fp, attrs, width, height);
230 // if a loader was found set the outputs
233 loader = lookupPtr->loader;
234 header = lookupPtr->header;
235 profile = lookupPtr->profile;
238 // Reset to the start of the file.
239 if( fseek(fp, 0, SEEK_SET) )
241 DALI_LOG_ERROR("Error seeking to start of file\n");
247 } // namespace - empty
249 ResourceThreadImage::ResourceThreadImage(ResourceLoader& resourceLoader)
250 : ResourceThreadBase(resourceLoader)
254 ResourceThreadImage::~ResourceThreadImage()
258 ResourcePointer ResourceThreadImage::LoadResourceSynchronously( const Integration::ResourceType& resourceType, const std::string& resourcePath )
260 ResourcePointer resource;
261 BitmapPtr bitmap = 0;
263 FILE * const fp = fopen( resourcePath.c_str(), "rb" );
266 bool result = ConvertStreamToBitmap( resourceType, resourcePath, fp, StubbedResourceLoadingClient(), bitmap );
267 if( result && bitmap )
269 resource.Reset(bitmap.Get());
277 void ResourceThreadImage::GetClosestImageSize( const std::string& filename,
278 const ImageAttributes& attributes,
279 Vector2 &closestSize )
281 FILE *fp = fopen(filename.c_str(), "rb");
284 LoadBitmapFunction loaderFunction;
285 LoadBitmapHeaderFunction headerFunction;
286 Bitmap::Profile profile;
288 if ( GetBitmapLoaderFunctions( fp,
289 GetFormatHint(filename),
297 const bool read_res = headerFunction(fp, attributes, width, height);
300 DALI_LOG_WARNING("Image Decoder failed to read header for %s\n", filename.c_str());
303 closestSize.width = (float)width;
304 closestSize.height = (float)height;
308 DALI_LOG_WARNING("Image Decoder for %s unavailable\n", filename.c_str());
315 void ResourceThreadImage::GetClosestImageSize( ResourcePointer resourceBuffer,
316 const ImageAttributes& attributes,
317 Vector2 &closestSize )
319 BitmapPtr bitmap = 0;
321 // Get the blob of binary data that we need to decode:
322 DALI_ASSERT_DEBUG( resourceBuffer );
323 Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( resourceBuffer.Get() );
325 if( encodedBlob != 0 )
327 const size_t blobSize = encodedBlob->GetVector().Size();
328 uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]);
329 DALI_ASSERT_DEBUG( blobSize > 0U );
330 DALI_ASSERT_DEBUG( blobBytes != 0U );
332 if( blobBytes != 0 && blobSize > 0U )
334 // Open a file handle on the memory buffer:
335 FILE * const fp = fmemopen(blobBytes, blobSize, "rb");
338 LoadBitmapFunction loaderFunction;
339 LoadBitmapHeaderFunction headerFunction;
340 Bitmap::Profile profile;
342 if ( GetBitmapLoaderFunctions( fp,
350 const bool read_res = headerFunction(fp, attributes, width, height);
353 DALI_LOG_WARNING("Image Decoder failed to read header for resourceBuffer\n");
356 closestSize.width = (float) width;
357 closestSize.height = (float) height;
366 //----------------- Called from separate thread (mThread) -----------------
368 void ResourceThreadImage::Load(const ResourceRequest& request)
370 DALI_LOG_TRACE_METHOD( mLogFilter );
371 DALI_LOG_INFO( mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str() );
373 bool fileNotFound = false;
374 BitmapPtr bitmap = 0;
377 Dali::Internal::Platform::FileCloser fileCloser( request.GetPath().c_str(), "rb" );
378 FILE * const fp = fileCloser.GetFile();
382 result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, *this, bitmap );
383 // Last chance to interrupt a cancelled load before it is reported back to clients
384 // which have already stopped tracking it:
385 InterruptionPoint(); // Note: This can throw an exception.
386 if( result && bitmap )
388 // Construct LoadedResource and ResourcePointer for image data
389 LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) );
390 // Queue the loaded resource
391 mResourceLoader.AddLoadedResource( resource );
395 DALI_LOG_WARNING( "Unable to decode %s\n", request.GetPath().c_str() );
400 DALI_LOG_WARNING( "Failed to open file to load \"%s\"\n", request.GetPath().c_str() );
408 FailedResource resource(request.GetId(), FailureFileNotFound );
409 mResourceLoader.AddFailedLoad(resource);
413 FailedResource resource(request.GetId(), FailureUnknown);
414 mResourceLoader.AddFailedLoad(resource);
419 void ResourceThreadImage::Decode(const ResourceRequest& request)
421 DALI_LOG_TRACE_METHOD( mLogFilter );
422 DALI_LOG_INFO(mLogFilter, Debug::Verbose, "%s(%s)\n", __FUNCTION__, request.GetPath().c_str());
424 BitmapPtr bitmap = 0;
426 // Get the blob of binary data that we need to decode:
427 DALI_ASSERT_DEBUG( request.GetResource() );
429 DALI_ASSERT_DEBUG( 0 != dynamic_cast<Dali::RefCountedVector<uint8_t>*>( request.GetResource().Get() ) && "Only blobs of binary data can be decoded." );
430 Dali::RefCountedVector<uint8_t>* const encodedBlob = reinterpret_cast<Dali::RefCountedVector<uint8_t>*>( request.GetResource().Get() );
432 if( encodedBlob != 0 )
434 const size_t blobSize = encodedBlob->GetVector().Size();
435 uint8_t * const blobBytes = &(encodedBlob->GetVector()[0]);
436 DALI_ASSERT_DEBUG( blobSize > 0U );
437 DALI_ASSERT_DEBUG( blobBytes != 0U );
439 if( blobBytes != 0 && blobSize > 0U )
441 // Open a file handle on the memory buffer:
442 Dali::Internal::Platform::FileCloser fileCloser( blobBytes, blobSize, "rb" );
443 FILE * const fp = fileCloser.GetFile();
446 bool result = ConvertStreamToBitmap( *request.GetType(), request.GetPath(), fp, StubbedResourceLoadingClient(), bitmap );
448 if ( result && bitmap )
450 // Construct LoadedResource and ResourcePointer for image data
451 LoadedResource resource( request.GetId(), request.GetType()->id, ResourcePointer( bitmap.Get() ) );
452 // Queue the loaded resource
453 mResourceLoader.AddLoadedResource( resource );
457 DALI_LOG_WARNING( "Unable to decode bitmap supplied as in-memory blob.\n" );
465 FailedResource resource(request.GetId(), FailureUnknown);
466 mResourceLoader.AddFailedLoad(resource);
470 void ResourceThreadImage::Save(const Integration::ResourceRequest& request)
472 DALI_LOG_TRACE_METHOD( mLogFilter );
473 DALI_ASSERT_DEBUG( request.GetType()->id == ResourceBitmap );
474 DALI_LOG_WARNING( "Image saving not supported on background resource threads." );
477 bool ResourceThreadImage::ConvertStreamToBitmap(const ResourceType& resourceType, std::string path, FILE * const fp, const ResourceLoadingClient& client, BitmapPtr& ptr)
479 DALI_LOG_TRACE_METHOD(mLogFilter);
480 DALI_ASSERT_DEBUG( ResourceBitmap == resourceType.id );
483 BitmapPtr bitmap = 0;
487 LoadBitmapFunction function;
488 LoadBitmapHeaderFunction header;
489 Bitmap::Profile profile;
491 if ( GetBitmapLoaderFunctions( fp,
492 GetFormatHint( path ),
497 bitmap = Bitmap::New(profile, true);
499 DALI_LOG_SET_OBJECT_STRING(bitmap, path);
500 const BitmapResourceType& resType = static_cast<const BitmapResourceType&>(resourceType);
501 ImageAttributes attributes = resType.imageAttributes;
503 // Check for cancellation now we have hit the filesystem, done some allocation, and burned some cycles:
504 client.InterruptionPoint(); // Note: This can throw an exception.
506 result = function( fp, *bitmap, attributes, client );
510 DALI_LOG_WARNING("Unable to convert %s\n", path.c_str());
514 // Apply the requested image attributes in best-effort fashion:
515 const ImageAttributes& requestedAttributes = resType.imageAttributes;
516 // Cut the bitmap according to the desired width and height so that the
517 // resulting bitmap has the same aspect ratio as the desired dimensions:
518 if( bitmap && bitmap->GetPackedPixelsProfile() && requestedAttributes.GetScalingMode() == ImageAttributes::ScaleToFill )
520 const unsigned loadedWidth = bitmap->GetImageWidth();
521 const unsigned loadedHeight = bitmap->GetImageHeight();
522 const unsigned desiredWidth = requestedAttributes.GetWidth();
523 const unsigned desiredHeight = requestedAttributes.GetHeight();
525 if( desiredWidth < 1U || desiredHeight < 1U )
527 DALI_LOG_WARNING( "Image scaling aborted for image %s as desired dimensions too small (%u, %u)\n.", path.c_str(), desiredWidth, desiredHeight );
529 else if( loadedWidth != desiredWidth || loadedHeight != desiredHeight )
531 const Vector2 desiredDims( desiredWidth, desiredHeight );
533 // Scale the desired rectangle back to fit inside the rectangle of the loaded bitmap:
534 // There are two candidates (scaled by x, and scaled by y) and we choose the smallest area one.
535 const float widthsRatio = loadedWidth / float(desiredWidth);
536 const Vector2 scaledByWidth = desiredDims * widthsRatio;
537 const float heightsRatio = loadedHeight / float(desiredHeight);
538 const Vector2 scaledByHeight = desiredDims * heightsRatio;
539 // Trim top and bottom if the area of the horizontally-fitted candidate is less, else trim the sides:
540 const bool trimTopAndBottom = scaledByWidth.width * scaledByWidth.height < scaledByHeight.width * scaledByHeight.height;
541 const Vector2 scaledDims = trimTopAndBottom ? scaledByWidth : scaledByHeight;
543 // Work out how many pixels to trim from top and bottom, and left and right:
544 // (We only ever do one dimension)
545 const unsigned scanlinesToTrim = trimTopAndBottom ? fabsf( (scaledDims.y - loadedHeight) * 0.5f ) : 0;
546 const unsigned columnsToTrim = trimTopAndBottom ? 0 : fabsf( (scaledDims.x - loadedWidth) * 0.5f );
548 DALI_LOG_INFO( mLogFilter, Debug::Concise, "ImageAttributes::ScaleToFill - Bitmap, desired(%f, %f), loaded(%u,%u), cut_target(%f, %f), trimmed(%u, %u), vertical = %s.\n", desiredDims.x, desiredDims.y, loadedWidth, loadedHeight, scaledDims.x, scaledDims.y, columnsToTrim, scanlinesToTrim, trimTopAndBottom ? "true" : "false" );
550 // Make a new bitmap with the central part of the loaded one if required:
551 if( scanlinesToTrim > 0 || columnsToTrim > 0 ) ///@ToDo: Make this test a bit fuzzy (allow say a 5% difference).
553 client.InterruptionPoint(); // Note: This can throw an exception.
555 const unsigned newWidth = loadedWidth - 2 * columnsToTrim;
556 const unsigned newHeight = loadedHeight - 2 * scanlinesToTrim;
557 BitmapPtr croppedBitmap = Bitmap::New( Bitmap::BITMAP_2D_PACKED_PIXELS, true );
558 Bitmap::PackedPixelsProfile * packedView = croppedBitmap->GetPackedPixelsProfile();
559 DALI_ASSERT_DEBUG( packedView );
560 const Pixel::Format pixelFormat = bitmap->GetPixelFormat();
561 packedView->ReserveBuffer( pixelFormat, newWidth, newHeight, newWidth, newHeight );
563 const unsigned bytesPerPixel = Pixel::GetBytesPerPixel( pixelFormat );
565 const PixelBuffer * const srcPixels = bitmap->GetBuffer() + scanlinesToTrim * loadedWidth * bytesPerPixel;
566 PixelBuffer * const destPixels = croppedBitmap->GetBuffer();
567 DALI_ASSERT_DEBUG( srcPixels && destPixels );
569 // Optimize to a single memcpy if the left and right edges don't need a crop, else copy a scanline at a time:
570 if( trimTopAndBottom )
572 memcpy( destPixels, srcPixels, newHeight * newWidth * bytesPerPixel );
576 for( unsigned y = 0; y < newHeight; ++y )
578 memcpy( &destPixels[y * newWidth * bytesPerPixel], &srcPixels[y * loadedWidth * bytesPerPixel + columnsToTrim * bytesPerPixel], newWidth * bytesPerPixel );
582 // Overwrite the loaded bitmap with the cropped version:
583 bitmap = croppedBitmap;
590 DALI_LOG_WARNING("Image Decoder for %s unavailable\n", path.c_str());
594 ptr.Reset( bitmap.Get() );
598 } // namespace SlpPlatform