[dali_2.3.25] Merge branch '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 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(DALI_UNLIKELY(!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       if(DALI_UNLIKELY(!cmykBuffer))
986       {
987         DALI_LOG_ERROR("cmykBuffer allocation is failed [%zu]\n", sizeof(uint8_t) * scaledPostXformWidth * scaledPostXformHeight * cmykBytesPerPixel);
988         return false;
989       }
990
991       decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(cmykBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
992       if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
993       {
994         free(cmykBuffer);
995         return false;
996       }
997       ConvertTjpfCMYKToRGB888(cmykBuffer, bitmapPixelBuffer, scaledPostXformWidth, scaledPostXformHeight);
998
999       free(cmykBuffer);
1000     }
1001     else
1002     {
1003       decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<uint8_t*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
1004       if(DALI_UNLIKELY(decodeResult == -1 && IsJpegDecodingFailed()))
1005       {
1006         return false;
1007       }
1008     }
1009     pixelBuffers.push_back(bitmap);
1010
1011     // Transform bitmap
1012     result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
1013   }
1014
1015   return result;
1016 }
1017
1018 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)
1019 {
1020   if(DALI_UNLIKELY(!pixelBuffer))
1021   {
1022     DALI_LOG_ERROR("Null input buffer\n");
1023     return false;
1024   }
1025
1026   // Translate pixel format enum:
1027   int jpegPixelFormat = -1;
1028
1029   switch(pixelFormat)
1030   {
1031     case Pixel::L8:
1032     {
1033       jpegPixelFormat = TJPF_GRAY;
1034       break;
1035     }
1036     case Pixel::RGB888:
1037     {
1038       jpegPixelFormat = TJPF_RGB;
1039       break;
1040     }
1041     case Pixel::RGBA8888:
1042     {
1043       // Ignore the alpha:
1044       jpegPixelFormat = TJPF_RGBX;
1045       break;
1046     }
1047     case Pixel::BGRA8888:
1048     {
1049       // Ignore the alpha:
1050       jpegPixelFormat = TJPF_BGRX;
1051       break;
1052     }
1053     default:
1054     {
1055       DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG. Format enum : [%d]\n", pixelFormat);
1056       return false;
1057     }
1058   }
1059
1060   // Assert quality is in the documented allowable range of the jpeg-turbo lib:
1061   DALI_ASSERT_DEBUG(quality >= 1);
1062   DALI_ASSERT_DEBUG(quality <= 100);
1063   if(quality < 1)
1064   {
1065     quality = 1;
1066   }
1067   if(quality > 100)
1068   {
1069     quality = 100;
1070   }
1071
1072   // Initialise a JPEG codec:
1073   {
1074     auto jpeg = MakeJpegCompressor();
1075     if(DALI_UNLIKELY(!jpeg))
1076     {
1077       DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
1078       return false;
1079     }
1080
1081     // Safely wrap the jpeg codec's buffer in case we are about to throw, then
1082     // save the pixels to a persistent buffer that we own and let our cleaner
1083     // class clean up the buffer as it goes out of scope:
1084     auto dstBuffer = MakeJpegMemory();
1085
1086     // Run the compressor:
1087     unsigned long dstBufferSize = 0;
1088     const int     flags         = 0;
1089
1090     if(DALI_UNLIKELY(tjCompress2(jpeg.get(),
1091                                  const_cast<uint8_t*>(pixelBuffer),
1092                                  width,
1093                                  0,
1094                                  height,
1095                                  jpegPixelFormat,
1096                                  SetPointer(dstBuffer),
1097                                  &dstBufferSize,
1098                                  TJSAMP_444,
1099                                  quality,
1100                                  flags)))
1101     {
1102       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
1103       return false;
1104     }
1105
1106     encodedPixels.ResizeUninitialized(dstBufferSize);
1107     memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
1108   }
1109   return true;
1110 }
1111
1112 JpegTransform ConvertExifOrientation(ExifData* exifData)
1113 {
1114   auto             transform   = JpegTransform::NONE;
1115   ExifEntry* const entry       = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
1116   int              orientation = 0;
1117   if(entry)
1118   {
1119     orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
1120     switch(orientation)
1121     {
1122       case 1:
1123       {
1124         transform = JpegTransform::NONE;
1125         break;
1126       }
1127       case 2:
1128       {
1129         transform = JpegTransform::FLIP_HORIZONTAL;
1130         break;
1131       }
1132       case 3:
1133       {
1134         transform = JpegTransform::ROTATE_180;
1135         break;
1136       }
1137       case 4:
1138       {
1139         transform = JpegTransform::FLIP_VERTICAL;
1140         break;
1141       }
1142       case 5:
1143       {
1144         transform = JpegTransform::TRANSPOSE;
1145         break;
1146       }
1147       case 6:
1148       {
1149         transform = JpegTransform::ROTATE_90;
1150         break;
1151       }
1152       case 7:
1153       {
1154         transform = JpegTransform::TRANSVERSE;
1155         break;
1156       }
1157       case 8:
1158       {
1159         transform = JpegTransform::ROTATE_270;
1160         break;
1161       }
1162       default:
1163       {
1164         // Try to keep loading the file, but let app developer know there was something fishy:
1165         DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
1166         break;
1167       }
1168     }
1169   }
1170   return transform;
1171 }
1172
1173 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
1174 {
1175   bool success = true;
1176   if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1177   {
1178     std::swap(requiredWidth, requiredHeight);
1179     std::swap(postXformImageWidth, postXformImageHeight);
1180   }
1181
1182   // Apply the special rules for when there are one or two zeros in requested dimensions:
1183   const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
1184   requiredWidth                          = correctedDesired.GetWidth();
1185   requiredHeight                         = correctedDesired.GetHeight();
1186
1187   // Rescale image during decode using one of the decoder's built-in rescaling
1188   // ratios (expected to be powers of 2), keeping the final image at least as
1189   // wide and high as was requested:
1190
1191   int              numFactors = 0;
1192   tjscalingfactor* factors    = tjGetScalingFactors(&numFactors);
1193   if(DALI_UNLIKELY(factors == NULL))
1194   {
1195     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
1196     success = false;
1197   }
1198   else
1199   {
1200     // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
1201     // apply it if the application requested one of those:
1202     // (use a switch case here so this code will fail to compile if other modes are added)
1203     bool useTurboJpegScaleFactor = true;
1204     switch(samplingMode)
1205     {
1206       case SamplingMode::BOX:
1207       case SamplingMode::BOX_THEN_NEAREST:
1208       case SamplingMode::BOX_THEN_LINEAR:
1209       case SamplingMode::DONT_CARE:
1210       {
1211         useTurboJpegScaleFactor = true;
1212         break;
1213       }
1214       case SamplingMode::NO_FILTER:
1215       case SamplingMode::NEAREST:
1216       case SamplingMode::LINEAR:
1217       {
1218         useTurboJpegScaleFactor = false;
1219         break;
1220       }
1221     }
1222
1223     int scaleFactorIndex(-1);
1224     int fittedScaledWidth(postXformImageWidth);
1225     int fittedScaledHeight(postXformImageHeight);
1226     if(useTurboJpegScaleFactor)
1227     {
1228       // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1229       for(int i = 0; i < numFactors; ++i)
1230       {
1231         int  scaledWidth        = TJSCALED(postXformImageWidth, factors[i]);
1232         int  scaledHeight       = TJSCALED(postXformImageHeight, factors[i]);
1233         bool widthLessRequired  = scaledWidth < requiredWidth;
1234         bool heightLessRequired = scaledHeight < requiredHeight;
1235         // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1236         if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1237         {
1238           break;
1239         }
1240         // If both dimensions are smaller than the desired one, we were done at the last iteration:
1241         if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1242         {
1243           break;
1244         }
1245         // If the width is smaller than the desired one, we were done at the last iteration:
1246         if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1247         {
1248           break;
1249         }
1250         // If the width is smaller than the desired one, we were done at the last iteration:
1251         if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1252         {
1253           break;
1254         }
1255         // This factor stays is within our fitting mode constraint so remember it:
1256         scaleFactorIndex   = i;
1257         fittedScaledWidth  = scaledWidth;
1258         fittedScaledHeight = scaledHeight;
1259       }
1260     }
1261
1262     const int maxTextureSize = static_cast<int>(Dali::GetMaxTextureSize());
1263
1264     // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1265     if(fittedScaledWidth >= maxTextureSize ||
1266        fittedScaledHeight >= maxTextureSize)
1267     {
1268       for(int i = scaleFactorIndex + 1; i < numFactors; ++i)
1269       {
1270         // Continue downscaling to below maximum texture size (if possible)
1271         scaleFactorIndex   = i;
1272         fittedScaledWidth  = TJSCALED(postXformImageWidth, factors[i]);
1273         fittedScaledHeight = TJSCALED(postXformImageHeight, factors[i]);
1274
1275         if(fittedScaledWidth < maxTextureSize &&
1276            fittedScaledHeight < maxTextureSize)
1277         {
1278           // Current scale-factor downscales to below maximum texture size
1279           break;
1280         }
1281       }
1282     }
1283
1284     // We have finally chosen the scale-factor, return width/height values
1285     if(scaleFactorIndex >= 0 && scaleFactorIndex < numFactors)
1286     {
1287       preXformImageWidth   = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1288       preXformImageHeight  = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1289       postXformImageWidth  = fittedScaledWidth;
1290       postXformImageHeight = fittedScaledHeight;
1291     }
1292   }
1293
1294   return success;
1295 }
1296
1297 ExifHandle LoadExifData(FILE* fp)
1298 {
1299   auto    exifData = MakeNullExifData();
1300   uint8_t dataBuffer[1024];
1301
1302   if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
1303   {
1304     DALI_LOG_ERROR("Error seeking to start of file\n");
1305   }
1306   else
1307   {
1308     auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1309       exif_loader_new(), exif_loader_unref};
1310
1311     while(!feof(fp))
1312     {
1313       int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1314       if(size <= 0)
1315       {
1316         break;
1317       }
1318       if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1319       {
1320         break;
1321       }
1322     }
1323
1324     exifData.reset(exif_loader_get_data(exifLoader.get()));
1325   }
1326
1327   return exifData;
1328 }
1329
1330 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1331 {
1332   unsigned int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
1333   unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1334   FILE* const  fp             = input.file;
1335
1336   bool success = false;
1337
1338   unsigned int headerWidth;
1339   unsigned int headerHeight;
1340
1341   success = LoadJpegHeader(fp, headerWidth, headerHeight);
1342   if(DALI_LIKELY(success))
1343   {
1344     auto transform = JpegTransform::NONE;
1345
1346     if(input.reorientationRequested)
1347     {
1348       auto exifData = LoadExifData(fp);
1349       if(exifData)
1350       {
1351         transform = ConvertExifOrientation(exifData.get());
1352       }
1353     }
1354
1355     if(requiredWidth == 0 && requiredHeight == 0)
1356     {
1357       if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1358       {
1359         std::swap(headerWidth, headerHeight);
1360       }
1361     }
1362     else
1363     {
1364       int preXformImageWidth   = headerWidth;
1365       int preXformImageHeight  = headerHeight;
1366       int postXformImageWidth  = headerWidth;
1367       int postXformImageHeight = headerHeight;
1368
1369       success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1370       if(DALI_LIKELY(success))
1371       {
1372         headerWidth  = postXformImageWidth;
1373         headerHeight = postXformImageHeight;
1374       }
1375     }
1376     width  = headerWidth;
1377     height = headerHeight;
1378   }
1379
1380   return success;
1381 }
1382
1383 } // namespace TizenPlatform
1384
1385 } // namespace Dali