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