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