2 * Copyright (c) 2017 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 "loader-jpeg.h"
26 #include <libexif/exif-data.h>
27 #include <libexif/exif-loader.h>
28 #include <libexif/exif-tag.h>
29 #include <turbojpeg.h>
34 #include <dali/integration-api/bitmap.h>
37 #include "platform-capabilities.h"
38 #include "image-operations.h"
39 #include <image-loading.h>
43 using Dali::Integration::Bitmap;
44 using Dali::Integration::PixelBuffer;
46 namespace Pixel = Dali::Pixel;
49 const unsigned int DECODED_L8 = 1;
50 const unsigned int DECODED_RGB888 = 3;
51 const unsigned int DECODED_RGBA8888 = 4;
53 /** Transformations that can be applied to decoded pixels to respect exif orientation
54 * codes in image headers */
55 enum class JpegTransform
57 NONE, //< no transformation 0th-Row = top & 0th-Column = left
58 FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
59 FLIP_VERTICAL, //< vertical flip 0th-Row = bottom & 0th-Column = right
60 TRANSPOSE, //< transpose across UL-to-LR axis 0th-Row = bottom & 0th-Column = left
61 TRANSVERSE, //< transpose across UR-to-LL axis 0th-Row = left & 0th-Column = top
62 ROTATE_90, //< 90-degree clockwise rotation 0th-Row = right & 0th-Column = top
63 ROTATE_180, //< 180-degree rotation 0th-Row = right & 0th-Column = bottom
64 ROTATE_270, //< 270-degree clockwise (or 90 ccw) 0th-Row = left & 0th-Column = bottom
68 * @brief Error handling bookeeping for the JPEG Turbo library's
69 * setjmp/longjmp simulated exceptions.
73 struct jpeg_error_mgr errorManager;
78 * @brief Called by the JPEG library when it hits an error.
79 * We jump out of the library so our loader code can return an error.
81 void JpegErrorHandler ( j_common_ptr cinfo )
83 DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" );
84 /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
85 JpegErrorState * myerr = reinterpret_cast<JpegErrorState *>( cinfo->err );
87 /* Return control to the setjmp point */
88 longjmp( myerr->jumpBuffer, 1 );
91 void JpegOutputMessageHandler( j_common_ptr cinfo )
93 /* Stop libjpeg from printing to stderr - Do Nothing */
97 * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
98 * the JPEG to be displayed and fatal errors.
100 bool IsJpegErrorFatal( const std::string& errorMessage )
102 if( ( errorMessage.find("Corrupt JPEG data") != std::string::npos ) ||
103 ( errorMessage.find("Invalid SOS parameters") != std::string::npos ) ||
104 ( errorMessage.find("Invalid JPEG file structure") != std::string::npos ) ||
105 ( errorMessage.find("Unsupported JPEG process") != std::string::npos ) ||
106 ( errorMessage.find("Unsupported marker type") != std::string::npos ) ||
107 ( errorMessage.find("Bogus marker length") != std::string::npos ) ||
108 ( errorMessage.find("Bogus DQT index") != std::string::npos ) ||
109 ( errorMessage.find("Bogus Huffman table definition") != std::string::npos ))
116 // helpers for safe exif memory handling
117 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
119 ExifHandle MakeNullExifData()
121 return ExifHandle{nullptr, exif_data_free};
124 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
126 return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
129 // Helpers for safe Jpeg memory handling
130 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
132 JpegHandle MakeJpegCompressor()
134 return JpegHandle{tjInitCompress(), tjDestroy};
137 JpegHandle MakeJpegDecompressor()
139 return JpegHandle{tjInitDecompress(), tjDestroy};
142 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
144 JpegMemoryHandle MakeJpegMemory()
146 return JpegMemoryHandle{nullptr, tjFree};
149 template<class T, class Deleter>
150 class UniquePointerSetter final
153 UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
154 : mUniquePointer(uniquePointer),
158 /// @brief Pointer to Pointer cast operator
159 operator T** () { return &mRawPointer; }
161 /// @brief Destructor, reset the unique_ptr
162 ~UniquePointerSetter() { mUniquePointer.reset(mRawPointer); }
165 std::unique_ptr<T, Deleter>& mUniquePointer;
169 template<typename T, typename Deleter>
170 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
172 return UniquePointerSetter<T, Deleter>{uniquePointer};
175 using TransformFunction = std::function<void(PixelBuffer*,unsigned, unsigned)>;
176 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
178 /// @brief Select the transform function depending on the pixel format
179 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
180 Pixel::Format pixelFormat)
182 auto function = TransformFunction{};
184 int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
185 switch( decodedPixelSize )
189 function = functions[0];
194 function = functions[1];
197 case DECODED_RGBA8888:
199 function = functions[2];
204 DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
205 function = functions[1];
212 /// @brief Apply a transform to a buffer
213 bool Transform(const TransformFunctionArray& transformFunctions,
217 Pixel::Format pixelFormat )
219 auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
220 if(transformFunction)
222 transformFunction(buffer, width, height);
224 return bool(transformFunction);
227 /// @brief Auxiliar type to represent pixel data with different number of bytes
235 void FlipVertical(PixelBuffer* buffer, int width, int height)
237 // Destination pixel, set as the first pixel of screen
238 auto to = reinterpret_cast<PixelType<N>*>( buffer );
240 // Source pixel, as the image is flipped horizontally and vertically,
241 // the source pixel is the end of the buffer of size width * height
242 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
244 for (auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
246 std::swap(*from, *to);
251 void FlipHorizontal(PixelBuffer* buffer, int width, int height)
253 for(auto iy = 0; iy < height; ++iy)
255 //Set the destination pixel as the beginning of the row
256 auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
257 //Set the source pixel as the end of the row to flip in X axis
258 auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
259 for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
261 std::swap(*from, *to);
267 void Transpose(PixelBuffer* buffer, int width, int height)
269 //Transform vertically only
270 for(auto iy = 0; iy < height / 2; ++iy)
272 for(auto ix = 0; ix < width; ++ix)
274 auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
275 auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
276 std::swap(*from, *to);
282 void Transverse(PixelBuffer* buffer, int width, int height)
284 using PixelT = PixelType<N>;
286 data.Resize( width * height );
287 auto dataPtr = data.Begin();
289 auto original = reinterpret_cast<PixelT*>(buffer);
290 std::copy(original, original + width * height, dataPtr);
293 for( auto iy = 0; iy < width; ++iy )
295 for( auto ix = 0; ix < height; ++ix, ++to )
297 auto from = dataPtr + ix * width + iy;
305 void Rotate90(PixelBuffer* buffer, int width, int height)
307 using PixelT = PixelType<N>;
309 data.Resize(width * height);
310 auto dataPtr = data.Begin();
312 auto original = reinterpret_cast<PixelT*>(buffer);
313 std::copy(original, original + width * height, dataPtr);
315 std::swap(width, height);
316 auto hw = width * height;
319 auto to = original + width - 1;
322 for(auto ix = width; --ix >= 0;)
324 for(auto iy = height; --iy >= 0; ++from)
334 void Rotate180(PixelBuffer* buffer, int width, int height)
336 using PixelT = PixelType<N>;
338 data.Resize(width * height);
339 auto dataPtr = data.Begin();
341 auto original = reinterpret_cast<PixelT*>(buffer);
342 std::copy(original, original + width * height, dataPtr);
345 for( auto iy = 0; iy < width; iy++ )
347 for( auto ix = 0; ix < height; ix++ )
349 auto from = dataPtr + (height - ix) * width - 1 - iy;
358 void Rotate270(PixelBuffer* buffer, int width, int height)
360 using PixelT = PixelType<N>;
362 data.Resize(width * height);
363 auto dataPtr = data.Begin();
365 auto original = reinterpret_cast<PixelT*>(buffer);
366 std::copy(original, original + width * height, dataPtr);
369 std::swap(width, height);
370 auto hw = width * height;
372 auto* to = original + hw - width;
373 auto* from = dataPtr;
377 for(auto ix = width; --ix >= 0;)
379 for(auto iy = height; --iy >= 0;)
394 namespace TizenPlatform
397 JpegTransform ConvertExifOrientation(ExifData* exifData);
398 bool TransformSize( int requiredWidth, int requiredHeight,
399 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
400 JpegTransform transform,
401 int& preXformImageWidth, int& preXformImageHeight,
402 int& postXformImageWidth, int& postXformImageHeight );
404 bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
406 // using libjpeg API to avoid having to read the whole file in a buffer
407 struct jpeg_decompress_struct cinfo;
408 struct JpegErrorState jerr;
409 cinfo.err = jpeg_std_error( &jerr.errorManager );
411 jerr.errorManager.output_message = JpegOutputMessageHandler;
412 jerr.errorManager.error_exit = JpegErrorHandler;
414 // On error exit from the JPEG lib, control will pass via JpegErrorHandler
415 // into this branch body for cleanup and error return:
416 if(setjmp(jerr.jumpBuffer))
418 jpeg_destroy_decompress(&cinfo);
422 // jpeg_create_decompress internally uses C casts
423 #pragma GCC diagnostic push
424 #pragma GCC diagnostic ignored "-Wold-style-cast"
425 jpeg_create_decompress( &cinfo );
426 #pragma GCC diagnostic pop
428 jpeg_stdio_src( &cinfo, fp );
430 // Check header to see if it is JPEG file
431 if( jpeg_read_header( &cinfo, TRUE ) != JPEG_HEADER_OK )
434 jpeg_destroy_decompress( &cinfo );
438 width = cinfo.image_width;
439 height = cinfo.image_height;
441 jpeg_destroy_decompress( &cinfo );
445 bool LoadBitmapFromJpeg( const ImageLoader::Input& input, Integration::Bitmap& bitmap )
448 FILE* const fp = input.file;
450 if( fseek(fp,0,SEEK_END) )
452 DALI_LOG_ERROR("Error seeking to end of file\n");
456 long positionIndicator = ftell(fp);
457 unsigned int jpegBufferSize = 0u;
458 if( positionIndicator > -1L )
460 jpegBufferSize = static_cast<unsigned int>(positionIndicator);
463 if( 0u == jpegBufferSize )
468 if( fseek(fp, 0, SEEK_SET) )
470 DALI_LOG_ERROR("Error seeking to start of file\n");
474 Vector<unsigned char> jpegBuffer;
477 jpegBuffer.Resize( jpegBufferSize );
481 DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U );
484 unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
486 // Pull the compressed JPEG image bytes out of a file and into memory:
487 if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
489 DALI_LOG_WARNING("Error on image file read.\n");
493 if( fseek(fp, 0, SEEK_SET) )
495 DALI_LOG_ERROR("Error seeking to start of file\n");
498 auto jpeg = MakeJpegDecompressor();
502 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
506 auto transform = JpegTransform::NONE;
508 if( input.reorientationRequested )
510 auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
513 transform = ConvertExifOrientation(exifData.get());
517 // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
518 int chrominanceSubsampling = -1;
519 int preXformImageWidth = 0, preXformImageHeight = 0;
521 // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
522 // Temporarily separate Ubuntu and other profiles.
523 #ifndef DALI_PROFILE_UBUNTU
524 int jpegColorspace = -1;
525 if( tjDecompressHeader3( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace ) == -1 )
527 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
528 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
531 if( tjDecompressHeader2( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
533 DALI_LOG_ERROR("%s\n", tjGetErrorStr());
534 // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
538 if(preXformImageWidth == 0 || preXformImageHeight == 0)
540 DALI_LOG_WARNING("Invalid Image!\n");
544 int requiredWidth = input.scalingParameters.dimensions.GetWidth();
545 int requiredHeight = input.scalingParameters.dimensions.GetHeight();
547 // If transform is a 90 or 270 degree rotation, the logical width and height
548 // request from the client needs to be adjusted to account by effectively
549 // rotating that too, and the final width and height need to be swapped:
550 int postXformImageWidth = preXformImageWidth;
551 int postXformImageHeight = preXformImageHeight;
554 int scaledPreXformWidth = preXformImageWidth;
555 int scaledPreXformHeight = preXformImageHeight;
556 int scaledPostXformWidth = postXformImageWidth;
557 int scaledPostXformHeight = postXformImageHeight;
559 TransformSize( requiredWidth, requiredHeight,
560 input.scalingParameters.scalingMode,
561 input.scalingParameters.samplingMode,
563 scaledPreXformWidth, scaledPreXformHeight,
564 scaledPostXformWidth, scaledPostXformHeight );
567 // Colorspace conversion options
568 TJPF pixelLibJpegType = TJPF_RGB;
569 Pixel::Format pixelFormat = Pixel::RGB888;
570 #ifndef DALI_PROFILE_UBUNTU
571 switch (jpegColorspace)
574 // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
575 // YCbCr images must be converted to RGB before they can actually be displayed.
578 pixelLibJpegType = TJPF_RGB;
579 pixelFormat = Pixel::RGB888;
584 pixelLibJpegType = TJPF_GRAY;
585 pixelFormat = Pixel::L8;
591 pixelLibJpegType = TJPF_CMYK;
592 pixelFormat = Pixel::RGBA8888;
597 pixelLibJpegType = TJPF_RGB;
598 pixelFormat = Pixel::RGB888;
603 // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
604 PixelBuffer* bitmapPixelBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, scaledPostXformWidth, scaledPostXformHeight ) ;
606 if( tjDecompress2( jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>( bitmapPixelBuffer ), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags ) == -1 )
608 std::string errorString = tjGetErrorStr();
610 if( IsJpegErrorFatal( errorString ) )
612 DALI_LOG_ERROR("%s\n", errorString.c_str() );
617 DALI_LOG_WARNING("%s\n", errorString.c_str() );
621 const unsigned int bufferWidth = GetTextureDimension( scaledPreXformWidth );
622 const unsigned int bufferHeight = GetTextureDimension( scaledPreXformHeight );
627 case JpegTransform::NONE:
632 // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
633 case JpegTransform::ROTATE_180:
635 static auto rotate180Functions = TransformFunctionArray {
640 result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
643 case JpegTransform::ROTATE_270:
645 static auto rotate270Functions = TransformFunctionArray {
650 result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
653 case JpegTransform::ROTATE_90:
655 static auto rotate90Functions = TransformFunctionArray {
660 result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
663 case JpegTransform::FLIP_VERTICAL:
665 static auto flipVerticalFunctions = TransformFunctionArray {
670 result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
673 // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
674 case JpegTransform::FLIP_HORIZONTAL:
676 static auto flipHorizontalFunctions = TransformFunctionArray {
681 result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
684 case JpegTransform::TRANSPOSE:
686 static auto transposeFunctions = TransformFunctionArray {
691 result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
694 case JpegTransform::TRANSVERSE:
696 static auto transverseFunctions = TransformFunctionArray {
701 result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
706 DALI_LOG_ERROR( "Unsupported JPEG Orientation transformation: %x.\n", transform );
713 bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char >& encodedPixels,
714 const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality )
719 DALI_LOG_ERROR("Null input buffer\n");
723 // Translate pixel format enum:
724 int jpegPixelFormat = -1;
726 switch( pixelFormat )
730 jpegPixelFormat = TJPF_RGB;
733 case Pixel::RGBA8888:
736 jpegPixelFormat = TJPF_RGBX;
739 case Pixel::BGRA8888:
742 jpegPixelFormat = TJPF_BGRX;
747 DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
752 // Assert quality is in the documented allowable range of the jpeg-turbo lib:
753 DALI_ASSERT_DEBUG( quality >= 1 );
754 DALI_ASSERT_DEBUG( quality <= 100 );
764 // Initialise a JPEG codec:
766 auto jpeg = MakeJpegCompressor();
769 DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
774 // Safely wrap the jpeg codec's buffer in case we are about to throw, then
775 // save the pixels to a persistent buffer that we own and let our cleaner
776 // class clean up the buffer as it goes out of scope:
777 auto dstBuffer = MakeJpegMemory();
779 // Run the compressor:
780 unsigned long dstBufferSize = 0;
783 if( tjCompress2( jpeg.get(),
784 const_cast<unsigned char*>(pixelBuffer),
786 jpegPixelFormat, SetPointer(dstBuffer), &dstBufferSize,
787 TJSAMP_444, quality, flags ) )
789 DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
793 encodedPixels.Resize( dstBufferSize );
794 memcpy( encodedPixels.Begin(), dstBuffer.get(), dstBufferSize );
800 JpegTransform ConvertExifOrientation(ExifData* exifData)
802 auto transform = JpegTransform::NONE;
803 ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
807 orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
808 switch( orientation )
812 transform = JpegTransform::NONE;
817 transform = JpegTransform::FLIP_HORIZONTAL;
822 transform = JpegTransform::FLIP_VERTICAL;
827 transform = JpegTransform::TRANSPOSE;
832 transform = JpegTransform::TRANSVERSE;
837 transform = JpegTransform::ROTATE_90;
842 transform = JpegTransform::ROTATE_180;
847 transform = JpegTransform::ROTATE_270;
852 // Try to keep loading the file, but let app developer know there was something fishy:
853 DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
861 bool TransformSize( int requiredWidth, int requiredHeight,
862 FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
863 JpegTransform transform,
864 int& preXformImageWidth, int& preXformImageHeight,
865 int& postXformImageWidth, int& postXformImageHeight )
869 if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::ROTATE_180 || transform == JpegTransform::TRANSVERSE)
871 std::swap( requiredWidth, requiredHeight );
872 std::swap( postXformImageWidth, postXformImageHeight );
875 // Apply the special rules for when there are one or two zeros in requested dimensions:
876 const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
877 requiredWidth = correctedDesired.GetWidth();
878 requiredHeight = correctedDesired.GetHeight();
880 // Rescale image during decode using one of the decoder's built-in rescaling
881 // ratios (expected to be powers of 2), keeping the final image at least as
882 // wide and high as was requested:
885 tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
886 if( factors == NULL )
888 DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
893 // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
894 // apply it if the application requested one of those:
895 // (use a switch case here so this code will fail to compile if other modes are added)
896 bool downscale = true;
897 switch( samplingMode )
899 case SamplingMode::BOX:
900 case SamplingMode::BOX_THEN_NEAREST:
901 case SamplingMode::BOX_THEN_LINEAR:
902 case SamplingMode::DONT_CARE:
907 case SamplingMode::NO_FILTER:
908 case SamplingMode::NEAREST:
909 case SamplingMode::LINEAR:
916 int scaleFactorIndex( 0 );
919 // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
920 for( int i = 1; i < numFactors; ++i )
922 bool widthLessRequired = TJSCALED( postXformImageWidth, factors[i]) < requiredWidth;
923 bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
924 // If either scaled dimension is smaller than the desired one, we were done at the last iteration
925 if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
929 // If both dimensions are smaller than the desired one, we were done at the last iteration:
930 if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
934 // If the width is smaller than the desired one, we were done at the last iteration:
935 if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
939 // If the width is smaller than the desired one, we were done at the last iteration:
940 if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
944 // This factor stays is within our fitting mode constraint so remember it:
945 scaleFactorIndex = i;
949 // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
950 for( int i = scaleFactorIndex; i < numFactors; ++i )
952 // Continue downscaling to below maximum texture size (if possible)
953 scaleFactorIndex = i;
955 if( TJSCALED(postXformImageWidth, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) &&
956 TJSCALED(postXformImageHeight, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) )
958 // Current scale-factor downscales to below maximum texture size
963 // We have finally chosen the scale-factor, return width/height values
964 if( scaleFactorIndex > 0 )
966 preXformImageWidth = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
967 preXformImageHeight = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
968 postXformImageWidth = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
969 postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
976 ExifHandle LoadExifData( FILE* fp )
978 auto exifData = MakeNullExifData();
979 unsigned char dataBuffer[1024];
981 if( fseek( fp, 0, SEEK_SET ) )
983 DALI_LOG_ERROR("Error seeking to start of file\n");
987 auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
988 exif_loader_new(), exif_loader_unref };
992 int size = fread( dataBuffer, 1, sizeof( dataBuffer ), fp );
997 if( ! exif_loader_write( exifLoader.get(), dataBuffer, size ) )
1003 exifData.reset( exif_loader_get_data( exifLoader.get() ) );
1009 bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
1011 unsigned int requiredWidth = input.scalingParameters.dimensions.GetWidth();
1012 unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1013 FILE* const fp = input.file;
1015 bool success = false;
1016 if( requiredWidth == 0 && requiredHeight == 0 )
1018 success = LoadJpegHeader( fp, width, height );
1022 // Double check we get the same width/height from the header
1023 unsigned int headerWidth;
1024 unsigned int headerHeight;
1025 if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
1027 auto transform = JpegTransform::NONE;
1029 if( input.reorientationRequested )
1031 auto exifData = LoadExifData( fp );
1034 transform = ConvertExifOrientation(exifData.get());
1037 int preXformImageWidth = headerWidth;
1038 int preXformImageHeight = headerHeight;
1039 int postXformImageWidth = headerWidth;
1040 int postXformImageHeight = headerHeight;
1042 success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
1045 width = postXformImageWidth;
1046 height = postXformImageHeight;
1052 width = headerWidth;
1053 height = headerHeight;
1061 } // namespace TizenPlatform