5f130c71a8d7712535fdb86f93d78885f7970633
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / image-loaders / loader-jpeg-turbo.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // INTERNAL HEADERS
19 #include "loader-jpeg.h"
20 #include "resource-loading-client.h"
21 #include <dali/integration-api/bitmap.h>
22 #include <resource-loader/debug/resource-loader-debug.h>
23 #include "platform-capabilities.h"
24 #include "image-operations.h"
25
26 // EXTERNAL HEADERS
27 #include <libexif/exif-data.h>
28 #include <libexif/exif-loader.h>
29 #include <libexif/exif-tag.h>
30 #include <turbojpeg.h>
31 #include <jpeglib.h>
32 #include <cstring>
33 #include <setjmp.h>
34
35 namespace Dali
36 {
37 using Integration::Bitmap;
38
39 namespace TizenPlatform
40 {
41
42 namespace
43 {
44   const unsigned DECODED_PIXEL_SIZE = 3;
45   const TJPF DECODED_PIXEL_LIBJPEG_TYPE = TJPF_RGB;
46
47   /** Transformations that can be applied to decoded pixels to respect exif orientation
48    *  codes in image headers */
49   enum JPGFORM_CODE
50   {
51     JPGFORM_NONE = 1, /* no transformation 0th-Row = top & 0th-Column = left */
52     JPGFORM_FLIP_H,   /* horizontal flip 0th-Row = top & 0th-Column = right */
53     JPGFORM_FLIP_V,   /* vertical flip   0th-Row = bottom & 0th-Column = right*/
54     JPGFORM_TRANSPOSE, /* transpose across UL-to-LR axis  0th-Row = bottom & 0th-Column = left*/
55     JPGFORM_TRANSVERSE,/* transpose across UR-to-LL axis  0th-Row = left   & 0th-Column = top*/
56     JPGFORM_ROT_90,    /* 90-degree clockwise rotation  0th-Row = right  & 0th-Column = top*/
57     JPGFORM_ROT_180,   /* 180-degree rotation  0th-Row = right  & 0th-Column = bottom*/
58     JPGFORM_ROT_270    /* 270-degree clockwise (or 90 ccw) 0th-Row = left  & 0th-Column = bottom*/
59   };
60
61   struct RGB888Type
62   {
63      char R;
64      char G;
65      char B;
66   };
67
68   /**
69    * @brief Error handling bookeeping for the JPEG Turbo library's
70    * setjmp/longjmp simulated exceptions.
71    */
72   struct JpegErrorState {
73     struct jpeg_error_mgr errorManager;
74     jmp_buf jumpBuffer;
75   };
76
77   /**
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.
80    */
81   void  JpegErrorHandler ( j_common_ptr cinfo )
82   {
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 );
86
87     /* Return control to the setjmp point */
88     longjmp( myerr->jumpBuffer, 1 );
89   }
90
91   void JpegOutputMessageHandler( j_common_ptr cinfo )
92   {
93     /* Stop libjpeg from printing to stderr - Do Nothing */
94   }
95
96   /** Simple struct to ensure xif data is deleted. */
97   struct ExifAutoPtr
98   {
99     ExifAutoPtr( ExifData* data)
100     :mData( data )
101     {}
102
103     ~ExifAutoPtr()
104     {
105       exif_data_free( mData);
106     }
107     ExifData *mData;
108   };
109
110   /** simple class to enforce clean-up of JPEG structures. */
111   struct AutoJpg
112   {
113     AutoJpg(const tjhandle jpgHandle)
114     : mHnd(jpgHandle)
115     {
116     }
117
118     ~AutoJpg()
119     {
120       // clean up JPG resources
121       tjDestroy( mHnd );
122     }
123
124     tjhandle GetHandle() const
125     {
126       return mHnd ;
127     }
128
129   private:
130     AutoJpg( const AutoJpg& ); //< not defined
131     AutoJpg& operator= ( const AutoJpg& ); //< not defined
132
133     tjhandle mHnd;
134   }; // struct AutoJpg;
135
136   /** RAII wrapper to free memory allocated by the jpeg-turbo library. */
137   struct AutoJpgMem
138   {
139     AutoJpgMem(unsigned char * const tjMem)
140     : mTjMem(tjMem)
141     {
142     }
143
144     ~AutoJpgMem()
145     {
146       tjFree(mTjMem);
147     }
148
149     unsigned char * Get() const
150     {
151       return mTjMem;
152     }
153
154   private:
155     AutoJpgMem( const AutoJpgMem& ); //< not defined
156     AutoJpgMem& operator= ( const AutoJpgMem& ); //< not defined
157
158     unsigned char * const mTjMem;
159   };
160
161   // Workaround to avoid exceeding the maximum texture size
162   const int MAX_TEXTURE_WIDTH  = 4096;
163   const int MAX_TEXTURE_HEIGHT = 4096;
164
165 } // namespace
166
167 bool JpegRotate90 (unsigned char *buffer, int width, int height, int bpp);
168 bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp);
169 bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp);
170 JPGFORM_CODE ConvertExifOrientation(ExifData* exifData);
171 bool TransformSize( int requiredWidth, int requiredHeight,
172                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
173                     JPGFORM_CODE transform,
174                     int& preXformImageWidth, int& preXformImageHeight,
175                     int& postXformImageWidth, int& postXformImageHeight );
176
177 bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
178 {
179   // using libjpeg API to avoid having to read the whole file in a buffer
180   struct jpeg_decompress_struct cinfo;
181   struct JpegErrorState jerr;
182   cinfo.err = jpeg_std_error( &jerr.errorManager );
183
184   jerr.errorManager.output_message = JpegOutputMessageHandler;
185   jerr.errorManager.error_exit = JpegErrorHandler;
186
187   // On error exit from the JPEG lib, control will pass via JpegErrorHandler
188   // into this branch body for cleanup and error return:
189   if(setjmp(jerr.jumpBuffer))
190   {
191     jpeg_destroy_decompress(&cinfo);
192     return false;
193   }
194
195   jpeg_create_decompress( &cinfo );
196
197   jpeg_stdio_src( &cinfo, fp );
198
199   // Check header to see if it is  JPEG file
200   if( jpeg_read_header( &cinfo, TRUE ) != JPEG_HEADER_OK )
201   {
202     width = height = 0;
203     jpeg_destroy_decompress( &cinfo );
204     return false;
205   }
206
207   width = cinfo.image_width;
208   height = cinfo.image_height;
209
210   jpeg_destroy_decompress( &cinfo );
211   return true;
212 }
213
214 bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader::Input& input, Integration::Bitmap& bitmap )
215 {
216   const int flags= 0;
217   FILE* const fp = input.file;
218
219   if( fseek(fp,0,SEEK_END) )
220   {
221     DALI_LOG_ERROR("Error seeking to end of file\n");
222     return false;
223   }
224
225   long positionIndicator = ftell(fp);
226   unsigned int jpegBufferSize = 0u;
227   if( positionIndicator > -1L )
228   {
229     jpegBufferSize = static_cast<unsigned int>(positionIndicator);
230   }
231
232   if( 0u == jpegBufferSize )
233   {
234     return false;
235   }
236
237   if( fseek(fp, 0, SEEK_SET) )
238   {
239     DALI_LOG_ERROR("Error seeking to start of file\n");
240     return false;
241   }
242
243   Vector<unsigned char> jpegBuffer;
244   try
245   {
246     jpegBuffer.Reserve( jpegBufferSize );
247   }
248   catch(...)
249   {
250     DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U );
251     return false;
252   }
253   unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
254
255   // Pull the compressed JPEG image bytes out of a file and into memory:
256   if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
257   {
258     DALI_LOG_WARNING("Error on image file read.\n");
259     return false;
260   }
261
262   if( fseek(fp, 0, SEEK_SET) )
263   {
264     DALI_LOG_ERROR("Error seeking to start of file\n");
265   }
266
267   // Allow early cancellation between the load and the decompress:
268   client.InterruptionPoint();
269
270   AutoJpg autoJpg(tjInitDecompress());
271
272   if(autoJpg.GetHandle() == NULL)
273   {
274     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
275     return false;
276   }
277
278   JPGFORM_CODE transform = JPGFORM_NONE;
279
280   if( input.reorientationRequested )
281   {
282     ExifAutoPtr exifData( exif_data_new_from_data(jpegBufferPtr, jpegBufferSize) );
283     if( exifData.mData )
284     {
285       transform = ConvertExifOrientation(exifData.mData);
286     }
287   }
288
289   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
290   int chrominanceSubsampling = -1;
291   int preXformImageWidth = 0, preXformImageHeight = 0;
292   if( tjDecompressHeader2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
293   {
294     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
295     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
296   }
297
298   if(preXformImageWidth == 0 || preXformImageHeight == 0)
299   {
300     DALI_LOG_WARNING("Invalid Image!\n");
301     return false;
302   }
303
304   int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
305   int requiredHeight = input.scalingParameters.dimensions.GetHeight();
306
307   // If transform is a 90 or 270 degree rotation, the logical width and height
308   // request from the client needs to be adjusted to account by effectively
309   // rotating that too, and the final width and height need to be swapped:
310   int postXformImageWidth = preXformImageWidth;
311   int postXformImageHeight = preXformImageHeight;
312
313
314   int scaledPreXformWidth   = preXformImageWidth;
315   int scaledPreXformHeight  = preXformImageHeight;
316   int scaledPostXformWidth  = postXformImageWidth;
317   int scaledPostXformHeight = postXformImageHeight;
318
319   TransformSize( requiredWidth, requiredHeight,
320                  input.scalingParameters.scalingMode,
321                  input.scalingParameters.samplingMode,
322                  transform,
323                  scaledPreXformWidth, scaledPreXformHeight,
324                  scaledPostXformWidth, scaledPostXformHeight );
325
326   // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
327
328   unsigned char * const bitmapPixelBuffer =  bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight);
329
330   // Allow early cancellation before decoding:
331   client.InterruptionPoint();
332
333   if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, 0, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
334   {
335     std::string errorString = tjGetErrorStr();
336
337     if( errorString.find("Corrupt JPEG data") == std::string::npos )
338     {
339       DALI_LOG_ERROR("%s\n", errorString.c_str());
340       return false;
341     }
342     else
343     {
344       DALI_LOG_WARNING("%s\n", errorString.c_str());
345     }
346
347     //TurboJPEG API functions will now return an error code if a warning is triggered in the underlying libjpeg API.
348     //So, we need to distinguish return of tjGetErrorStr() is warning or error.
349     //If the return string has 'Corrupt JPEG data' prefix, it means warning.
350   }
351
352   const unsigned int  bufferWidth  = GetTextureDimension( scaledPreXformWidth );
353   const unsigned int  bufferHeight = GetTextureDimension( scaledPreXformHeight );
354
355   if( transform != JPGFORM_NONE )
356   {
357     // Allow early cancellation before shuffling pixels around on the CPU:
358     client.InterruptionPoint();
359   }
360
361   bool result = false;
362   switch(transform)
363   {
364     case JPGFORM_NONE:
365     {
366       result = true;
367       break;
368     }
369     // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
370     case JPGFORM_ROT_180:
371     {
372       result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
373       break;
374     }
375     case JPGFORM_ROT_270:
376     {
377       result = JpegRotate270(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
378       break;
379     }
380     case JPGFORM_ROT_90:
381     {
382       result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
383       break;
384     }
385     /// Less-common orientation changes, since they don't correspond to a camera's
386     // physical orientation:
387     case JPGFORM_FLIP_H:
388     case JPGFORM_FLIP_V:
389     case JPGFORM_TRANSPOSE:
390     case JPGFORM_TRANSVERSE:
391     {
392       DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
393       break;
394     }
395   }
396   return result;
397 }
398
399 ///@Todo: Move all these rotation functions to portable/image-operations and take "Jpeg" out of their names.
400 bool JpegRotate90(unsigned char *buffer, int width, int height, int bpp)
401 {
402   int  w, iw, ih, hw = 0;
403   int ix, iy = 0;
404   iw = width;
405   ih = height;
406   Vector<unsigned char> data;
407   data.Reserve(width * height * bpp);
408   unsigned char *dataPtr = data.Begin();
409   memcpy(dataPtr, buffer, width * height * bpp);
410   w = ih;
411   ih = iw;
412   iw = w;
413   hw = iw * ih;
414   hw = - hw - 1;
415   switch(bpp)
416   {
417     case 3:
418     {
419       RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + iw - 1;
420       RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
421
422       for(ix = iw; -- ix >= 0;)
423       {
424         for(iy = ih; -- iy >= 0; ++from )
425         {
426           *to = *from;
427           to += iw;
428         }
429         to += hw;
430       }
431       break;
432     }
433
434     default:
435     {
436       return false;
437     }
438   }
439
440   return true;
441 }
442
443 bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp)
444 {
445   int  ix, iw, ih, hw = 0;
446   iw = width;
447   ih = height;
448   hw = iw * ih;
449   ix = hw;
450
451   switch(bpp)
452   {
453     case 3:
454     {
455       RGB888Type tmp;
456       RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) ;
457       RGB888Type* from = reinterpret_cast<RGB888Type*>( buffer ) + hw - 1;
458       for(; --ix >= (hw / 2); ++to, --from)
459       {
460         tmp = *to;
461         *to = *from;
462         *from = tmp;
463       }
464       break;
465     }
466
467     default:
468     {
469       return false;
470     }
471   }
472
473   return true;
474 }
475
476 bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp)
477 {
478   int  w, iw, ih, hw = 0;
479   int ix, iy = 0;
480
481   iw = width;
482   ih = height;
483   Vector<unsigned char> data;
484   data.Reserve(width * height * bpp);
485   unsigned char *dataPtr = data.Begin();
486   memcpy(dataPtr, buffer, width * height * bpp);
487   w = ih;
488   ih = iw;
489   iw = w;
490   hw = iw * ih;
491
492   switch(bpp)
493   {
494     case 3:
495     {
496       RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + hw  - iw;
497       RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
498
499       w = -w;
500       hw =  hw + 1;
501       for(ix = iw; -- ix >= 0;)
502       {
503         for(iy = ih; -- iy >= 0;)
504         {
505           *to = *from;
506           from += 1;
507           to += w;
508         }
509         to += hw;
510       }
511       break;
512     }
513     default:
514     {
515       return false;
516     }
517   }
518
519   return true;
520 }
521
522 bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char >& encodedPixels,
523                    const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality )
524 {
525   if( !pixelBuffer )
526   {
527     DALI_LOG_ERROR("Null input buffer\n");
528     return false;
529   }
530
531   // Translate pixel format enum:
532   int jpegPixelFormat = -1;
533
534   switch( pixelFormat )
535   {
536     case Pixel::RGB888:
537     {
538       jpegPixelFormat = TJPF_RGB;
539       break;
540     }
541     case Pixel::RGBA8888:
542     {
543       // Ignore the alpha:
544       jpegPixelFormat = TJPF_RGBX;
545       break;
546     }
547     case Pixel::BGRA8888:
548     {
549       // Ignore the alpha:
550       jpegPixelFormat = TJPF_BGRX;
551       break;
552     }
553     default:
554     {
555       DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
556       return false;
557     }
558   }
559
560   // Assert quality is in the documented allowable range of the jpeg-turbo lib:
561   DALI_ASSERT_DEBUG( quality >= 1 );
562   DALI_ASSERT_DEBUG( quality <= 100 );
563   if( quality < 1 )
564   {
565     quality = 1;
566   }
567   if( quality > 100 )
568   {
569     quality = 100;
570   }
571
572   // Initialise a JPEG codec:
573   AutoJpg autoJpg( tjInitCompress() );
574   {
575     if( autoJpg.GetHandle() == NULL )
576     {
577       DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
578       return false;
579     }
580
581     // Run the compressor:
582     unsigned char* dstBuffer = NULL;
583     unsigned long dstBufferSize = 0;
584     const int flags = 0;
585
586     if( tjCompress2( autoJpg.GetHandle(), const_cast<unsigned char*>(pixelBuffer), width, 0, height, jpegPixelFormat, &dstBuffer, &dstBufferSize, TJSAMP_444, quality, flags ) )
587     {
588       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
589       return false;
590     }
591
592     // Safely wrap the jpeg codec's buffer in case we are about to throw, then
593     // save the pixels to a persistent buffer that we own and let our cleaner
594     // class clean up the buffer as it goes out of scope:
595     AutoJpgMem cleaner( dstBuffer );
596     encodedPixels.Reserve( dstBufferSize );
597     memcpy( encodedPixels.Begin(), dstBuffer, dstBufferSize );
598   }
599   return true;
600 }
601
602
603 JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
604 {
605   JPGFORM_CODE transform = JPGFORM_NONE;
606   ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
607   int orientation = 0;
608   if( entry )
609   {
610     orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
611     switch( orientation )
612     {
613       case 1:
614       {
615         transform = JPGFORM_NONE;
616         break;
617       }
618       case 2:
619       {
620         transform = JPGFORM_FLIP_H;
621         break;
622       }
623       case 3:
624       {
625         transform = JPGFORM_FLIP_V;
626         break;
627       }
628       case 4:
629       {
630         transform = JPGFORM_TRANSPOSE;
631         break;
632       }
633       case 5:
634       {
635         transform = JPGFORM_TRANSVERSE;
636         break;
637       }
638       case 6:
639       {
640         transform = JPGFORM_ROT_90;
641         break;
642       }
643       case 7:
644       {
645         transform = JPGFORM_ROT_180;
646         break;
647       }
648       case 8:
649       {
650         transform = JPGFORM_ROT_270;
651         break;
652       }
653       default:
654       {
655         // Try to keep loading the file, but let app developer know there was something fishy:
656         DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
657         break;
658       }
659     }
660   }
661   return transform;
662 }
663
664 bool TransformSize( int requiredWidth, int requiredHeight,
665                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
666                     JPGFORM_CODE transform,
667                     int& preXformImageWidth, int& preXformImageHeight,
668                     int& postXformImageWidth, int& postXformImageHeight )
669 {
670   bool success = true;
671
672   if( transform == JPGFORM_ROT_90 || transform == JPGFORM_ROT_270 )
673   {
674     std::swap( requiredWidth, requiredHeight );
675     std::swap( postXformImageWidth, postXformImageHeight );
676   }
677
678   // Apply the special rules for when there are one or two zeros in requested dimensions:
679   const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
680   requiredWidth = correctedDesired.GetWidth();
681   requiredHeight = correctedDesired.GetHeight();
682
683   // Rescale image during decode using one of the decoder's built-in rescaling
684   // ratios (expected to be powers of 2), keeping the final image at least as
685   // wide and high as was requested:
686
687   int numFactors = 0;
688   tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
689   if( factors == NULL )
690   {
691     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
692     success = false;
693   }
694   else
695   {
696     // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
697     // apply it if the application requested one of those:
698     // (use a switch case here so this code will fail to compile if other modes are added)
699     bool downscale = true;
700     switch( samplingMode )
701     {
702       case SamplingMode::BOX:
703       case SamplingMode::BOX_THEN_NEAREST:
704       case SamplingMode::BOX_THEN_LINEAR:
705       case SamplingMode::DONT_CARE:
706       {
707         downscale = true;
708         break;
709       }
710       case SamplingMode::NO_FILTER:
711       case SamplingMode::NEAREST:
712       case SamplingMode::LINEAR:
713       {
714         downscale = false;
715         break;
716       }
717     }
718
719     int scaleFactorIndex( 0 );
720     if( downscale )
721     {
722       // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
723       for( int i = 1; i < numFactors; ++i )
724       {
725         bool widthLessRequired  = TJSCALED( postXformImageWidth,  factors[i]) < requiredWidth;
726         bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
727         // If either scaled dimension is smaller than the desired one, we were done at the last iteration
728         if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
729         {
730           break;
731         }
732         // If both dimensions are smaller than the desired one, we were done at the last iteration:
733         if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
734         {
735           break;
736         }
737         // If the width is smaller than the desired one, we were done at the last iteration:
738         if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
739         {
740           break;
741         }
742         // If the width is smaller than the desired one, we were done at the last iteration:
743         if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
744         {
745           break;
746         }
747         // This factor stays is within our fitting mode constraint so remember it:
748         scaleFactorIndex = i;
749       }
750     }
751
752     // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
753     for( int i = scaleFactorIndex; i < numFactors; ++i )
754     {
755       // Continue downscaling to below maximum texture size (if possible)
756       scaleFactorIndex = i;
757
758       if( TJSCALED(postXformImageWidth,  (factors[i])) < MAX_TEXTURE_WIDTH &&
759           TJSCALED(postXformImageHeight, (factors[i])) < MAX_TEXTURE_HEIGHT )
760       {
761         // Current scale-factor downscales to below maximum texture size
762         break;
763       }
764     }
765
766     // We have finally chosen the scale-factor, return width/height values
767     if( scaleFactorIndex > 0 )
768     {
769       preXformImageWidth   = TJSCALED(preXformImageWidth,   (factors[scaleFactorIndex]));
770       preXformImageHeight  = TJSCALED(preXformImageHeight,  (factors[scaleFactorIndex]));
771       postXformImageWidth  = TJSCALED(postXformImageWidth,  (factors[scaleFactorIndex]));
772       postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
773     }
774   }
775
776   return success;
777 }
778
779 ExifData* LoadExifData( FILE* fp )
780 {
781   ExifData*     exifData=NULL;
782   ExifLoader*   exifLoader;
783   unsigned char dataBuffer[1024];
784
785   if( fseek( fp, 0, SEEK_SET ) )
786   {
787     DALI_LOG_ERROR("Error seeking to start of file\n");
788   }
789   else
790   {
791     exifLoader = exif_loader_new ();
792
793     while( !feof(fp) )
794     {
795       int size = fread( dataBuffer, 1, sizeof( dataBuffer ), fp );
796       if( size <= 0 )
797       {
798         break;
799       }
800       if( ! exif_loader_write( exifLoader, dataBuffer, size ) )
801       {
802         break;
803       }
804     }
805
806     exifData = exif_loader_get_data( exifLoader );
807     exif_loader_unref( exifLoader );
808   }
809
810   return exifData;
811 }
812
813 bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
814 {
815   unsigned int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
816   unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
817   FILE* const fp = input.file;
818
819   bool success = false;
820   if( requiredWidth == 0 && requiredHeight == 0 )
821   {
822     success = LoadJpegHeader( fp, width, height );
823   }
824   else
825   {
826     // Double check we get the same width/height from the header
827     unsigned int headerWidth;
828     unsigned int headerHeight;
829     if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
830     {
831       JPGFORM_CODE transform = JPGFORM_NONE;
832
833       if( input.reorientationRequested )
834       {
835         ExifAutoPtr exifData( LoadExifData( fp ) );
836         if( exifData.mData )
837         {
838           transform = ConvertExifOrientation(exifData.mData);
839         }
840
841         int preXformImageWidth = headerWidth;
842         int preXformImageHeight = headerHeight;
843         int postXformImageWidth = headerWidth;
844         int postXformImageHeight = headerHeight;
845
846         success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
847         if(success)
848         {
849           width = postXformImageWidth;
850           height = postXformImageHeight;
851         }
852       }
853       else
854       {
855         success = true;
856         width = headerWidth;
857         height = headerHeight;
858       }
859     }
860   }
861   return success;
862 }
863
864
865 } // namespace TizenPlatform
866
867 } // namespace Dali