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