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