[Tizen] Support YUV decoding for JPEG
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-jpeg-turbo.cpp
1 /*
2  * Copyright (c) 2022 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 <dali/internal/imaging/common/loader-jpeg.h>
20
21 // EXTERNAL HEADERS
22 #include <jpeglib.h>
23 #include <libexif/exif-data.h>
24 #include <libexif/exif-loader.h>
25 #include <libexif/exif-tag.h>
26 #include <setjmp.h>
27 #include <stdlib.h>
28 #include <turbojpeg.h>
29 #include <array>
30 #include <cstring>
31 #include <functional>
32 #include <memory>
33 #include <utility>
34
35 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
36 #include <dali/public-api/object/property-array.h>
37 #include <dali/public-api/object/property-map.h>
38
39 // INTERNAL HEADERS
40 #include <dali/devel-api/adaptor-framework/environment-variable.h>
41 #include <dali/devel-api/adaptor-framework/image-loading.h>
42 #include <dali/internal/imaging/common/image-operations.h>
43 #include <dali/internal/imaging/common/pixel-buffer-impl.h>
44 #include <dali/internal/legacy/tizen/platform-capabilities.h>
45
46 namespace
47 {
48 using Dali::Vector;
49 namespace Pixel                     = Dali::Pixel;
50 using PixelArray                    = unsigned char*;
51 const unsigned int DECODED_L8       = 1;
52 const unsigned int DECODED_RGB888   = 3;
53 const unsigned int DECODED_RGBA8888 = 4;
54
55 constexpr auto DECODE_JPEG_TO_YUV_ENV = "DALI_DECODE_JPEG_TO_YUV_ENV";
56
57 /** Transformations that can be applied to decoded pixels to respect exif orientation
58   *  codes in image headers */
59 enum class JpegTransform
60 {
61   NONE,            //< no transformation 0th-Row = top & 0th-Column = left
62   FLIP_HORIZONTAL, //< horizontal flip 0th-Row = top & 0th-Column = right
63   ROTATE_180,      //< 180-degree rotation   0th-Row = bottom & 0th-Column = right
64   FLIP_VERTICAL,   //< vertical flip  0th-Row = bottom & 0th-Column = left
65   TRANSPOSE,       //< transpose across UL-to-LR axis  0th-Row = left   & 0th-Column = top
66   ROTATE_90,       //< 90-degree clockwise rotation  0th-Row = right  & 0th-Column = top
67   TRANSVERSE,      //< transpose across UR-to-LL axis  0th-Row = right  & 0th-Column = bottom
68   ROTATE_270,      //< 270-degree clockwise (or 90 ccw) 0th-Row = left  & 0th-Column = bottom
69 };
70
71 /**
72   * @brief Error handling bookeeping for the JPEG Turbo library's
73   * setjmp/longjmp simulated exceptions.
74   */
75 struct JpegErrorState
76 {
77   struct jpeg_error_mgr errorManager;
78   jmp_buf               jumpBuffer;
79 };
80
81 /**
82   * @brief Called by the JPEG library when it hits an error.
83   * We jump out of the library so our loader code can return an error.
84   */
85 void JpegErrorHandler(j_common_ptr cinfo)
86 {
87   DALI_LOG_ERROR("JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n");
88   /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
89   JpegErrorState* myerr = reinterpret_cast<JpegErrorState*>(cinfo->err);
90
91   /* Return control to the setjmp point */
92   longjmp(myerr->jumpBuffer, 1);
93 }
94
95 void JpegOutputMessageHandler(j_common_ptr cinfo)
96 {
97   /* Stop libjpeg from printing to stderr - Do Nothing */
98 }
99
100 /**
101   * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
102   * the JPEG to be displayed and fatal errors.
103   */
104 bool IsJpegErrorFatal(const std::string& errorMessage)
105 {
106   if((errorMessage.find("Corrupt JPEG data") != std::string::npos) ||
107      (errorMessage.find("Invalid SOS parameters") != std::string::npos) ||
108      (errorMessage.find("Invalid JPEG file structure") != std::string::npos) ||
109      (errorMessage.find("Unsupported JPEG process") != std::string::npos) ||
110      (errorMessage.find("Unsupported marker type") != std::string::npos) ||
111      (errorMessage.find("Bogus marker length") != std::string::npos) ||
112      (errorMessage.find("Bogus DQT index") != std::string::npos) ||
113      (errorMessage.find("Bogus Huffman table definition") != std::string::npos))
114   {
115     return false;
116   }
117   return true;
118 }
119
120 // helpers for safe exif memory handling
121 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
122
123 ExifHandle MakeNullExifData()
124 {
125   return ExifHandle{nullptr, exif_data_free};
126 }
127
128 ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
129 {
130   return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
131 }
132
133 // Helpers for safe Jpeg memory handling
134 using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
135
136 JpegHandle MakeJpegCompressor()
137 {
138   return JpegHandle{tjInitCompress(), tjDestroy};
139 }
140
141 JpegHandle MakeJpegDecompressor()
142 {
143   return JpegHandle{tjInitDecompress(), tjDestroy};
144 }
145
146 using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
147
148 JpegMemoryHandle MakeJpegMemory()
149 {
150   return JpegMemoryHandle{nullptr, tjFree};
151 }
152
153 template<class T, class Deleter>
154 class UniquePointerSetter final
155 {
156 public:
157   UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
158   : mUniquePointer(uniquePointer),
159     mRawPointer(nullptr)
160   {
161   }
162
163   /// @brief Pointer to Pointer cast operator
164   operator T**()
165   {
166     return &mRawPointer;
167   }
168
169   /// @brief Destructor, reset the unique_ptr
170   ~UniquePointerSetter()
171   {
172     mUniquePointer.reset(mRawPointer);
173   }
174
175 private:
176   std::unique_ptr<T, Deleter>& mUniquePointer;
177   T*                           mRawPointer;
178 };
179
180 template<typename T, typename Deleter>
181 UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
182 {
183   return UniquePointerSetter<T, Deleter>{uniquePointer};
184 }
185
186 using TransformFunction      = std::function<void(PixelArray, unsigned, unsigned)>;
187 using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
188
189 /// @brief Select the transform function depending on the pixel format
190 TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
191                                        Pixel::Format                 pixelFormat)
192 {
193   auto function = TransformFunction{};
194
195   int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
196   switch(decodedPixelSize)
197   {
198     case DECODED_L8:
199     {
200       function = functions[0];
201       break;
202     }
203     case DECODED_RGB888:
204     {
205       function = functions[1];
206       break;
207     }
208     case DECODED_RGBA8888:
209     {
210       function = functions[2];
211       break;
212     }
213     default:
214     {
215       DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
216       function = functions[1];
217       break;
218     }
219   }
220   return function;
221 }
222
223 // Storing Exif fields as properties
224 template<class R, class V>
225 R ConvertExifNumeric(const ExifEntry& entry)
226 {
227   return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
228 }
229
230 void AddExifFieldPropertyMap(Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd)
231 {
232   auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd));
233   switch(entry.format)
234   {
235     case EXIF_FORMAT_ASCII:
236     {
237       out.Insert(shortName, std::string(reinterpret_cast<char*>(entry.data), entry.size));
238       break;
239     }
240     case EXIF_FORMAT_SHORT:
241     {
242       out.Insert(shortName, ConvertExifNumeric<int, uint16_t>(entry));
243       break;
244     }
245     case EXIF_FORMAT_LONG:
246     {
247       out.Insert(shortName, ConvertExifNumeric<int, uint32_t>(entry));
248       break;
249     }
250     case EXIF_FORMAT_SSHORT:
251     {
252       out.Insert(shortName, ConvertExifNumeric<int, int16_t>(entry));
253       break;
254     }
255     case EXIF_FORMAT_SLONG:
256     {
257       out.Insert(shortName, ConvertExifNumeric<int, int32_t>(entry));
258       break;
259     }
260     case EXIF_FORMAT_FLOAT:
261     {
262       out.Insert(shortName, ConvertExifNumeric<float, float>(entry));
263       break;
264     }
265     case EXIF_FORMAT_DOUBLE:
266     {
267       out.Insert(shortName, ConvertExifNumeric<float, double>(entry));
268       break;
269     }
270     case EXIF_FORMAT_RATIONAL:
271     {
272       auto                  values = reinterpret_cast<unsigned int*>(entry.data);
273       Dali::Property::Array array;
274       array.Add(static_cast<int>(values[0]));
275       array.Add(static_cast<int>(values[1]));
276       out.Insert(shortName, array);
277       break;
278     }
279     case EXIF_FORMAT_SBYTE:
280     {
281       out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
282       break;
283     }
284     case EXIF_FORMAT_BYTE:
285     {
286       out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
287       break;
288     }
289     case EXIF_FORMAT_SRATIONAL:
290     {
291       auto                  values = reinterpret_cast<int*>(entry.data);
292       Dali::Property::Array array;
293       array.Add(values[0]);
294       array.Add(values[1]);
295       out.Insert(shortName, array);
296       break;
297     }
298     case EXIF_FORMAT_UNDEFINED:
299     default:
300     {
301       std::stringstream ss;
302       ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
303       out.Insert(shortName, ss.str());
304     }
305   }
306 }
307
308 /// @brief Apply a transform to a buffer
309 bool Transform(const TransformFunctionArray& transformFunctions,
310                PixelArray                    buffer,
311                int                           width,
312                int                           height,
313                Pixel::Format                 pixelFormat)
314 {
315   auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
316   if(transformFunction)
317   {
318     transformFunction(buffer, width, height);
319   }
320   return bool(transformFunction);
321 }
322
323 /// @brief Auxiliar type to represent pixel data with different number of bytes
324 template<size_t N>
325 struct PixelType
326 {
327   char _[N];
328 };
329
330 template<size_t N>
331 void Rotate180(PixelArray buffer, int width, int height)
332 {
333   // Destination pixel, set as the first pixel of screen
334   auto to = reinterpret_cast<PixelType<N>*>(buffer);
335
336   // Source pixel, as the image is flipped horizontally and vertically,
337   // the source pixel is the end of the buffer of size width * height
338   auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
339
340   for(auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
341   {
342     std::swap(*from, *to);
343   }
344 }
345
346 template<size_t N>
347 void FlipHorizontal(PixelArray buffer, int width, int height)
348 {
349   for(auto iy = 0; iy < height; ++iy)
350   {
351     //Set the destination pixel as the beginning of the row
352     auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
353     //Set the source pixel as the end of the row to flip in X axis
354     auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
355     for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
356     {
357       std::swap(*from, *to);
358     }
359   }
360 }
361
362 template<size_t N>
363 void FlipVertical(PixelArray buffer, int width, int height)
364 {
365   //Transform vertically only
366   for(auto iy = 0; iy < height / 2; ++iy)
367   {
368     for(auto ix = 0; ix < width; ++ix)
369     {
370       auto to   = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
371       auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
372       std::swap(*from, *to);
373     }
374   }
375 }
376
377 template<size_t N>
378 void Transpose(PixelArray buffer, int width, int height)
379 {
380   using PixelT = PixelType<N>;
381   Vector<PixelT> data;
382   data.Resize(width * height);
383   auto dataPtr = data.Begin();
384
385   auto original = reinterpret_cast<PixelT*>(buffer);
386   std::copy(original, original + width * height, dataPtr);
387
388   auto to = original;
389   for(auto iy = 0; iy < width; ++iy)
390   {
391     for(auto ix = 0; ix < height; ++ix, ++to)
392     {
393       auto from = dataPtr + ix * width + iy;
394       *to       = *from;
395     }
396   }
397 }
398
399 template<size_t N>
400 void Rotate90(PixelArray buffer, int width, int height)
401 {
402   using PixelT = PixelType<N>;
403   Vector<PixelT> data;
404   data.Resize(width * height);
405   auto dataPtr = data.Begin();
406
407   auto original = reinterpret_cast<PixelT*>(buffer);
408   std::copy(original, original + width * height, dataPtr);
409
410   std::swap(width, height);
411   auto hw = width * height;
412   hw      = -hw - 1;
413
414   auto to   = original + width - 1;
415   auto from = dataPtr;
416
417   for(auto ix = width; --ix >= 0;)
418   {
419     for(auto iy = height; --iy >= 0; ++from)
420     {
421       *to = *from;
422       to += width;
423     }
424     to += hw;
425   }
426 }
427
428 template<size_t N>
429 void Transverse(PixelArray buffer, int width, int height)
430 {
431   using PixelT = PixelType<N>;
432   Vector<PixelT> data;
433   data.Resize(width * height);
434   auto dataPtr = data.Begin();
435
436   auto original = reinterpret_cast<PixelT*>(buffer);
437   std::copy(original, original + width * height, dataPtr);
438
439   auto to = original;
440   for(auto iy = 0; iy < width; iy++)
441   {
442     for(auto ix = 0; ix < height; ix++)
443     {
444       auto from = dataPtr + (height - ix) * width - 1 - iy;
445       *to       = *from;
446       ++to;
447     }
448   }
449 }
450
451 template<size_t N>
452 void Rotate270(PixelArray buffer, int width, int height)
453 {
454   using PixelT = PixelType<N>;
455   Vector<PixelT> data;
456   data.Resize(width * height);
457   auto dataPtr = data.Begin();
458
459   auto original = reinterpret_cast<PixelT*>(buffer);
460   std::copy(original, original + width * height, dataPtr);
461
462   auto w = height;
463   std::swap(width, height);
464   auto hw = width * height;
465
466   auto* to   = original + hw - width;
467   auto* from = dataPtr;
468
469   w  = -w;
470   hw = hw + 1;
471   for(auto ix = width; --ix >= 0;)
472   {
473     for(auto iy = height; --iy >= 0;)
474     {
475       *to = *from;
476       ++from;
477       to += w;
478     }
479     to += hw;
480   }
481 }
482
483 } // namespace
484
485 namespace Dali
486 {
487 namespace TizenPlatform
488 {
489 JpegTransform ConvertExifOrientation(ExifData* exifData);
490 bool          TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
491
492 bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
493 {
494   // using libjpeg API to avoid having to read the whole file in a buffer
495   struct jpeg_decompress_struct cinfo;
496   struct JpegErrorState         jerr;
497   cinfo.err = jpeg_std_error(&jerr.errorManager);
498
499   jerr.errorManager.output_message = JpegOutputMessageHandler;
500   jerr.errorManager.error_exit     = JpegErrorHandler;
501
502   // On error exit from the JPEG lib, control will pass via JpegErrorHandler
503   // into this branch body for cleanup and error return:
504   if(setjmp(jerr.jumpBuffer))
505   {
506     DALI_LOG_ERROR("setjmp failed\n");
507     jpeg_destroy_decompress(&cinfo);
508     return false;
509   }
510
511 // jpeg_create_decompress internally uses C casts
512 #pragma GCC diagnostic push
513 #pragma GCC diagnostic ignored "-Wold-style-cast"
514   jpeg_create_decompress(&cinfo);
515 #pragma GCC diagnostic pop
516
517   jpeg_stdio_src(&cinfo, fp);
518
519   // Check header to see if it is  JPEG file
520   if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
521   {
522     DALI_LOG_ERROR("jpeg_read_header failed\n");
523     width = height = 0;
524     jpeg_destroy_decompress(&cinfo);
525     return false;
526   }
527
528   width  = cinfo.image_width;
529   height = cinfo.image_height;
530
531   jpeg_destroy_decompress(&cinfo);
532   return true;
533 }
534
535 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
536 {
537   const int   flags = 0;
538   FILE* const fp    = input.file;
539
540   if(fseek(fp, 0, SEEK_END))
541   {
542     DALI_LOG_ERROR("Error seeking to end of file\n");
543     return false;
544   }
545
546   long         positionIndicator = ftell(fp);
547   unsigned int jpegBufferSize    = 0u;
548   if(positionIndicator > -1L)
549   {
550     jpegBufferSize = static_cast<unsigned int>(positionIndicator);
551   }
552
553   if(0u == jpegBufferSize)
554   {
555     DALI_LOG_ERROR("Jpeg buffer size error\n");
556     return false;
557   }
558
559   if(fseek(fp, 0, SEEK_SET))
560   {
561     DALI_LOG_ERROR("Error seeking to start of file\n");
562     return false;
563   }
564
565   Vector<unsigned char> jpegBuffer;
566   try
567   {
568     jpegBuffer.Resize(jpegBufferSize);
569   }
570   catch(...)
571   {
572     DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
573     return false;
574   }
575   unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
576
577   // Pull the compressed JPEG image bytes out of a file and into memory:
578   if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
579   {
580     DALI_LOG_ERROR("Error on image file read.\n");
581     return false;
582   }
583
584   if(fseek(fp, 0, SEEK_SET))
585   {
586     DALI_LOG_ERROR("Error seeking to start of file\n");
587   }
588
589   auto jpeg = MakeJpegDecompressor();
590
591   if(!jpeg)
592   {
593     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
594     return false;
595   }
596
597   auto transform = JpegTransform::NONE;
598
599   // extract exif data
600   auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
601
602   if(exifData && input.reorientationRequested)
603   {
604     transform = ConvertExifOrientation(exifData.get());
605   }
606
607   std::unique_ptr<Property::Map> exifMap;
608   exifMap.reset(new Property::Map());
609
610   for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
611   {
612     auto content = exifData->ifd[k];
613     for(auto i = 0u; i < content->count; ++i)
614     {
615       auto&&      tag       = content->entries[i];
616       const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
617       if(shortName)
618       {
619         AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
620       }
621     }
622   }
623
624   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
625   int chrominanceSubsampling = -1;
626   int preXformImageWidth = 0, preXformImageHeight = 0;
627
628   // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
629   // Temporarily separate Ubuntu and other profiles.
630 #ifndef DALI_PROFILE_UBUNTU
631   int jpegColorspace = -1;
632   if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
633   {
634     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
635     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
636   }
637 #else
638   if(tjDecompressHeader2(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling) == -1)
639   {
640     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
641     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
642   }
643 #endif
644
645   if(preXformImageWidth == 0 || preXformImageHeight == 0)
646   {
647     DALI_LOG_ERROR("Invalid Image!\n");
648     return false;
649   }
650
651   int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
652   int requiredHeight = input.scalingParameters.dimensions.GetHeight();
653
654   // If transform is a 90 or 270 degree rotation, the logical width and height
655   // request from the client needs to be adjusted to account by effectively
656   // rotating that too, and the final width and height need to be swapped:
657   int postXformImageWidth  = preXformImageWidth;
658   int postXformImageHeight = preXformImageHeight;
659
660   int scaledPreXformWidth   = preXformImageWidth;
661   int scaledPreXformHeight  = preXformImageHeight;
662   int scaledPostXformWidth  = postXformImageWidth;
663   int scaledPostXformHeight = postXformImageHeight;
664
665   TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
666
667   // Colorspace conversion options
668   TJPF          pixelLibJpegType = TJPF_RGB;
669   Pixel::Format pixelFormat      = Pixel::RGB888;
670 #ifndef DALI_PROFILE_UBUNTU
671   switch(jpegColorspace)
672   {
673     case TJCS_RGB:
674     // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
675     // YCbCr images must be converted to RGB before they can actually be displayed.
676     case TJCS_YCbCr:
677     {
678       pixelLibJpegType = TJPF_RGB;
679       pixelFormat      = Pixel::RGB888;
680       break;
681     }
682     case TJCS_GRAY:
683     {
684       pixelLibJpegType = TJPF_GRAY;
685       pixelFormat      = Pixel::L8;
686       break;
687     }
688     case TJCS_CMYK:
689     case TJCS_YCCK:
690     {
691       pixelLibJpegType = TJPF_CMYK;
692       pixelFormat      = Pixel::RGBA8888;
693       break;
694     }
695     default:
696     {
697       pixelLibJpegType = TJPF_RGB;
698       pixelFormat      = Pixel::RGB888;
699       break;
700     }
701   }
702 #endif
703   // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
704   bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
705
706   // set metadata
707   GetImplementation(bitmap).SetMetadata(std::move(exifMap));
708
709   auto bitmapPixelBuffer = bitmap.GetBuffer();
710
711   if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
712   {
713     std::string errorString = tjGetErrorStr();
714
715     if(IsJpegErrorFatal(errorString))
716     {
717       DALI_LOG_ERROR("%s\n", errorString.c_str());
718       return false;
719     }
720     else
721     {
722       DALI_LOG_WARNING("%s\n", errorString.c_str());
723     }
724   }
725
726   const unsigned int bufferWidth  = GetTextureDimension(scaledPreXformWidth);
727   const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
728
729   bool result = false;
730   switch(transform)
731   {
732     case JpegTransform::NONE:
733     {
734       result = true;
735       break;
736     }
737     // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
738     case JpegTransform::ROTATE_180:
739     {
740       static auto rotate180Functions = TransformFunctionArray{
741         &Rotate180<1>,
742         &Rotate180<3>,
743         &Rotate180<4>,
744       };
745       result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
746       break;
747     }
748     case JpegTransform::ROTATE_270:
749     {
750       static auto rotate270Functions = TransformFunctionArray{
751         &Rotate270<1>,
752         &Rotate270<3>,
753         &Rotate270<4>,
754       };
755       result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
756       break;
757     }
758     case JpegTransform::ROTATE_90:
759     {
760       static auto rotate90Functions = TransformFunctionArray{
761         &Rotate90<1>,
762         &Rotate90<3>,
763         &Rotate90<4>,
764       };
765       result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
766       break;
767     }
768     case JpegTransform::FLIP_VERTICAL:
769     {
770       static auto flipVerticalFunctions = TransformFunctionArray{
771         &FlipVertical<1>,
772         &FlipVertical<3>,
773         &FlipVertical<4>,
774       };
775       result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
776       break;
777     }
778     // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
779     case JpegTransform::FLIP_HORIZONTAL:
780     {
781       static auto flipHorizontalFunctions = TransformFunctionArray{
782         &FlipHorizontal<1>,
783         &FlipHorizontal<3>,
784         &FlipHorizontal<4>,
785       };
786       result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
787       break;
788     }
789     case JpegTransform::TRANSPOSE:
790     {
791       static auto transposeFunctions = TransformFunctionArray{
792         &Transpose<1>,
793         &Transpose<3>,
794         &Transpose<4>,
795       };
796       result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
797       break;
798     }
799     case JpegTransform::TRANSVERSE:
800     {
801       static auto transverseFunctions = TransformFunctionArray{
802         &Transverse<1>,
803         &Transverse<3>,
804         &Transverse<4>,
805       };
806       result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
807       break;
808     }
809     default:
810     {
811       DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
812       break;
813     }
814   }
815
816   return result;
817 }
818
819 bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
820 {
821   const int   flags = 0;
822   FILE* const fp    = input.file;
823
824   if(fseek(fp, 0, SEEK_END))
825   {
826     DALI_LOG_ERROR("Error seeking to end of file\n");
827     return false;
828   }
829
830   long         positionIndicator = ftell(fp);
831   unsigned int jpegBufferSize    = 0u;
832   if(positionIndicator > -1L)
833   {
834     jpegBufferSize = static_cast<unsigned int>(positionIndicator);
835   }
836
837   if(0u == jpegBufferSize)
838   {
839     DALI_LOG_ERROR("Jpeg buffer size error\n");
840     return false;
841   }
842
843   if(fseek(fp, 0, SEEK_SET))
844   {
845     DALI_LOG_ERROR("Error seeking to start of file\n");
846     return false;
847   }
848
849   Vector<unsigned char> jpegBuffer;
850   try
851   {
852     jpegBuffer.Resize(jpegBufferSize);
853   }
854   catch(...)
855   {
856     DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
857     return false;
858   }
859   unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
860
861   // Pull the compressed JPEG image bytes out of a file and into memory:
862   if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
863   {
864     DALI_LOG_ERROR("Error on image file read.\n");
865     return false;
866   }
867
868   if(fseek(fp, 0, SEEK_SET))
869   {
870     DALI_LOG_ERROR("Error seeking to start of file\n");
871   }
872
873   auto jpeg = MakeJpegDecompressor();
874
875   if(!jpeg)
876   {
877     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
878     return false;
879   }
880
881   auto transform = JpegTransform::NONE;
882
883   // extract exif data
884   auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
885
886   if(exifData && input.reorientationRequested)
887   {
888     transform = ConvertExifOrientation(exifData.get());
889   }
890
891   std::unique_ptr<Property::Map> exifMap;
892   exifMap.reset(new Property::Map());
893
894   for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
895   {
896     auto content = exifData->ifd[k];
897     for(auto i = 0u; i < content->count; ++i)
898     {
899       auto&&      tag       = content->entries[i];
900       const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
901       if(shortName)
902       {
903         AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
904       }
905     }
906   }
907
908   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
909   int chrominanceSubsampling = -1;
910   int preXformImageWidth = 0, preXformImageHeight = 0;
911
912   int jpegColorspace = -1;
913   if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
914   {
915     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
916     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
917   }
918
919   if(preXformImageWidth == 0 || preXformImageHeight == 0)
920   {
921     DALI_LOG_ERROR("Invalid Image!\n");
922     return false;
923   }
924
925   int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
926   int requiredHeight = input.scalingParameters.dimensions.GetHeight();
927
928   // If transform is a 90 or 270 degree rotation, the logical width and height
929   // request from the client needs to be adjusted to account by effectively
930   // rotating that too, and the final width and height need to be swapped:
931   int postXformImageWidth  = preXformImageWidth;
932   int postXformImageHeight = preXformImageHeight;
933
934   int scaledPreXformWidth   = preXformImageWidth;
935   int scaledPreXformHeight  = preXformImageHeight;
936   int scaledPostXformWidth  = postXformImageWidth;
937   int scaledPostXformHeight = postXformImageHeight;
938
939   TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
940
941   auto decodeToYuvString = EnvironmentVariable::GetEnvironmentVariable(DECODE_JPEG_TO_YUV_ENV);
942   bool decodeToYuv       = decodeToYuvString ? std::atoi(decodeToYuvString) : false;
943   int  decodeResult      = -1;
944   bool result            = false;
945
946   // Now we support YUV420 only
947   if(decodeToYuv && chrominanceSubsampling == TJSAMP_420 && transform == JpegTransform::NONE)
948   {
949     unsigned char* planes[3];
950
951     // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
952     for(int i = 0; i < 3; i++)
953     {
954       auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
955
956       unsigned char* buffer = static_cast<unsigned char*>(malloc(planeSize));
957       if(!buffer)
958       {
959         DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
960         pixelBuffers.clear();
961         return false;
962       }
963
964       int width, height, planeWidth;
965
966       if(i == 0)
967       {
968         // luminance
969         width      = scaledPostXformWidth;
970         height     = scaledPostXformHeight;
971         planeWidth = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
972       }
973       else
974       {
975         width      = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
976         height     = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
977         planeWidth = width;
978       }
979
980       Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, Pixel::L8);
981       Dali::Devel::PixelBuffer          bitmap   = Devel::PixelBuffer(internal.Get());
982
983       planes[i] = buffer;
984
985       pixelBuffers.push_back(bitmap);
986     }
987
988     decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
989     if(decodeResult == -1)
990     {
991       std::string errorString = tjGetErrorStr();
992
993       if(IsJpegErrorFatal(errorString))
994       {
995         DALI_LOG_ERROR("%s\n", errorString.c_str());
996         pixelBuffers.clear();
997         return false;
998       }
999       else
1000       {
1001         DALI_LOG_WARNING("%s\n", errorString.c_str());
1002       }
1003     }
1004
1005     result = true;
1006   }
1007   else
1008   {
1009     // Colorspace conversion options
1010     TJPF          pixelLibJpegType = TJPF_RGB;
1011     Pixel::Format pixelFormat      = Pixel::RGB888;
1012
1013     switch(jpegColorspace)
1014     {
1015       case TJCS_RGB:
1016       // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
1017       // YCbCr images must be converted to RGB before they can actually be displayed.
1018       case TJCS_YCbCr:
1019       {
1020         pixelLibJpegType = TJPF_RGB;
1021         pixelFormat      = Pixel::RGB888;
1022         break;
1023       }
1024       case TJCS_GRAY:
1025       {
1026         pixelLibJpegType = TJPF_GRAY;
1027         pixelFormat      = Pixel::L8;
1028         break;
1029       }
1030       case TJCS_CMYK:
1031       case TJCS_YCCK:
1032       {
1033         pixelLibJpegType = TJPF_CMYK;
1034         pixelFormat      = Pixel::RGBA8888;
1035         break;
1036       }
1037       default:
1038       {
1039         pixelLibJpegType = TJPF_RGB;
1040         pixelFormat      = Pixel::RGB888;
1041         break;
1042       }
1043     }
1044
1045     // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
1046     Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
1047
1048     // set metadata
1049     GetImplementation(bitmap).SetMetadata(std::move(exifMap));
1050
1051     auto bitmapPixelBuffer = bitmap.GetBuffer();
1052
1053     decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
1054     if(decodeResult == -1)
1055     {
1056       std::string errorString = tjGetErrorStr();
1057
1058       if(IsJpegErrorFatal(errorString))
1059       {
1060         DALI_LOG_ERROR("%s\n", errorString.c_str());
1061         return false;
1062       }
1063       else
1064       {
1065         DALI_LOG_WARNING("%s\n", errorString.c_str());
1066       }
1067     }
1068     pixelBuffers.push_back(bitmap);
1069
1070     const unsigned int bufferWidth  = GetTextureDimension(scaledPreXformWidth);
1071     const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
1072
1073     switch(transform)
1074     {
1075       case JpegTransform::NONE:
1076       {
1077         result = true;
1078         break;
1079       }
1080       // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
1081       case JpegTransform::ROTATE_180:
1082       {
1083         static auto rotate180Functions = TransformFunctionArray{
1084           &Rotate180<1>,
1085           &Rotate180<3>,
1086           &Rotate180<4>,
1087         };
1088         result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1089         break;
1090       }
1091       case JpegTransform::ROTATE_270:
1092       {
1093         static auto rotate270Functions = TransformFunctionArray{
1094           &Rotate270<1>,
1095           &Rotate270<3>,
1096           &Rotate270<4>,
1097         };
1098         result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1099         break;
1100       }
1101       case JpegTransform::ROTATE_90:
1102       {
1103         static auto rotate90Functions = TransformFunctionArray{
1104           &Rotate90<1>,
1105           &Rotate90<3>,
1106           &Rotate90<4>,
1107         };
1108         result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1109         break;
1110       }
1111       case JpegTransform::FLIP_VERTICAL:
1112       {
1113         static auto flipVerticalFunctions = TransformFunctionArray{
1114           &FlipVertical<1>,
1115           &FlipVertical<3>,
1116           &FlipVertical<4>,
1117         };
1118         result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1119         break;
1120       }
1121       // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
1122       case JpegTransform::FLIP_HORIZONTAL:
1123       {
1124         static auto flipHorizontalFunctions = TransformFunctionArray{
1125           &FlipHorizontal<1>,
1126           &FlipHorizontal<3>,
1127           &FlipHorizontal<4>,
1128         };
1129         result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1130         break;
1131       }
1132       case JpegTransform::TRANSPOSE:
1133       {
1134         static auto transposeFunctions = TransformFunctionArray{
1135           &Transpose<1>,
1136           &Transpose<3>,
1137           &Transpose<4>,
1138         };
1139         result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1140         break;
1141       }
1142       case JpegTransform::TRANSVERSE:
1143       {
1144         static auto transverseFunctions = TransformFunctionArray{
1145           &Transverse<1>,
1146           &Transverse<3>,
1147           &Transverse<4>,
1148         };
1149         result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
1150         break;
1151       }
1152       default:
1153       {
1154         DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
1155         break;
1156       }
1157     }
1158   }
1159
1160   return result;
1161 }
1162
1163 bool EncodeToJpeg(const unsigned char* const pixelBuffer, Vector<unsigned char>& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality)
1164 {
1165   if(!pixelBuffer)
1166   {
1167     DALI_LOG_ERROR("Null input buffer\n");
1168     return false;
1169   }
1170
1171   // Translate pixel format enum:
1172   int jpegPixelFormat = -1;
1173
1174   switch(pixelFormat)
1175   {
1176     case Pixel::RGB888:
1177     {
1178       jpegPixelFormat = TJPF_RGB;
1179       break;
1180     }
1181     case Pixel::RGBA8888:
1182     {
1183       // Ignore the alpha:
1184       jpegPixelFormat = TJPF_RGBX;
1185       break;
1186     }
1187     case Pixel::BGRA8888:
1188     {
1189       // Ignore the alpha:
1190       jpegPixelFormat = TJPF_BGRX;
1191       break;
1192     }
1193     default:
1194     {
1195       DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG.\n");
1196       return false;
1197     }
1198   }
1199
1200   // Assert quality is in the documented allowable range of the jpeg-turbo lib:
1201   DALI_ASSERT_DEBUG(quality >= 1);
1202   DALI_ASSERT_DEBUG(quality <= 100);
1203   if(quality < 1)
1204   {
1205     quality = 1;
1206   }
1207   if(quality > 100)
1208   {
1209     quality = 100;
1210   }
1211
1212   // Initialise a JPEG codec:
1213   {
1214     auto jpeg = MakeJpegCompressor();
1215     if(!jpeg)
1216     {
1217       DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
1218       return false;
1219     }
1220
1221     // Safely wrap the jpeg codec's buffer in case we are about to throw, then
1222     // save the pixels to a persistent buffer that we own and let our cleaner
1223     // class clean up the buffer as it goes out of scope:
1224     auto dstBuffer = MakeJpegMemory();
1225
1226     // Run the compressor:
1227     unsigned long dstBufferSize = 0;
1228     const int     flags         = 0;
1229
1230     if(tjCompress2(jpeg.get(),
1231                    const_cast<unsigned char*>(pixelBuffer),
1232                    width,
1233                    0,
1234                    height,
1235                    jpegPixelFormat,
1236                    SetPointer(dstBuffer),
1237                    &dstBufferSize,
1238                    TJSAMP_444,
1239                    quality,
1240                    flags))
1241     {
1242       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
1243       return false;
1244     }
1245
1246     encodedPixels.Resize(dstBufferSize);
1247     memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
1248   }
1249   return true;
1250 }
1251
1252 JpegTransform ConvertExifOrientation(ExifData* exifData)
1253 {
1254   auto             transform   = JpegTransform::NONE;
1255   ExifEntry* const entry       = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
1256   int              orientation = 0;
1257   if(entry)
1258   {
1259     orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
1260     switch(orientation)
1261     {
1262       case 1:
1263       {
1264         transform = JpegTransform::NONE;
1265         break;
1266       }
1267       case 2:
1268       {
1269         transform = JpegTransform::FLIP_HORIZONTAL;
1270         break;
1271       }
1272       case 3:
1273       {
1274         transform = JpegTransform::ROTATE_180;
1275         break;
1276       }
1277       case 4:
1278       {
1279         transform = JpegTransform::FLIP_VERTICAL;
1280         break;
1281       }
1282       case 5:
1283       {
1284         transform = JpegTransform::TRANSPOSE;
1285         break;
1286       }
1287       case 6:
1288       {
1289         transform = JpegTransform::ROTATE_90;
1290         break;
1291       }
1292       case 7:
1293       {
1294         transform = JpegTransform::TRANSVERSE;
1295         break;
1296       }
1297       case 8:
1298       {
1299         transform = JpegTransform::ROTATE_270;
1300         break;
1301       }
1302       default:
1303       {
1304         // Try to keep loading the file, but let app developer know there was something fishy:
1305         DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
1306         break;
1307       }
1308     }
1309   }
1310   return transform;
1311 }
1312
1313 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
1314 {
1315   bool success = true;
1316   if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1317   {
1318     std::swap(requiredWidth, requiredHeight);
1319     std::swap(postXformImageWidth, postXformImageHeight);
1320   }
1321
1322   // Apply the special rules for when there are one or two zeros in requested dimensions:
1323   const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
1324   requiredWidth                          = correctedDesired.GetWidth();
1325   requiredHeight                         = correctedDesired.GetHeight();
1326
1327   // Rescale image during decode using one of the decoder's built-in rescaling
1328   // ratios (expected to be powers of 2), keeping the final image at least as
1329   // wide and high as was requested:
1330
1331   int              numFactors = 0;
1332   tjscalingfactor* factors    = tjGetScalingFactors(&numFactors);
1333   if(factors == NULL)
1334   {
1335     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1336     success = false;
1337   }
1338   else
1339   {
1340     // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1341     // apply it if the application requested one of those:
1342     // (use a switch case here so this code will fail to compile if other modes are added)
1343     bool downscale = true;
1344     switch(samplingMode)
1345     {
1346       case SamplingMode::BOX:
1347       case SamplingMode::BOX_THEN_NEAREST:
1348       case SamplingMode::BOX_THEN_LINEAR:
1349       case SamplingMode::DONT_CARE:
1350       {
1351         downscale = true;
1352         break;
1353       }
1354       case SamplingMode::NO_FILTER:
1355       case SamplingMode::NEAREST:
1356       case SamplingMode::LINEAR:
1357       {
1358         downscale = false;
1359         break;
1360       }
1361     }
1362
1363     int scaleFactorIndex(0);
1364     if(downscale)
1365     {
1366       // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1367       for(int i = 1; i < numFactors; ++i)
1368       {
1369         bool widthLessRequired  = TJSCALED(postXformImageWidth, factors[i]) < requiredWidth;
1370         bool heightLessRequired = TJSCALED(postXformImageHeight, factors[i]) < requiredHeight;
1371         // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1372         if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1373         {
1374           break;
1375         }
1376         // If both dimensions are smaller than the desired one, we were done at the last iteration:
1377         if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1378         {
1379           break;
1380         }
1381         // If the width is smaller than the desired one, we were done at the last iteration:
1382         if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1383         {
1384           break;
1385         }
1386         // If the width is smaller than the desired one, we were done at the last iteration:
1387         if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1388         {
1389           break;
1390         }
1391         // This factor stays is within our fitting mode constraint so remember it:
1392         scaleFactorIndex = i;
1393       }
1394     }
1395
1396     // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1397     for(int i = scaleFactorIndex; i < numFactors; ++i)
1398     {
1399       // Continue downscaling to below maximum texture size (if possible)
1400       scaleFactorIndex = i;
1401
1402       if(TJSCALED(postXformImageWidth, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()) &&
1403          TJSCALED(postXformImageHeight, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()))
1404       {
1405         // Current scale-factor downscales to below maximum texture size
1406         break;
1407       }
1408     }
1409
1410     // We have finally chosen the scale-factor, return width/height values
1411     if(scaleFactorIndex > 0)
1412     {
1413       preXformImageWidth   = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1414       preXformImageHeight  = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1415       postXformImageWidth  = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1416       postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1417     }
1418   }
1419
1420   return success;
1421 }
1422
1423 ExifHandle LoadExifData(FILE* fp)
1424 {
1425   auto          exifData = MakeNullExifData();
1426   unsigned char dataBuffer[1024];
1427
1428   if(fseek(fp, 0, SEEK_SET))
1429   {
1430     DALI_LOG_ERROR("Error seeking to start of file\n");
1431   }
1432   else
1433   {
1434     auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1435       exif_loader_new(), exif_loader_unref};
1436
1437     while(!feof(fp))
1438     {
1439       int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1440       if(size <= 0)
1441       {
1442         break;
1443       }
1444       if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1445       {
1446         break;
1447       }
1448     }
1449
1450     exifData.reset(exif_loader_get_data(exifLoader.get()));
1451   }
1452
1453   return exifData;
1454 }
1455
1456 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1457 {
1458   unsigned int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
1459   unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1460   FILE* const  fp             = input.file;
1461
1462   bool success = false;
1463
1464   unsigned int headerWidth;
1465   unsigned int headerHeight;
1466
1467   success = LoadJpegHeader(fp, headerWidth, headerHeight);
1468   if(success)
1469   {
1470     auto transform = JpegTransform::NONE;
1471
1472     if(input.reorientationRequested)
1473     {
1474       auto exifData = LoadExifData(fp);
1475       if(exifData)
1476       {
1477         transform = ConvertExifOrientation(exifData.get());
1478       }
1479     }
1480
1481     if(requiredWidth == 0 && requiredHeight == 0)
1482     {
1483       if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1484       {
1485         std::swap(headerWidth, headerHeight);
1486       }
1487     }
1488     else
1489     {
1490       int preXformImageWidth   = headerWidth;
1491       int preXformImageHeight  = headerHeight;
1492       int postXformImageWidth  = headerWidth;
1493       int postXformImageHeight = headerHeight;
1494
1495       success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1496       if(success)
1497       {
1498         headerWidth  = postXformImageWidth;
1499         headerHeight = postXformImageHeight;
1500       }
1501     }
1502     width  = headerWidth;
1503     height = headerHeight;
1504   }
1505
1506   return success;
1507 }
1508
1509 } // namespace TizenPlatform
1510
1511 } // namespace Dali