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