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