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