support jpeg image of CMYK colorspace decoding
[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  // CLASS HEADER
19 #include "loader-jpeg.h"
20
21 // EXTERNAL HEADERS
22 #include <functional>
23 #include <array>
24 #include <utility>
25 #include <memory>
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 #include <dali/integration-api/bitmap.h>
35
36 // INTERNAL HEADERS
37 #include "platform-capabilities.h"
38 #include "image-operations.h"
39 #include <image-loading.h>
40
41 namespace
42 {
43 using Dali::Integration::Bitmap;
44 using Dali::Integration::PixelBuffer;
45 using Dali::Vector;
46 namespace Pixel = Dali::Pixel;
47
48
49 const unsigned int DECODED_L8 = 1;
50 const unsigned int DECODED_RGB888 = 3;
51 const unsigned int DECODED_RGBA8888 = 4;
52
53 /** Transformations that can be applied to decoded pixels to respect exif orientation
54   *  codes in image headers */
55 enum class JpegTransform
56 {
57   NONE,             //< no transformation 0th-Row = top & 0th-Column = left
58   FLIP_HORIZONTAL,  //< horizontal flip 0th-Row = top & 0th-Column = right
59   FLIP_VERTICAL,    //< vertical flip   0th-Row = bottom & 0th-Column = right
60   TRANSPOSE,        //< transpose across UL-to-LR axis  0th-Row = bottom & 0th-Column = left
61   TRANSVERSE,       //< transpose across UR-to-LL axis  0th-Row = left   & 0th-Column = top
62   ROTATE_90,        //< 90-degree clockwise rotation  0th-Row = right  & 0th-Column = top
63   ROTATE_180,       //< 180-degree rotation  0th-Row = right  & 0th-Column = bottom
64   ROTATE_270,       //< 270-degree clockwise (or 90 ccw) 0th-Row = left  & 0th-Column = bottom
65 };
66
67 /**
68   * @brief Error handling bookeeping for the JPEG Turbo library's
69   * setjmp/longjmp simulated exceptions.
70   */
71 struct JpegErrorState
72 {
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       ( errorMessage.find("Invalid JPEG file structure") != std::string::npos ) ||
105       ( errorMessage.find("Unsupported JPEG process") != std::string::npos ) ||
106       ( errorMessage.find("Unsupported marker type") != std::string::npos ) ||
107       ( errorMessage.find("Bogus marker length") != std::string::npos ) ||
108       ( errorMessage.find("Bogus DQT index") != std::string::npos ) ||
109       ( errorMessage.find("Bogus Huffman table definition") != std::string::npos ))
110   {
111     return false;
112   }
113   return true;
114 }
115
116 // helpers for safe exif memory handling
117 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
118
119 ExifHandle MakeNullExifData()
120 {
121   return ExifHandle{nullptr, exif_data_free};
122 }
123
124 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
125 {
126   return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
127 }
128
129 // Helpers for safe Jpeg memory handling
130 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
131
132 JpegHandle MakeJpegCompressor()
133 {
134   return JpegHandle{tjInitCompress(), tjDestroy};
135 }
136
137 JpegHandle MakeJpegDecompressor()
138 {
139   return JpegHandle{tjInitDecompress(), tjDestroy};
140 }
141
142 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
143
144 JpegMemoryHandle MakeJpegMemory()
145 {
146   return JpegMemoryHandle{nullptr, tjFree};
147 }
148
149 template<class T, class Deleter>
150 class UniquePointerSetter final
151 {
152 public:
153   UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
154   : mUniquePointer(uniquePointer),
155     mRawPointer(nullptr)
156   {}
157
158   /// @brief Pointer to Pointer cast operator
159   operator T** () { return &mRawPointer; }
160
161   /// @brief Destructor, reset the unique_ptr
162   ~UniquePointerSetter() { mUniquePointer.reset(mRawPointer); }
163
164 private:
165   std::unique_ptr<T, Deleter>& mUniquePointer;
166   T* mRawPointer;
167 };
168
169 template<typename T, typename Deleter>
170 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
171 {
172   return UniquePointerSetter<T, Deleter>{uniquePointer};
173 }
174
175 using TransformFunction = std::function<void(PixelBuffer*,unsigned, unsigned)>;
176 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
177
178 /// @brief Select the transform function depending on the pixel format
179 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
180                                        Pixel::Format pixelFormat)
181 {
182   auto function = TransformFunction{};
183
184   int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
185   switch( decodedPixelSize )
186   {
187     case DECODED_L8:
188     {
189       function = functions[0];
190       break;
191     }
192     case DECODED_RGB888:
193     {
194       function = functions[1];
195       break;
196     }
197     case DECODED_RGBA8888:
198     {
199       function = functions[2];
200       break;
201     }
202     default:
203     {
204       DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
205       function = functions[1];
206       break;
207     }
208   }
209   return function;
210 }
211
212 /// @brief Apply a transform to a buffer
213 bool Transform(const TransformFunctionArray& transformFunctions,
214                PixelBuffer *buffer,
215                int width,
216                int height,
217                Pixel::Format pixelFormat )
218 {
219   auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
220   if(transformFunction)
221   {
222     transformFunction(buffer, width, height);
223   }
224   return bool(transformFunction);
225 }
226
227 /// @brief Auxiliar type to represent pixel data with different number of bytes
228 template<size_t N>
229 struct PixelType
230 {
231   char _[N];
232 };
233
234 template<size_t N>
235 void FlipVertical(PixelBuffer* buffer, int width, int height)
236 {
237   // Destination pixel, set as the first pixel of screen
238   auto to = reinterpret_cast<PixelType<N>*>( buffer );
239
240   // Source pixel, as the image is flipped horizontally and vertically,
241   // the source pixel is the end of the buffer of size width * height
242   auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
243
244   for (auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
245   {
246     std::swap(*from, *to);
247   }
248 }
249
250 template<size_t N>
251 void FlipHorizontal(PixelBuffer* buffer, int width, int height)
252 {
253   for(auto iy = 0; iy < height; ++iy)
254   {
255     //Set the destination pixel as the beginning of the row
256     auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
257     //Set the source pixel as the end of the row to flip in X axis
258     auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
259     for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
260     {
261       std::swap(*from, *to);
262     }
263   }
264 }
265
266 template<size_t N>
267 void Transpose(PixelBuffer* buffer, int width, int height)
268 {
269   //Transform vertically only
270   for(auto iy = 0; iy < height / 2; ++iy)
271   {
272     for(auto ix = 0; ix < width; ++ix)
273     {
274       auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
275       auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
276       std::swap(*from, *to);
277     }
278   }
279 }
280
281 template<size_t N>
282 void Transverse(PixelBuffer* buffer, int width, int height)
283 {
284   using PixelT = PixelType<N>;
285   Vector<PixelT> data;
286   data.Resize( width * height );
287   auto dataPtr = data.Begin();
288
289   auto original = reinterpret_cast<PixelT*>(buffer);
290   std::copy(original, original + width * height, dataPtr);
291
292   auto to = original;
293   for( auto iy = 0; iy < width; ++iy )
294   {
295     for( auto ix = 0; ix < height; ++ix, ++to )
296     {
297       auto from = dataPtr + ix * width + iy;
298       *to = *from;
299     }
300   }
301 }
302
303
304 template<size_t N>
305 void Rotate90(PixelBuffer* buffer, int width, int height)
306 {
307   using PixelT = PixelType<N>;
308   Vector<PixelT> data;
309   data.Resize(width * height);
310   auto dataPtr = data.Begin();
311
312   auto original = reinterpret_cast<PixelT*>(buffer);
313   std::copy(original, original + width * height, dataPtr);
314
315   std::swap(width, height);
316   auto hw = width * height;
317   hw = - hw - 1;
318
319   auto to = original + width - 1;
320   auto from = dataPtr;
321
322   for(auto ix = width; --ix >= 0;)
323   {
324     for(auto iy = height; --iy >= 0; ++from)
325     {
326       *to = *from;
327       to += width;
328     }
329     to += hw;
330   }
331 }
332
333 template<size_t N>
334 void Rotate180(PixelBuffer* buffer, int width, int height)
335 {
336   using PixelT = PixelType<N>;
337   Vector<PixelT> data;
338   data.Resize(width * height);
339   auto dataPtr = data.Begin();
340
341   auto original = reinterpret_cast<PixelT*>(buffer);
342   std::copy(original, original + width * height, dataPtr);
343
344   auto to = original;
345   for( auto iy = 0; iy < width; iy++ )
346   {
347     for( auto ix = 0; ix < height; ix++ )
348     {
349       auto from = dataPtr + (height - ix) * width - 1 - iy;
350       *to = *from;
351       ++to;
352     }
353   }
354 }
355
356
357 template<size_t N>
358 void Rotate270(PixelBuffer* buffer, int width, int height)
359 {
360   using PixelT = PixelType<N>;
361   Vector<PixelT> data;
362   data.Resize(width * height);
363   auto dataPtr = data.Begin();
364
365   auto original = reinterpret_cast<PixelT*>(buffer);
366   std::copy(original, original + width * height, dataPtr);
367
368   auto w = height;
369   std::swap(width, height);
370   auto hw = width * height;
371
372   auto* to = original + hw  - width;
373   auto* from = dataPtr;
374
375   w = -w;
376   hw =  hw + 1;
377   for(auto ix = width; --ix >= 0;)
378   {
379     for(auto iy = height; --iy >= 0;)
380     {
381       *to = *from;
382       ++from;
383       to += w;
384     }
385     to += hw;
386   }
387 }
388
389 } // namespace
390
391 namespace Dali
392 {
393
394 namespace TizenPlatform
395 {
396
397 JpegTransform ConvertExifOrientation(ExifData* exifData);
398 bool TransformSize( int requiredWidth, int requiredHeight,
399                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
400                     JpegTransform transform,
401                     int& preXformImageWidth, int& preXformImageHeight,
402                     int& postXformImageWidth, int& postXformImageHeight );
403
404 bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
405 {
406   // using libjpeg API to avoid having to read the whole file in a buffer
407   struct jpeg_decompress_struct cinfo;
408   struct JpegErrorState jerr;
409   cinfo.err = jpeg_std_error( &jerr.errorManager );
410
411   jerr.errorManager.output_message = JpegOutputMessageHandler;
412   jerr.errorManager.error_exit = JpegErrorHandler;
413
414   // On error exit from the JPEG lib, control will pass via JpegErrorHandler
415   // into this branch body for cleanup and error return:
416   if(setjmp(jerr.jumpBuffer))
417   {
418     jpeg_destroy_decompress(&cinfo);
419     return false;
420   }
421
422 // jpeg_create_decompress internally uses C casts
423 #pragma GCC diagnostic push
424 #pragma GCC diagnostic ignored "-Wold-style-cast"
425   jpeg_create_decompress( &cinfo );
426 #pragma GCC diagnostic pop
427
428   jpeg_stdio_src( &cinfo, fp );
429
430   // Check header to see if it is  JPEG file
431   if( jpeg_read_header( &cinfo, TRUE ) != JPEG_HEADER_OK )
432   {
433     width = height = 0;
434     jpeg_destroy_decompress( &cinfo );
435     return false;
436   }
437
438   width = cinfo.image_width;
439   height = cinfo.image_height;
440
441   jpeg_destroy_decompress( &cinfo );
442   return true;
443 }
444
445 bool LoadBitmapFromJpeg( const ImageLoader::Input& input, Integration::Bitmap& bitmap )
446 {
447   const int flags= 0;
448   FILE* const fp = input.file;
449
450   if( fseek(fp,0,SEEK_END) )
451   {
452     DALI_LOG_ERROR("Error seeking to end of file\n");
453     return false;
454   }
455
456   long positionIndicator = ftell(fp);
457   unsigned int jpegBufferSize = 0u;
458   if( positionIndicator > -1L )
459   {
460     jpegBufferSize = static_cast<unsigned int>(positionIndicator);
461   }
462
463   if( 0u == jpegBufferSize )
464   {
465     return false;
466   }
467
468   if( fseek(fp, 0, SEEK_SET) )
469   {
470     DALI_LOG_ERROR("Error seeking to start of file\n");
471     return false;
472   }
473
474   Vector<unsigned char> jpegBuffer;
475   try
476   {
477     jpegBuffer.Resize( jpegBufferSize );
478   }
479   catch(...)
480   {
481     DALI_LOG_ERROR( "Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U );
482     return false;
483   }
484   unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
485
486   // Pull the compressed JPEG image bytes out of a file and into memory:
487   if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
488   {
489     DALI_LOG_WARNING("Error on image file read.\n");
490     return false;
491   }
492
493   if( fseek(fp, 0, SEEK_SET) )
494   {
495     DALI_LOG_ERROR("Error seeking to start of file\n");
496   }
497
498   auto jpeg = MakeJpegDecompressor();
499
500   if(!jpeg)
501   {
502     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
503     return false;
504   }
505
506   auto transform = JpegTransform::NONE;
507
508   if( input.reorientationRequested )
509   {
510     auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
511     if( exifData )
512     {
513       transform = ConvertExifOrientation(exifData.get());
514     }
515   }
516
517   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
518   int chrominanceSubsampling = -1;
519   int preXformImageWidth = 0, preXformImageHeight = 0;
520
521   // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
522   // Temporarily separate Ubuntu and other profiles.
523 #ifndef DALI_PROFILE_UBUNTU
524   int jpegColorspace = -1;
525   if( tjDecompressHeader3( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace ) == -1 )
526   {
527     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
528     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
529   }
530 #else
531   if( tjDecompressHeader2( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
532   {
533     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
534     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
535   }
536 #endif
537
538   if(preXformImageWidth == 0 || preXformImageHeight == 0)
539   {
540     DALI_LOG_WARNING("Invalid Image!\n");
541     return false;
542   }
543
544   int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
545   int requiredHeight = input.scalingParameters.dimensions.GetHeight();
546
547   // If transform is a 90 or 270 degree rotation, the logical width and height
548   // request from the client needs to be adjusted to account by effectively
549   // rotating that too, and the final width and height need to be swapped:
550   int postXformImageWidth = preXformImageWidth;
551   int postXformImageHeight = preXformImageHeight;
552
553
554   int scaledPreXformWidth   = preXformImageWidth;
555   int scaledPreXformHeight  = preXformImageHeight;
556   int scaledPostXformWidth  = postXformImageWidth;
557   int scaledPostXformHeight = postXformImageHeight;
558
559   TransformSize( requiredWidth, requiredHeight,
560                  input.scalingParameters.scalingMode,
561                  input.scalingParameters.samplingMode,
562                  transform,
563                  scaledPreXformWidth, scaledPreXformHeight,
564                  scaledPostXformWidth, scaledPostXformHeight );
565
566
567   // Colorspace conversion options
568   TJPF pixelLibJpegType = TJPF_RGB;
569   Pixel::Format pixelFormat = Pixel::RGB888;
570 #ifndef DALI_PROFILE_UBUNTU
571   switch (jpegColorspace)
572   {
573     case TJCS_RGB:
574     // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
575     // YCbCr images must be converted to RGB before they can actually be displayed.
576     case TJCS_YCbCr:
577     {
578       pixelLibJpegType = TJPF_RGB;
579       pixelFormat = Pixel::RGB888;
580       break;
581     }
582     case TJCS_GRAY:
583     {
584       pixelLibJpegType = TJPF_GRAY;
585       pixelFormat = Pixel::L8;
586       break;
587     }
588     case TJCS_CMYK:
589     case TJCS_YCCK:
590     {
591       pixelLibJpegType = TJPF_CMYK;
592       pixelFormat = Pixel::RGBA8888;
593       break;
594     }
595     default:
596     {
597       pixelLibJpegType = TJPF_RGB;
598       pixelFormat = Pixel::RGB888;
599       break;
600     }
601   }
602 #endif
603   // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
604   PixelBuffer* bitmapPixelBuffer =  bitmap.GetPackedPixelsProfile()->ReserveBuffer( pixelFormat, scaledPostXformWidth, scaledPostXformHeight ) ;
605
606   if( tjDecompress2( jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>( bitmapPixelBuffer ), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags ) == -1 )
607   {
608     std::string errorString = tjGetErrorStr();
609
610     if( IsJpegErrorFatal( errorString ) )
611     {
612         DALI_LOG_ERROR("%s\n", errorString.c_str() );
613         return false;
614     }
615     else
616     {
617         DALI_LOG_WARNING("%s\n", errorString.c_str() );
618     }
619   }
620
621   const unsigned int  bufferWidth  = GetTextureDimension( scaledPreXformWidth );
622   const unsigned int  bufferHeight = GetTextureDimension( scaledPreXformHeight );
623
624   bool result = false;
625   switch(transform)
626   {
627     case JpegTransform::NONE:
628     {
629       result = true;
630       break;
631     }
632     // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
633     case JpegTransform::ROTATE_180:
634     {
635       static auto rotate180Functions = TransformFunctionArray {
636         &Rotate180<1>,
637         &Rotate180<3>,
638         &Rotate180<4>,
639       };
640       result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
641       break;
642     }
643     case JpegTransform::ROTATE_270:
644     {
645       static auto rotate270Functions = TransformFunctionArray {
646         &Rotate270<1>,
647         &Rotate270<3>,
648         &Rotate270<4>,
649       };
650       result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
651       break;
652     }
653     case JpegTransform::ROTATE_90:
654     {
655       static auto rotate90Functions = TransformFunctionArray {
656         &Rotate90<1>,
657         &Rotate90<3>,
658         &Rotate90<4>,
659       };
660       result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
661       break;
662     }
663     case JpegTransform::FLIP_VERTICAL:
664     {
665       static auto flipVerticalFunctions = TransformFunctionArray {
666         &FlipVertical<1>,
667         &FlipVertical<3>,
668         &FlipVertical<4>,
669       };
670       result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
671       break;
672     }
673     // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
674     case JpegTransform::FLIP_HORIZONTAL:
675     {
676       static auto flipHorizontalFunctions = TransformFunctionArray {
677         &FlipHorizontal<1>,
678         &FlipHorizontal<3>,
679         &FlipHorizontal<4>,
680       };
681       result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
682       break;
683     }
684     case JpegTransform::TRANSPOSE:
685     {
686       static auto transposeFunctions = TransformFunctionArray {
687         &Transpose<1>,
688         &Transpose<3>,
689         &Transpose<4>,
690       };
691       result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
692       break;
693     }
694     case JpegTransform::TRANSVERSE:
695     {
696       static auto transverseFunctions = TransformFunctionArray {
697         &Transverse<1>,
698         &Transverse<3>,
699         &Transverse<4>,
700       };
701       result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
702       break;
703     }
704     default:
705     {
706       DALI_LOG_ERROR( "Unsupported JPEG Orientation transformation: %x.\n", transform );
707       break;
708     }
709   }
710   return result;
711 }
712
713 bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char >& encodedPixels,
714                    const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality )
715 {
716
717   if( !pixelBuffer )
718   {
719     DALI_LOG_ERROR("Null input buffer\n");
720     return false;
721   }
722
723   // Translate pixel format enum:
724   int jpegPixelFormat = -1;
725
726   switch( pixelFormat )
727   {
728     case Pixel::RGB888:
729     {
730       jpegPixelFormat = TJPF_RGB;
731       break;
732     }
733     case Pixel::RGBA8888:
734     {
735       // Ignore the alpha:
736       jpegPixelFormat = TJPF_RGBX;
737       break;
738     }
739     case Pixel::BGRA8888:
740     {
741       // Ignore the alpha:
742       jpegPixelFormat = TJPF_BGRX;
743       break;
744     }
745     default:
746     {
747       DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
748       return false;
749     }
750   }
751
752   // Assert quality is in the documented allowable range of the jpeg-turbo lib:
753   DALI_ASSERT_DEBUG( quality >= 1 );
754   DALI_ASSERT_DEBUG( quality <= 100 );
755   if( quality < 1 )
756   {
757     quality = 1;
758   }
759   if( quality > 100 )
760   {
761     quality = 100;
762   }
763
764   // Initialise a JPEG codec:
765   {
766     auto jpeg = MakeJpegCompressor();
767     if( jpeg )
768     {
769       DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
770       return false;
771     }
772
773
774     // Safely wrap the jpeg codec's buffer in case we are about to throw, then
775     // save the pixels to a persistent buffer that we own and let our cleaner
776     // class clean up the buffer as it goes out of scope:
777     auto dstBuffer = MakeJpegMemory();
778
779     // Run the compressor:
780     unsigned long dstBufferSize = 0;
781     const int flags = 0;
782
783     if( tjCompress2( jpeg.get(),
784                      const_cast<unsigned char*>(pixelBuffer),
785                      width, 0, height,
786                      jpegPixelFormat, SetPointer(dstBuffer), &dstBufferSize,
787                      TJSAMP_444, quality, flags ) )
788     {
789       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
790       return false;
791     }
792
793     encodedPixels.Resize( dstBufferSize );
794     memcpy( encodedPixels.Begin(), dstBuffer.get(), dstBufferSize );
795   }
796   return true;
797 }
798
799
800 JpegTransform ConvertExifOrientation(ExifData* exifData)
801 {
802   auto transform = JpegTransform::NONE;
803   ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
804   int orientation = 0;
805   if( entry )
806   {
807     orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
808     switch( orientation )
809     {
810       case 1:
811       {
812         transform = JpegTransform::NONE;
813         break;
814       }
815       case 2:
816       {
817         transform = JpegTransform::FLIP_HORIZONTAL;
818         break;
819       }
820       case 3:
821       {
822         transform = JpegTransform::FLIP_VERTICAL;
823         break;
824       }
825       case 4:
826       {
827         transform = JpegTransform::TRANSPOSE;
828         break;
829       }
830       case 5:
831       {
832         transform = JpegTransform::TRANSVERSE;
833         break;
834       }
835       case 6:
836       {
837         transform = JpegTransform::ROTATE_90;
838         break;
839       }
840       case 7:
841       {
842         transform = JpegTransform::ROTATE_180;
843         break;
844       }
845       case 8:
846       {
847         transform = JpegTransform::ROTATE_270;
848         break;
849       }
850       default:
851       {
852         // Try to keep loading the file, but let app developer know there was something fishy:
853         DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
854         break;
855       }
856     }
857   }
858   return transform;
859 }
860
861 bool TransformSize( int requiredWidth, int requiredHeight,
862                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
863                     JpegTransform transform,
864                     int& preXformImageWidth, int& preXformImageHeight,
865                     int& postXformImageWidth, int& postXformImageHeight )
866 {
867   bool success = true;
868
869   if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::ROTATE_180 || transform == JpegTransform::TRANSVERSE)
870   {
871     std::swap( requiredWidth, requiredHeight );
872     std::swap( postXformImageWidth, postXformImageHeight );
873   }
874
875   // Apply the special rules for when there are one or two zeros in requested dimensions:
876   const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
877   requiredWidth = correctedDesired.GetWidth();
878   requiredHeight = correctedDesired.GetHeight();
879
880   // Rescale image during decode using one of the decoder's built-in rescaling
881   // ratios (expected to be powers of 2), keeping the final image at least as
882   // wide and high as was requested:
883
884   int numFactors = 0;
885   tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
886   if( factors == NULL )
887   {
888     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
889     success = false;
890   }
891   else
892   {
893     // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
894     // apply it if the application requested one of those:
895     // (use a switch case here so this code will fail to compile if other modes are added)
896     bool downscale = true;
897     switch( samplingMode )
898     {
899       case SamplingMode::BOX:
900       case SamplingMode::BOX_THEN_NEAREST:
901       case SamplingMode::BOX_THEN_LINEAR:
902       case SamplingMode::DONT_CARE:
903       {
904         downscale = true;
905         break;
906       }
907       case SamplingMode::NO_FILTER:
908       case SamplingMode::NEAREST:
909       case SamplingMode::LINEAR:
910       {
911         downscale = false;
912         break;
913       }
914     }
915
916     int scaleFactorIndex( 0 );
917     if( downscale )
918     {
919       // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
920       for( int i = 1; i < numFactors; ++i )
921       {
922         bool widthLessRequired  = TJSCALED( postXformImageWidth,  factors[i]) < requiredWidth;
923         bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
924         // If either scaled dimension is smaller than the desired one, we were done at the last iteration
925         if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
926         {
927           break;
928         }
929         // If both dimensions are smaller than the desired one, we were done at the last iteration:
930         if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
931         {
932           break;
933         }
934         // If the width is smaller than the desired one, we were done at the last iteration:
935         if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
936         {
937           break;
938         }
939         // If the width is smaller than the desired one, we were done at the last iteration:
940         if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
941         {
942           break;
943         }
944         // This factor stays is within our fitting mode constraint so remember it:
945         scaleFactorIndex = i;
946       }
947     }
948
949     // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
950     for( int i = scaleFactorIndex; i < numFactors; ++i )
951     {
952       // Continue downscaling to below maximum texture size (if possible)
953       scaleFactorIndex = i;
954
955       if( TJSCALED(postXformImageWidth,  (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) &&
956           TJSCALED(postXformImageHeight, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) )
957       {
958         // Current scale-factor downscales to below maximum texture size
959         break;
960       }
961     }
962
963     // We have finally chosen the scale-factor, return width/height values
964     if( scaleFactorIndex > 0 )
965     {
966       preXformImageWidth   = TJSCALED(preXformImageWidth,   (factors[scaleFactorIndex]));
967       preXformImageHeight  = TJSCALED(preXformImageHeight,  (factors[scaleFactorIndex]));
968       postXformImageWidth  = TJSCALED(postXformImageWidth,  (factors[scaleFactorIndex]));
969       postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
970     }
971   }
972
973   return success;
974 }
975
976 ExifHandle LoadExifData( FILE* fp )
977 {
978   auto exifData = MakeNullExifData();
979   unsigned char dataBuffer[1024];
980
981   if( fseek( fp, 0, SEEK_SET ) )
982   {
983     DALI_LOG_ERROR("Error seeking to start of file\n");
984   }
985   else
986   {
987     auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
988         exif_loader_new(), exif_loader_unref };
989
990     while( !feof(fp) )
991     {
992       int size = fread( dataBuffer, 1, sizeof( dataBuffer ), fp );
993       if( size <= 0 )
994       {
995         break;
996       }
997       if( ! exif_loader_write( exifLoader.get(), dataBuffer, size ) )
998       {
999         break;
1000       }
1001     }
1002
1003     exifData.reset( exif_loader_get_data( exifLoader.get() ) );
1004   }
1005
1006   return exifData;
1007 }
1008
1009 bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsigned int& height )
1010 {
1011   unsigned int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
1012   unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1013   FILE* const fp = input.file;
1014
1015   bool success = false;
1016   if( requiredWidth == 0 && requiredHeight == 0 )
1017   {
1018     success = LoadJpegHeader( fp, width, height );
1019   }
1020   else
1021   {
1022     // Double check we get the same width/height from the header
1023     unsigned int headerWidth;
1024     unsigned int headerHeight;
1025     if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
1026     {
1027       auto transform = JpegTransform::NONE;
1028
1029       if( input.reorientationRequested )
1030       {
1031         auto exifData = LoadExifData( fp );
1032         if( exifData )
1033         {
1034           transform = ConvertExifOrientation(exifData.get());
1035         }
1036
1037         int preXformImageWidth = headerWidth;
1038         int preXformImageHeight = headerHeight;
1039         int postXformImageWidth = headerWidth;
1040         int postXformImageHeight = headerHeight;
1041
1042         success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
1043         if(success)
1044         {
1045           width = postXformImageWidth;
1046           height = postXformImageHeight;
1047         }
1048       }
1049       else
1050       {
1051         success = true;
1052         width = headerWidth;
1053         height = headerHeight;
1054       }
1055     }
1056   }
1057   return success;
1058 }
1059
1060
1061 } // namespace TizenPlatform
1062
1063 } // namespace Dali