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