drag-and-drop: add const prefix to member data of DragData
[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(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(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(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(0u == jpegBufferSize)
550   {
551     DALI_LOG_ERROR("Jpeg buffer size error\n");
552     return false;
553   }
554
555   if(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(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
575   {
576     DALI_LOG_ERROR("Error on image file read.\n");
577     return false;
578   }
579
580   if(fseek(fp, 0, SEEK_SET))
581   {
582     DALI_LOG_ERROR("Error seeking to start of file\n");
583   }
584
585   auto jpeg = MakeJpegDecompressor();
586
587   if(!jpeg)
588   {
589     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
590     return false;
591   }
592
593   auto transform = JpegTransform::NONE;
594
595   // extract exif data
596   auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
597
598   if(exifData && input.reorientationRequested)
599   {
600     transform = ConvertExifOrientation(exifData.get());
601   }
602
603   std::unique_ptr<Property::Map> exifMap;
604   exifMap.reset(new Property::Map());
605
606   if(DALI_LIKELY(exifData))
607   {
608     for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
609     {
610       auto content = exifData->ifd[k];
611       for(auto i = 0u; i < content->count; ++i)
612       {
613         auto&&      tag       = content->entries[i];
614         const char* shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
615         if(shortName)
616         {
617           AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
618         }
619       }
620     }
621   }
622
623   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
624   int chrominanceSubsampling = -1;
625   int preXformImageWidth = 0, preXformImageHeight = 0;
626
627   // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
628   // Temporarily separate Ubuntu and other profiles.
629 #ifndef DALI_PROFILE_UBUNTU
630   int jpegColorspace = -1;
631   if(tjDecompressHeader3(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace) == -1)
632   {
633     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
634     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
635   }
636 #else
637   if(tjDecompressHeader2(jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling) == -1)
638   {
639     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
640     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
641   }
642 #endif
643
644   if(preXformImageWidth == 0 || preXformImageHeight == 0)
645   {
646     DALI_LOG_ERROR("Invalid Image!\n");
647     return false;
648   }
649
650   int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
651   int requiredHeight = input.scalingParameters.dimensions.GetHeight();
652
653   // If transform is a 90 or 270 degree rotation, the logical width and height
654   // request from the client needs to be adjusted to account by effectively
655   // rotating that too, and the final width and height need to be swapped:
656   int postXformImageWidth  = preXformImageWidth;
657   int postXformImageHeight = preXformImageHeight;
658
659   int scaledPreXformWidth   = preXformImageWidth;
660   int scaledPreXformHeight  = preXformImageHeight;
661   int scaledPostXformWidth  = postXformImageWidth;
662   int scaledPostXformHeight = postXformImageHeight;
663
664   TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
665
666   // Colorspace conversion options
667   TJPF          pixelLibJpegType = TJPF_RGB;
668   Pixel::Format pixelFormat      = Pixel::RGB888;
669 #ifndef DALI_PROFILE_UBUNTU
670   switch(jpegColorspace)
671   {
672     case TJCS_RGB:
673     // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
674     // YCbCr images must be converted to RGB before they can actually be displayed.
675     case TJCS_YCbCr:
676     {
677       pixelLibJpegType = TJPF_RGB;
678       pixelFormat      = Pixel::RGB888;
679       break;
680     }
681     case TJCS_GRAY:
682     {
683       pixelLibJpegType = TJPF_GRAY;
684       pixelFormat      = Pixel::L8;
685       break;
686     }
687     case TJCS_CMYK:
688     case TJCS_YCCK:
689     {
690       pixelLibJpegType = TJPF_CMYK;
691       pixelFormat      = Pixel::RGBA8888;
692       break;
693     }
694     default:
695     {
696       pixelLibJpegType = TJPF_RGB;
697       pixelFormat      = Pixel::RGB888;
698       break;
699     }
700   }
701 #endif
702   // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
703   bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
704
705   // set metadata
706   GetImplementation(bitmap).SetMetadata(std::move(exifMap));
707
708   auto bitmapPixelBuffer = bitmap.GetBuffer();
709
710   if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
711   {
712     std::string errorString = tjGetErrorStr();
713
714     if(IsJpegErrorFatal(errorString))
715     {
716       DALI_LOG_ERROR("%s\n", errorString.c_str());
717       return false;
718     }
719     else
720     {
721       DALI_LOG_WARNING("%s\n", errorString.c_str());
722     }
723   }
724
725   const unsigned int bufferWidth  = GetTextureDimension(scaledPreXformWidth);
726   const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
727
728   bool result = false;
729   switch(transform)
730   {
731     case JpegTransform::NONE:
732     {
733       result = true;
734       break;
735     }
736     // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
737     case JpegTransform::ROTATE_180:
738     {
739       static auto rotate180Functions = TransformFunctionArray{
740         &Rotate180<1>,
741         &Rotate180<3>,
742         &Rotate180<4>,
743       };
744       result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
745       break;
746     }
747     case JpegTransform::ROTATE_270:
748     {
749       static auto rotate270Functions = TransformFunctionArray{
750         &Rotate270<1>,
751         &Rotate270<3>,
752         &Rotate270<4>,
753       };
754       result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
755       break;
756     }
757     case JpegTransform::ROTATE_90:
758     {
759       static auto rotate90Functions = TransformFunctionArray{
760         &Rotate90<1>,
761         &Rotate90<3>,
762         &Rotate90<4>,
763       };
764       result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
765       break;
766     }
767     case JpegTransform::FLIP_VERTICAL:
768     {
769       static auto flipVerticalFunctions = TransformFunctionArray{
770         &FlipVertical<1>,
771         &FlipVertical<3>,
772         &FlipVertical<4>,
773       };
774       result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
775       break;
776     }
777     // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
778     case JpegTransform::FLIP_HORIZONTAL:
779     {
780       static auto flipHorizontalFunctions = TransformFunctionArray{
781         &FlipHorizontal<1>,
782         &FlipHorizontal<3>,
783         &FlipHorizontal<4>,
784       };
785       result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
786       break;
787     }
788     case JpegTransform::TRANSPOSE:
789     {
790       static auto transposeFunctions = TransformFunctionArray{
791         &Transpose<1>,
792         &Transpose<3>,
793         &Transpose<4>,
794       };
795       result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
796       break;
797     }
798     case JpegTransform::TRANSVERSE:
799     {
800       static auto transverseFunctions = TransformFunctionArray{
801         &Transverse<1>,
802         &Transverse<3>,
803         &Transverse<4>,
804       };
805       result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
806       break;
807     }
808     default:
809     {
810       DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
811       break;
812     }
813   }
814
815   return result;
816 }
817
818 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)
819 {
820   if(!pixelBuffer)
821   {
822     DALI_LOG_ERROR("Null input buffer\n");
823     return false;
824   }
825
826   // Translate pixel format enum:
827   int jpegPixelFormat = -1;
828
829   switch(pixelFormat)
830   {
831     case Pixel::RGB888:
832     {
833       jpegPixelFormat = TJPF_RGB;
834       break;
835     }
836     case Pixel::RGBA8888:
837     {
838       // Ignore the alpha:
839       jpegPixelFormat = TJPF_RGBX;
840       break;
841     }
842     case Pixel::BGRA8888:
843     {
844       // Ignore the alpha:
845       jpegPixelFormat = TJPF_BGRX;
846       break;
847     }
848     default:
849     {
850       DALI_LOG_ERROR("Unsupported pixel format for encoding to JPEG.\n");
851       return false;
852     }
853   }
854
855   // Assert quality is in the documented allowable range of the jpeg-turbo lib:
856   DALI_ASSERT_DEBUG(quality >= 1);
857   DALI_ASSERT_DEBUG(quality <= 100);
858   if(quality < 1)
859   {
860     quality = 1;
861   }
862   if(quality > 100)
863   {
864     quality = 100;
865   }
866
867   // Initialise a JPEG codec:
868   {
869     auto jpeg = MakeJpegCompressor();
870     if(!jpeg)
871     {
872       DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
873       return false;
874     }
875
876     // Safely wrap the jpeg codec's buffer in case we are about to throw, then
877     // save the pixels to a persistent buffer that we own and let our cleaner
878     // class clean up the buffer as it goes out of scope:
879     auto dstBuffer = MakeJpegMemory();
880
881     // Run the compressor:
882     unsigned long dstBufferSize = 0;
883     const int     flags         = 0;
884
885     if(tjCompress2(jpeg.get(),
886                    const_cast<unsigned char*>(pixelBuffer),
887                    width,
888                    0,
889                    height,
890                    jpegPixelFormat,
891                    SetPointer(dstBuffer),
892                    &dstBufferSize,
893                    TJSAMP_444,
894                    quality,
895                    flags))
896     {
897       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
898       return false;
899     }
900
901     encodedPixels.ResizeUninitialized(dstBufferSize);
902     memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
903   }
904   return true;
905 }
906
907 JpegTransform ConvertExifOrientation(ExifData* exifData)
908 {
909   auto             transform   = JpegTransform::NONE;
910   ExifEntry* const entry       = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
911   int              orientation = 0;
912   if(entry)
913   {
914     orientation = exif_get_short(entry->data, exif_data_get_byte_order(entry->parent->parent));
915     switch(orientation)
916     {
917       case 1:
918       {
919         transform = JpegTransform::NONE;
920         break;
921       }
922       case 2:
923       {
924         transform = JpegTransform::FLIP_HORIZONTAL;
925         break;
926       }
927       case 3:
928       {
929         transform = JpegTransform::ROTATE_180;
930         break;
931       }
932       case 4:
933       {
934         transform = JpegTransform::FLIP_VERTICAL;
935         break;
936       }
937       case 5:
938       {
939         transform = JpegTransform::TRANSPOSE;
940         break;
941       }
942       case 6:
943       {
944         transform = JpegTransform::ROTATE_90;
945         break;
946       }
947       case 7:
948       {
949         transform = JpegTransform::TRANSVERSE;
950         break;
951       }
952       case 8:
953       {
954         transform = JpegTransform::ROTATE_270;
955         break;
956       }
957       default:
958       {
959         // Try to keep loading the file, but let app developer know there was something fishy:
960         DALI_LOG_WARNING("Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry);
961         break;
962       }
963     }
964   }
965   return transform;
966 }
967
968 bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight)
969 {
970   bool success = true;
971   if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
972   {
973     std::swap(requiredWidth, requiredHeight);
974     std::swap(postXformImageWidth, postXformImageHeight);
975   }
976
977   // Apply the special rules for when there are one or two zeros in requested dimensions:
978   const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions(ImageDimensions(postXformImageWidth, postXformImageHeight), ImageDimensions(requiredWidth, requiredHeight));
979   requiredWidth                          = correctedDesired.GetWidth();
980   requiredHeight                         = correctedDesired.GetHeight();
981
982   // Rescale image during decode using one of the decoder's built-in rescaling
983   // ratios (expected to be powers of 2), keeping the final image at least as
984   // wide and high as was requested:
985
986   int              numFactors = 0;
987   tjscalingfactor* factors    = tjGetScalingFactors(&numFactors);
988   if(factors == NULL)
989   {
990     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
991     success = false;
992   }
993   else
994   {
995     // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
996     // apply it if the application requested one of those:
997     // (use a switch case here so this code will fail to compile if other modes are added)
998     bool downscale = true;
999     switch(samplingMode)
1000     {
1001       case SamplingMode::BOX:
1002       case SamplingMode::BOX_THEN_NEAREST:
1003       case SamplingMode::BOX_THEN_LINEAR:
1004       case SamplingMode::DONT_CARE:
1005       {
1006         downscale = true;
1007         break;
1008       }
1009       case SamplingMode::NO_FILTER:
1010       case SamplingMode::NEAREST:
1011       case SamplingMode::LINEAR:
1012       {
1013         downscale = false;
1014         break;
1015       }
1016     }
1017
1018     int scaleFactorIndex(0);
1019     if(downscale)
1020     {
1021       // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
1022       for(int i = 1; i < numFactors; ++i)
1023       {
1024         bool widthLessRequired  = TJSCALED(postXformImageWidth, factors[i]) < requiredWidth;
1025         bool heightLessRequired = TJSCALED(postXformImageHeight, factors[i]) < requiredHeight;
1026         // If either scaled dimension is smaller than the desired one, we were done at the last iteration
1027         if((fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired))
1028         {
1029           break;
1030         }
1031         // If both dimensions are smaller than the desired one, we were done at the last iteration:
1032         if((fittingMode == FittingMode::SHRINK_TO_FIT) && (widthLessRequired && heightLessRequired))
1033         {
1034           break;
1035         }
1036         // If the width is smaller than the desired one, we were done at the last iteration:
1037         if(fittingMode == FittingMode::FIT_WIDTH && widthLessRequired)
1038         {
1039           break;
1040         }
1041         // If the width is smaller than the desired one, we were done at the last iteration:
1042         if(fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired)
1043         {
1044           break;
1045         }
1046         // This factor stays is within our fitting mode constraint so remember it:
1047         scaleFactorIndex = i;
1048       }
1049     }
1050
1051     // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
1052     for(int i = scaleFactorIndex; i < numFactors; ++i)
1053     {
1054       // Continue downscaling to below maximum texture size (if possible)
1055       scaleFactorIndex = i;
1056
1057       if(TJSCALED(postXformImageWidth, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()) &&
1058          TJSCALED(postXformImageHeight, (factors[i])) < static_cast<int>(Dali::GetMaxTextureSize()))
1059       {
1060         // Current scale-factor downscales to below maximum texture size
1061         break;
1062       }
1063     }
1064
1065     // We have finally chosen the scale-factor, return width/height values
1066     if(scaleFactorIndex > 0)
1067     {
1068       preXformImageWidth   = TJSCALED(preXformImageWidth, (factors[scaleFactorIndex]));
1069       preXformImageHeight  = TJSCALED(preXformImageHeight, (factors[scaleFactorIndex]));
1070       postXformImageWidth  = TJSCALED(postXformImageWidth, (factors[scaleFactorIndex]));
1071       postXformImageHeight = TJSCALED(postXformImageHeight, (factors[scaleFactorIndex]));
1072     }
1073   }
1074
1075   return success;
1076 }
1077
1078 ExifHandle LoadExifData(FILE* fp)
1079 {
1080   auto          exifData = MakeNullExifData();
1081   unsigned char dataBuffer[1024];
1082
1083   if(fseek(fp, 0, SEEK_SET))
1084   {
1085     DALI_LOG_ERROR("Error seeking to start of file\n");
1086   }
1087   else
1088   {
1089     auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
1090       exif_loader_new(), exif_loader_unref};
1091
1092     while(!feof(fp))
1093     {
1094       int size = fread(dataBuffer, 1, sizeof(dataBuffer), fp);
1095       if(size <= 0)
1096       {
1097         break;
1098       }
1099       if(!exif_loader_write(exifLoader.get(), dataBuffer, size))
1100       {
1101         break;
1102       }
1103     }
1104
1105     exifData.reset(exif_loader_get_data(exifLoader.get()));
1106   }
1107
1108   return exifData;
1109 }
1110
1111 bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
1112 {
1113   unsigned int requiredWidth  = input.scalingParameters.dimensions.GetWidth();
1114   unsigned int requiredHeight = input.scalingParameters.dimensions.GetHeight();
1115   FILE* const  fp             = input.file;
1116
1117   bool success = false;
1118
1119   unsigned int headerWidth;
1120   unsigned int headerHeight;
1121
1122   success = LoadJpegHeader(fp, headerWidth, headerHeight);
1123   if(success)
1124   {
1125     auto transform = JpegTransform::NONE;
1126
1127     if(input.reorientationRequested)
1128     {
1129       auto exifData = LoadExifData(fp);
1130       if(exifData)
1131       {
1132         transform = ConvertExifOrientation(exifData.get());
1133       }
1134     }
1135
1136     if(requiredWidth == 0 && requiredHeight == 0)
1137     {
1138       if(transform == JpegTransform::TRANSPOSE || transform == JpegTransform::ROTATE_90 || transform == JpegTransform::TRANSVERSE || transform == JpegTransform::ROTATE_270)
1139       {
1140         std::swap(headerWidth, headerHeight);
1141       }
1142     }
1143     else
1144     {
1145       int preXformImageWidth   = headerWidth;
1146       int preXformImageHeight  = headerHeight;
1147       int postXformImageWidth  = headerWidth;
1148       int postXformImageHeight = headerHeight;
1149
1150       success = TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
1151       if(success)
1152       {
1153         headerWidth  = postXformImageWidth;
1154         headerHeight = postXformImageHeight;
1155       }
1156     }
1157     width  = headerWidth;
1158     height = headerHeight;
1159   }
1160
1161   return success;
1162 }
1163
1164 } // namespace TizenPlatform
1165
1166 } // namespace Dali