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