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