Exposing Exif Image metadata
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / image-loaders / loader-jpeg-turbo.cpp
index 455d1f7..54019c6 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  *
  */
 
-// INTERNAL HEADERS
+ // CLASS HEADER
 #include "loader-jpeg.h"
-#include "resource-loading-client.h"
-#include <dali/integration-api/bitmap.h>
-#include <resource-loader/debug/resource-loader-debug.h>
-#include "platform-capabilities.h"
-#include "image-operations.h"
 
 // EXTERNAL HEADERS
+#include <functional>
+#include <array>
+#include <utility>
+#include <memory>
 #include <libexif/exif-data.h>
 #include <libexif/exif-loader.h>
 #include <libexif/exif-tag.h>
 #include <cstring>
 #include <setjmp.h>
 
-namespace Dali
-{
-using Integration::Bitmap;
+#include <dali/public-api/object/property-map.h>
+#include <dali/public-api/object/property-array.h>
+#include <adaptors/devel-api/adaptor-framework/pixel-buffer.h>
 
-namespace TizenPlatform
-{
+
+// INTERNAL HEADERS
+#include "platform-capabilities.h"
+#include "image-operations.h"
+#include <image-loading.h>
+#include <adaptors/common/pixel-buffer-impl.h>
 
 namespace
 {
-  const unsigned DECODED_PIXEL_SIZE = 3;
-  const TJPF DECODED_PIXEL_LIBJPEG_TYPE = TJPF_RGB;
+using Dali::Vector;
+namespace Pixel = Dali::Pixel;
+using PixelArray = unsigned char*;
+const unsigned int DECODED_L8 = 1;
+const unsigned int DECODED_RGB888 = 3;
+const unsigned int DECODED_RGBA8888 = 4;
+
+/** Transformations that can be applied to decoded pixels to respect exif orientation
+  *  codes in image headers */
+enum class JpegTransform
+{
+  NONE,             //< no transformation 0th-Row = top & 0th-Column = left
+  FLIP_HORIZONTAL,  //< horizontal flip 0th-Row = top & 0th-Column = right
+  FLIP_VERTICAL,    //< vertical flip   0th-Row = bottom & 0th-Column = right
+  TRANSPOSE,        //< transpose across UL-to-LR axis  0th-Row = bottom & 0th-Column = left
+  TRANSVERSE,       //< transpose across UR-to-LL axis  0th-Row = left   & 0th-Column = top
+  ROTATE_90,        //< 90-degree clockwise rotation  0th-Row = right  & 0th-Column = top
+  ROTATE_180,       //< 180-degree rotation  0th-Row = right  & 0th-Column = bottom
+  ROTATE_270,       //< 270-degree clockwise (or 90 ccw) 0th-Row = left  & 0th-Column = bottom
+};
+
+/**
+  * @brief Error handling bookeeping for the JPEG Turbo library's
+  * setjmp/longjmp simulated exceptions.
+  */
+struct JpegErrorState
+{
+  struct jpeg_error_mgr errorManager;
+  jmp_buf jumpBuffer;
+};
+
+/**
+  * @brief Called by the JPEG library when it hits an error.
+  * We jump out of the library so our loader code can return an error.
+  */
+void  JpegErrorHandler ( j_common_ptr cinfo )
+{
+  DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" );
+  /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
+  JpegErrorState * myerr = reinterpret_cast<JpegErrorState *>( cinfo->err );
 
-  /** Transformations that can be applied to decoded pixels to respect exif orientation
-   *  codes in image headers */
-  enum JPGFORM_CODE
-  {
-    JPGFORM_NONE = 1, /* no transformation 0th-Row = top & 0th-Column = left */
-    JPGFORM_FLIP_H,   /* horizontal flip 0th-Row = top & 0th-Column = right */
-    JPGFORM_FLIP_V,   /* vertical flip   0th-Row = bottom & 0th-Column = right*/
-    JPGFORM_TRANSPOSE, /* transpose across UL-to-LR axis  0th-Row = bottom & 0th-Column = left*/
-    JPGFORM_TRANSVERSE,/* transpose across UR-to-LL axis  0th-Row = left   & 0th-Column = top*/
-    JPGFORM_ROT_90,    /* 90-degree clockwise rotation  0th-Row = right  & 0th-Column = top*/
-    JPGFORM_ROT_180,   /* 180-degree rotation  0th-Row = right  & 0th-Column = bottom*/
-    JPGFORM_ROT_270    /* 270-degree clockwise (or 90 ccw) 0th-Row = left  & 0th-Column = bottom*/
-  };
+  /* Return control to the setjmp point */
+  longjmp( myerr->jumpBuffer, 1 );
+}
 
-  struct RGB888Type
+void JpegOutputMessageHandler( j_common_ptr cinfo )
+{
+  /* Stop libjpeg from printing to stderr - Do Nothing */
+}
+
+/**
+  * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
+  * the JPEG to be displayed and fatal errors.
+  */
+bool IsJpegErrorFatal( const std::string& errorMessage )
+{
+  if( ( errorMessage.find("Corrupt JPEG data") != std::string::npos ) ||
+      ( errorMessage.find("Invalid SOS parameters") != std::string::npos ) ||
+      ( errorMessage.find("Invalid JPEG file structure") != std::string::npos ) ||
+      ( errorMessage.find("Unsupported JPEG process") != std::string::npos ) ||
+      ( errorMessage.find("Unsupported marker type") != std::string::npos ) ||
+      ( errorMessage.find("Bogus marker length") != std::string::npos ) ||
+      ( errorMessage.find("Bogus DQT index") != std::string::npos ) ||
+      ( errorMessage.find("Bogus Huffman table definition") != std::string::npos ))
   {
-     char R;
-     char G;
-     char B;
-  };
+    return false;
+  }
+  return true;
+}
 
-  /**
-   * @brief Error handling bookeeping for the JPEG Turbo library's
-   * setjmp/longjmp simulated exceptions.
-   */
-  struct JpegErrorState {
-    struct jpeg_error_mgr errorManager;
-    jmp_buf jumpBuffer;
-  };
+// helpers for safe exif memory handling
+using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
 
-  /**
-   * @brief Called by the JPEG library when it hits an error.
-   * We jump out of the library so our loader code can return an error.
-   */
-  void  JpegErrorHandler ( j_common_ptr cinfo )
-  {
-    DALI_LOG_ERROR( "JpegErrorHandler(): libjpeg-turbo fatal error in JPEG decoding.\n" );
-    /* cinfo->err really points to a JpegErrorState struct, so coerce pointer */
-    JpegErrorState * myerr = reinterpret_cast<JpegErrorState *>( cinfo->err );
+ExifHandle MakeNullExifData()
+{
+  return ExifHandle{nullptr, exif_data_free};
+}
 
-    /* Return control to the setjmp point */
-    longjmp( myerr->jumpBuffer, 1 );
-  }
+ExifHandle MakeExifDataFromData(unsigned char* data, unsigned int size)
+{
+  return ExifHandle{exif_data_new_from_data(data, size), exif_data_free};
+}
 
-  void JpegOutputMessageHandler( j_common_ptr cinfo )
+// Helpers for safe Jpeg memory handling
+using JpegHandle = std::unique_ptr<void /*tjhandle*/, decltype(tjDestroy)*>;
+
+JpegHandle MakeJpegCompressor()
+{
+  return JpegHandle{tjInitCompress(), tjDestroy};
+}
+
+JpegHandle MakeJpegDecompressor()
+{
+  return JpegHandle{tjInitDecompress(), tjDestroy};
+}
+
+using JpegMemoryHandle = std::unique_ptr<unsigned char, decltype(tjFree)*>;
+
+JpegMemoryHandle MakeJpegMemory()
+{
+  return JpegMemoryHandle{nullptr, tjFree};
+}
+
+template<class T, class Deleter>
+class UniquePointerSetter final
+{
+public:
+  UniquePointerSetter(std::unique_ptr<T, Deleter>& uniquePointer)
+  : mUniquePointer(uniquePointer),
+    mRawPointer(nullptr)
+  {}
+
+  /// @brief Pointer to Pointer cast operator
+  operator T** () { return &mRawPointer; }
+
+  /// @brief Destructor, reset the unique_ptr
+  ~UniquePointerSetter() { mUniquePointer.reset(mRawPointer); }
+
+private:
+  std::unique_ptr<T, Deleter>& mUniquePointer;
+  T* mRawPointer;
+};
+
+template<typename T, typename Deleter>
+UniquePointerSetter<T, Deleter> SetPointer(std::unique_ptr<T, Deleter>& uniquePointer)
+{
+  return UniquePointerSetter<T, Deleter>{uniquePointer};
+}
+
+using TransformFunction = std::function<void(PixelArray,unsigned, unsigned)>;
+using TransformFunctionArray = std::array<TransformFunction, 3>; // 1, 3 and 4 bytes per pixel
+
+/// @brief Select the transform function depending on the pixel format
+TransformFunction GetTransformFunction(const TransformFunctionArray& functions,
+                                       Pixel::Format pixelFormat)
+{
+  auto function = TransformFunction{};
+
+  int decodedPixelSize = Pixel::GetBytesPerPixel(pixelFormat);
+  switch( decodedPixelSize )
   {
-    /* Stop libjpeg from printing to stderr - Do Nothing */
+    case DECODED_L8:
+    {
+      function = functions[0];
+      break;
+    }
+    case DECODED_RGB888:
+    {
+      function = functions[1];
+      break;
+    }
+    case DECODED_RGBA8888:
+    {
+      function = functions[2];
+      break;
+    }
+    default:
+    {
+      DALI_LOG_ERROR("Transform operation not supported on this Pixel::Format!");
+      function = functions[1];
+      break;
+    }
   }
+  return function;
+}
+
+// Storing Exif fields as properties
+template<class R, class V>
+R ConvertExifNumeric( const ExifEntry& entry )
+{
+  return static_cast<R>((*reinterpret_cast<V*>(entry.data)));
+}
 
-  /**
-   * LibJPEG Turbo tjDecompress2 API doesn't distinguish between errors that still allow
-   * the JPEG to be displayed and fatal errors.
-   */
-  bool IsJpegErrorFatal( const std::string& errorMessage )
+void AddExifFieldPropertyMap( Dali::Property::Map& out, const ExifEntry& entry, ExifIfd ifd )
+{
+  auto shortName = std::string(exif_tag_get_name_in_ifd(entry.tag, ifd ));
+  switch( entry.format )
   {
-    if( ( errorMessage.find("Corrupt JPEG data") != std::string::npos ) ||
-        ( errorMessage.find("Invalid SOS parameters") != std::string::npos ) )
+    case EXIF_FORMAT_ASCII:
     {
-      return false;
+      out.Insert( shortName, std::string(reinterpret_cast<char *>(entry.data)) );
+      break;
+    }
+    case EXIF_FORMAT_SHORT:
+    {
+      out.Insert( shortName, ConvertExifNumeric<int, unsigned int>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_LONG:
+    {
+      out.Insert( shortName, ConvertExifNumeric<int, unsigned long>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_SSHORT:
+    {
+      out.Insert( shortName, ConvertExifNumeric<int, int>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_SLONG:
+    {
+      out.Insert( shortName, ConvertExifNumeric<int, long>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_FLOAT:
+    {
+      out.Insert (shortName, ConvertExifNumeric<float, float>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_DOUBLE:
+    {
+      out.Insert( shortName, ConvertExifNumeric<float, double>(entry) );
+      break;
+    }
+    case EXIF_FORMAT_RATIONAL:
+    {
+      auto values = reinterpret_cast<unsigned int*>( entry.data );
+      Dali::Property::Array array;
+      array.Add( static_cast<int>(values[0]) );
+      array.Add( static_cast<int>(values[1]) );
+      out.Insert(shortName, array);
+      break;
+    }
+    case EXIF_FORMAT_SBYTE:
+    {
+      out.Insert(shortName, "EXIF_FORMAT_SBYTE Unsupported");
+      break;
+    }
+    case EXIF_FORMAT_BYTE:
+    {
+      out.Insert(shortName, "EXIF_FORMAT_BYTE Unsupported");
+      break;
+    }
+    case EXIF_FORMAT_SRATIONAL:
+    {
+      auto values = reinterpret_cast<int*>( entry.data );
+      Dali::Property::Array array;
+      array.Add(values[0]);
+      array.Add(values[1]);
+      out.Insert(shortName, array);
+      break;
+    }
+    case EXIF_FORMAT_UNDEFINED:
+    default:
+    {
+      std::stringstream ss;
+      ss << "EXIF_FORMAT_UNDEFINED, size: " << entry.size << ", components: " << entry.components;
+      out.Insert( shortName, ss.str());
     }
-    return true;
   }
+}
 
+/// @brief Apply a transform to a buffer
+bool Transform(const TransformFunctionArray& transformFunctions,
+               PixelArray buffer,
+               int width,
+               int height,
+               Pixel::Format pixelFormat )
+{
+  auto transformFunction = GetTransformFunction(transformFunctions, pixelFormat);
+  if(transformFunction)
+  {
+    transformFunction(buffer, width, height);
+  }
+  return bool(transformFunction);
+}
 
-  /** Simple struct to ensure xif data is deleted. */
-  struct ExifAutoPtr
+/// @brief Auxiliar type to represent pixel data with different number of bytes
+template<size_t N>
+struct PixelType
+{
+  char _[N];
+};
+
+template<size_t N>
+void FlipVertical(PixelArray buffer, int width, int height)
+{
+  // Destination pixel, set as the first pixel of screen
+  auto to = reinterpret_cast<PixelType<N>*>( buffer );
+
+  // Source pixel, as the image is flipped horizontally and vertically,
+  // the source pixel is the end of the buffer of size width * height
+  auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * height - 1;
+
+  for (auto ix = 0, endLoop = (width * height) / 2; ix < endLoop; ++ix, ++to, --from)
   {
-    ExifAutoPtr( ExifData* data)
-    :mData( data )
-    {}
+    std::swap(*from, *to);
+  }
+}
 
-    ~ExifAutoPtr()
+template<size_t N>
+void FlipHorizontal(PixelArray buffer, int width, int height)
+{
+  for(auto iy = 0; iy < height; ++iy)
+  {
+    //Set the destination pixel as the beginning of the row
+    auto to = reinterpret_cast<PixelType<N>*>(buffer) + width * iy;
+    //Set the source pixel as the end of the row to flip in X axis
+    auto from = reinterpret_cast<PixelType<N>*>(buffer) + width * (iy + 1) - 1;
+    for(auto ix = 0; ix < width / 2; ++ix, ++to, --from)
     {
-      exif_data_free( mData);
+      std::swap(*from, *to);
     }
-    ExifData *mData;
-  };
+  }
+}
 
-  /** simple class to enforce clean-up of JPEG structures. */
-  struct AutoJpg
+template<size_t N>
+void Transpose(PixelArray buffer, int width, int height)
+{
+  //Transform vertically only
+  for(auto iy = 0; iy < height / 2; ++iy)
   {
-    AutoJpg(const tjhandle jpgHandle)
-    : mHnd(jpgHandle)
+    for(auto ix = 0; ix < width; ++ix)
     {
+      auto to = reinterpret_cast<PixelType<N>*>(buffer) + iy * width + ix;
+      auto from = reinterpret_cast<PixelType<N>*>(buffer) + (height - 1 - iy) * width + ix;
+      std::swap(*from, *to);
     }
+  }
+}
 
-    ~AutoJpg()
-    {
-      // clean up JPG resources
-      tjDestroy( mHnd );
-    }
+template<size_t N>
+void Transverse(PixelArray buffer, int width, int height)
+{
+  using PixelT = PixelType<N>;
+  Vector<PixelT> data;
+  data.Resize( width * height );
+  auto dataPtr = data.Begin();
 
-    tjhandle GetHandle() const
+  auto original = reinterpret_cast<PixelT*>(buffer);
+  std::copy(original, original + width * height, dataPtr);
+
+  auto to = original;
+  for( auto iy = 0; iy < width; ++iy )
+  {
+    for( auto ix = 0; ix < height; ++ix, ++to )
     {
-      return mHnd ;
+      auto from = dataPtr + ix * width + iy;
+      *to = *from;
     }
+  }
+}
+
+
+template<size_t N>
+void Rotate90(PixelArray buffer, int width, int height)
+{
+  using PixelT = PixelType<N>;
+  Vector<PixelT> data;
+  data.Resize(width * height);
+  auto dataPtr = data.Begin();
+
+  auto original = reinterpret_cast<PixelT*>(buffer);
+  std::copy(original, original + width * height, dataPtr);
 
-  private:
-    AutoJpg( const AutoJpg& ); //< not defined
-    AutoJpg& operator= ( const AutoJpg& ); //< not defined
+  std::swap(width, height);
+  auto hw = width * height;
+  hw = - hw - 1;
 
-    tjhandle mHnd;
-  }; // struct AutoJpg;
+  auto to = original + width - 1;
+  auto from = dataPtr;
 
-  /** RAII wrapper to free memory allocated by the jpeg-turbo library. */
-  struct AutoJpgMem
+  for(auto ix = width; --ix >= 0;)
   {
-    AutoJpgMem(unsigned char * const tjMem)
-    : mTjMem(tjMem)
+    for(auto iy = height; --iy >= 0; ++from)
     {
+      *to = *from;
+      to += width;
     }
+    to += hw;
+  }
+}
 
-    ~AutoJpgMem()
-    {
-      tjFree(mTjMem);
-    }
+template<size_t N>
+void Rotate180(PixelArray buffer, int width, int height)
+{
+  using PixelT = PixelType<N>;
+  Vector<PixelT> data;
+  data.Resize(width * height);
+  auto dataPtr = data.Begin();
 
-    unsigned char * Get() const
+  auto original = reinterpret_cast<PixelT*>(buffer);
+  std::copy(original, original + width * height, dataPtr);
+
+  auto to = original;
+  for( auto iy = 0; iy < width; iy++ )
+  {
+    for( auto ix = 0; ix < height; ix++ )
     {
-      return mTjMem;
+      auto from = dataPtr + (height - ix) * width - 1 - iy;
+      *to = *from;
+      ++to;
     }
+  }
+}
+
+
+template<size_t N>
+void Rotate270(PixelArray buffer, int width, int height)
+{
+  using PixelT = PixelType<N>;
+  Vector<PixelT> data;
+  data.Resize(width * height);
+  auto dataPtr = data.Begin();
+
+  auto original = reinterpret_cast<PixelT*>(buffer);
+  std::copy(original, original + width * height, dataPtr);
 
-  private:
-    AutoJpgMem( const AutoJpgMem& ); //< not defined
-    AutoJpgMem& operator= ( const AutoJpgMem& ); //< not defined
+  auto w = height;
+  std::swap(width, height);
+  auto hw = width * height;
 
-    unsigned char * const mTjMem;
-  };
+  auto* to = original + hw  - width;
+  auto* from = dataPtr;
 
-  // Workaround to avoid exceeding the maximum texture size
-  const int MAX_TEXTURE_WIDTH  = 4096;
-  const int MAX_TEXTURE_HEIGHT = 4096;
+  w = -w;
+  hw =  hw + 1;
+  for(auto ix = width; --ix >= 0;)
+  {
+    for(auto iy = height; --iy >= 0;)
+    {
+      *to = *from;
+      ++from;
+      to += w;
+    }
+    to += hw;
+  }
+}
 
 } // namespace
 
-bool JpegRotate90 (unsigned char *buffer, int width, int height, int bpp);
-bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp);
-bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp);
-JPGFORM_CODE ConvertExifOrientation(ExifData* exifData);
+namespace Dali
+{
+
+namespace TizenPlatform
+{
+
+JpegTransform ConvertExifOrientation(ExifData* exifData);
 bool TransformSize( int requiredWidth, int requiredHeight,
                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
-                    JPGFORM_CODE transform,
+                    JpegTransform transform,
                     int& preXformImageWidth, int& preXformImageHeight,
                     int& postXformImageWidth, int& postXformImageHeight );
 
@@ -207,7 +505,11 @@ bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
     return false;
   }
 
+// jpeg_create_decompress internally uses C casts
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wold-style-cast"
   jpeg_create_decompress( &cinfo );
+#pragma GCC diagnostic pop
 
   jpeg_stdio_src( &cinfo, fp );
 
@@ -226,7 +528,7 @@ bool LoadJpegHeader( FILE *fp, unsigned int &width, unsigned int &height )
   return true;
 }
 
-bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader::Input& input, Integration::Bitmap& bitmap )
+bool LoadBitmapFromJpeg( const ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap )
 {
   const int flags= 0;
   FILE* const fp = input.file;
@@ -258,7 +560,7 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
   Vector<unsigned char> jpegBuffer;
   try
   {
-    jpegBuffer.Reserve( jpegBufferSize );
+    jpegBuffer.Resize( jpegBufferSize );
   }
   catch(...)
   {
@@ -279,36 +581,61 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
     DALI_LOG_ERROR("Error seeking to start of file\n");
   }
 
-  // Allow early cancellation between the load and the decompress:
-  client.InterruptionPoint();
-
-  AutoJpg autoJpg(tjInitDecompress());
+  auto jpeg = MakeJpegDecompressor();
 
-  if(autoJpg.GetHandle() == NULL)
+  if(!jpeg)
   {
     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
     return false;
   }
 
-  JPGFORM_CODE transform = JPGFORM_NONE;
+  auto transform = JpegTransform::NONE;
+
+  // extract exif data
+  auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
+
+  if( exifData && input.reorientationRequested )
+  {
+    transform = ConvertExifOrientation(exifData.get());
+  }
+
+  std::unique_ptr<Property::Map> exifMap;
+  exifMap.reset( new Property::Map() );
 
-  if( input.reorientationRequested )
+  for( auto k = 0u; k < EXIF_IFD_COUNT; ++k )
   {
-    ExifAutoPtr exifData( exif_data_new_from_data(jpegBufferPtr, jpegBufferSize) );
-    if( exifData.mData )
+    auto content = exifData->ifd[k];
+    for (auto i = 0u; i < content->count; ++i)
     {
-      transform = ConvertExifOrientation(exifData.mData);
+      auto       &&tag      = content->entries[i];
+      const char *shortName = exif_tag_get_name_in_ifd(tag->tag, static_cast<ExifIfd>(k));
+      if(shortName)
+      {
+        AddExifFieldPropertyMap(*exifMap, *tag, static_cast<ExifIfd>(k));
+      }
     }
   }
 
   // Push jpeg data in memory buffer through TurboJPEG decoder to make a raw pixel array:
   int chrominanceSubsampling = -1;
   int preXformImageWidth = 0, preXformImageHeight = 0;
-  if( tjDecompressHeader2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
+
+  // In Ubuntu, the turbojpeg version is not correct. so build error occurs.
+  // Temporarily separate Ubuntu and other profiles.
+#ifndef DALI_PROFILE_UBUNTU
+  int jpegColorspace = -1;
+  if( tjDecompressHeader3( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling, &jpegColorspace ) == -1 )
+  {
+    DALI_LOG_ERROR("%s\n", tjGetErrorStr());
+    // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
+  }
+#else
+  if( tjDecompressHeader2( jpeg.get(), jpegBufferPtr, jpegBufferSize, &preXformImageWidth, &preXformImageHeight, &chrominanceSubsampling ) == -1 )
   {
     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
     // Do not set width and height to 0 or return early as this sometimes fails only on determining subsampling type.
   }
+#endif
 
   if(preXformImageWidth == 0 || preXformImageHeight == 0)
   {
@@ -338,201 +665,163 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
                  scaledPreXformWidth, scaledPreXformHeight,
                  scaledPostXformWidth, scaledPostXformHeight );
 
+
+  // Colorspace conversion options
+  TJPF pixelLibJpegType = TJPF_RGB;
+  Pixel::Format pixelFormat = Pixel::RGB888;
+#ifndef DALI_PROFILE_UBUNTU
+  switch (jpegColorspace)
+  {
+    case TJCS_RGB:
+    // YCbCr is not an absolute colorspace but rather a mathematical transformation of RGB designed solely for storage and transmission.
+    // YCbCr images must be converted to RGB before they can actually be displayed.
+    case TJCS_YCbCr:
+    {
+      pixelLibJpegType = TJPF_RGB;
+      pixelFormat = Pixel::RGB888;
+      break;
+    }
+    case TJCS_GRAY:
+    {
+      pixelLibJpegType = TJPF_GRAY;
+      pixelFormat = Pixel::L8;
+      break;
+    }
+    case TJCS_CMYK:
+    case TJCS_YCCK:
+    {
+      pixelLibJpegType = TJPF_CMYK;
+      pixelFormat = Pixel::RGBA8888;
+      break;
+    }
+    default:
+    {
+      pixelLibJpegType = TJPF_RGB;
+      pixelFormat = Pixel::RGB888;
+      break;
+    }
+  }
+#endif
   // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
+  bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
 
-  unsigned char * const bitmapPixelBuffer =  bitmap.GetPackedPixelsProfile()->ReserveBuffer(Pixel::RGB888, scaledPostXformWidth, scaledPostXformHeight);
+  // set metadata
+  GetImplementation(bitmap).SetMetadata( std::move(exifMap) );
 
-  // Allow early cancellation before decoding:
-  client.InterruptionPoint();
+  auto bitmapPixelBuffer = bitmap.GetBuffer();
 
-  if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, 0, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
+  if( tjDecompress2( jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>( bitmapPixelBuffer ), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags ) == -1 )
   {
     std::string errorString = tjGetErrorStr();
 
     if( IsJpegErrorFatal( errorString ) )
     {
-        DALI_LOG_ERROR("%s\n", errorString.c_str());
+        DALI_LOG_ERROR("%s\n", errorString.c_str() );
         return false;
     }
     else
     {
-        DALI_LOG_WARNING("%s\n", errorString.c_str());
+        DALI_LOG_WARNING("%s\n", errorString.c_str() );
     }
   }
 
   const unsigned int  bufferWidth  = GetTextureDimension( scaledPreXformWidth );
   const unsigned int  bufferHeight = GetTextureDimension( scaledPreXformHeight );
 
-  if( transform != JPGFORM_NONE )
-  {
-    // Allow early cancellation before shuffling pixels around on the CPU:
-    client.InterruptionPoint();
-  }
-
   bool result = false;
   switch(transform)
   {
-    case JPGFORM_NONE:
+    case JpegTransform::NONE:
     {
       result = true;
       break;
     }
     // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
-    case JPGFORM_ROT_180:
+    case JpegTransform::ROTATE_180:
     {
-      result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
+      static auto rotate180Functions = TransformFunctionArray {
+        &Rotate180<1>,
+        &Rotate180<3>,
+        &Rotate180<4>,
+      };
+      result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-    case JPGFORM_ROT_270:
+    case JpegTransform::ROTATE_270:
     {
-      result = JpegRotate270(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
+      static auto rotate270Functions = TransformFunctionArray {
+        &Rotate270<1>,
+        &Rotate270<3>,
+        &Rotate270<4>,
+      };
+      result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-    case JPGFORM_ROT_90:
+    case JpegTransform::ROTATE_90:
     {
-      result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
+      static auto rotate90Functions = TransformFunctionArray {
+        &Rotate90<1>,
+        &Rotate90<3>,
+        &Rotate90<4>,
+      };
+      result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-    /// Less-common orientation changes, since they don't correspond to a camera's
-    // physical orientation:
-    case JPGFORM_FLIP_H:
-    case JPGFORM_FLIP_V:
-    case JPGFORM_TRANSPOSE:
-    case JPGFORM_TRANSVERSE:
+    case JpegTransform::FLIP_VERTICAL:
     {
-      DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
+      static auto flipVerticalFunctions = TransformFunctionArray {
+        &FlipVertical<1>,
+        &FlipVertical<3>,
+        &FlipVertical<4>,
+      };
+      result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-  }
-  return result;
-}
-
-///@Todo: Move all these rotation functions to portable/image-operations and take "Jpeg" out of their names.
-bool JpegRotate90(unsigned char *buffer, int width, int height, int bpp)
-{
-  int  w, iw, ih, hw = 0;
-  int ix, iy = 0;
-  iw = width;
-  ih = height;
-  Vector<unsigned char> data;
-  data.Reserve(width * height * bpp);
-  unsigned char *dataPtr = data.Begin();
-  memcpy(dataPtr, buffer, width * height * bpp);
-  w = ih;
-  ih = iw;
-  iw = w;
-  hw = iw * ih;
-  hw = - hw - 1;
-  switch(bpp)
-  {
-    case 3:
+    // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
+    case JpegTransform::FLIP_HORIZONTAL:
     {
-      RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + iw - 1;
-      RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
-
-      for(ix = iw; -- ix >= 0;)
-      {
-        for(iy = ih; -- iy >= 0; ++from )
-        {
-          *to = *from;
-          to += iw;
-        }
-        to += hw;
-      }
+      static auto flipHorizontalFunctions = TransformFunctionArray {
+        &FlipHorizontal<1>,
+        &FlipHorizontal<3>,
+        &FlipHorizontal<4>,
+      };
+      result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-
-    default:
+    case JpegTransform::TRANSPOSE:
     {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool JpegRotate180(unsigned char *buffer, int width, int height, int bpp)
-{
-  int  ix, iw, ih, hw = 0;
-  iw = width;
-  ih = height;
-  hw = iw * ih;
-  ix = hw;
-
-  switch(bpp)
-  {
-    case 3:
-    {
-      RGB888Type tmp;
-      RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) ;
-      RGB888Type* from = reinterpret_cast<RGB888Type*>( buffer ) + hw - 1;
-      for(; --ix >= (hw / 2); ++to, --from)
-      {
-        tmp = *to;
-        *to = *from;
-        *from = tmp;
-      }
+      static auto transposeFunctions = TransformFunctionArray {
+        &Transpose<1>,
+        &Transpose<3>,
+        &Transpose<4>,
+      };
+      result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
-
-    default:
-    {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp)
-{
-  int  w, iw, ih, hw = 0;
-  int ix, iy = 0;
-
-  iw = width;
-  ih = height;
-  Vector<unsigned char> data;
-  data.Reserve(width * height * bpp);
-  unsigned char *dataPtr = data.Begin();
-  memcpy(dataPtr, buffer, width * height * bpp);
-  w = ih;
-  ih = iw;
-  iw = w;
-  hw = iw * ih;
-
-  switch(bpp)
-  {
-    case 3:
+    case JpegTransform::TRANSVERSE:
     {
-      RGB888Type* to = reinterpret_cast<RGB888Type*>(buffer) + hw  - iw;
-      RGB888Type* from = reinterpret_cast<RGB888Type*>( dataPtr );
-
-      w = -w;
-      hw =  hw + 1;
-      for(ix = iw; -- ix >= 0;)
-      {
-        for(iy = ih; -- iy >= 0;)
-        {
-          *to = *from;
-          from += 1;
-          to += w;
-        }
-        to += hw;
-      }
+      static auto transverseFunctions = TransformFunctionArray {
+        &Transverse<1>,
+        &Transverse<3>,
+        &Transverse<4>,
+      };
+      result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat );
       break;
     }
     default:
     {
-      return false;
+      DALI_LOG_ERROR( "Unsupported JPEG Orientation transformation: %x.\n", transform );
+      break;
     }
   }
 
-  return true;
+  return result;
 }
 
 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 )
 {
+
   if( !pixelBuffer )
   {
     DALI_LOG_ERROR("Null input buffer\n");
@@ -581,39 +870,44 @@ bool EncodeToJpeg( const unsigned char* const pixelBuffer, Vector< unsigned char
   }
 
   // Initialise a JPEG codec:
-  AutoJpg autoJpg( tjInitCompress() );
   {
-    if( autoJpg.GetHandle() == NULL )
+    auto jpeg = MakeJpegCompressor();
+    if( jpeg )
     {
       DALI_LOG_ERROR( "JPEG Compressor init failed: %s\n", tjGetErrorStr() );
       return false;
     }
 
+
+    // Safely wrap the jpeg codec's buffer in case we are about to throw, then
+    // save the pixels to a persistent buffer that we own and let our cleaner
+    // class clean up the buffer as it goes out of scope:
+    auto dstBuffer = MakeJpegMemory();
+
     // Run the compressor:
-    unsigned char* dstBuffer = NULL;
     unsigned long dstBufferSize = 0;
     const int flags = 0;
 
-    if( tjCompress2( autoJpg.GetHandle(), const_cast<unsigned char*>(pixelBuffer), width, 0, height, jpegPixelFormat, &dstBuffer, &dstBufferSize, TJSAMP_444, quality, flags ) )
+    if( tjCompress2( jpeg.get(),
+                     const_cast<unsigned char*>(pixelBuffer),
+                     width, 0, height,
+                     jpegPixelFormat, SetPointer(dstBuffer), &dstBufferSize,
+                     TJSAMP_444, quality, flags ) )
     {
       DALI_LOG_ERROR("JPEG Compression failed: %s\n", tjGetErrorStr());
       return false;
     }
 
-    // Safely wrap the jpeg codec's buffer in case we are about to throw, then
-    // save the pixels to a persistent buffer that we own and let our cleaner
-    // class clean up the buffer as it goes out of scope:
-    AutoJpgMem cleaner( dstBuffer );
-    encodedPixels.Reserve( dstBufferSize );
-    memcpy( encodedPixels.Begin(), dstBuffer, dstBufferSize );
+    encodedPixels.Resize( dstBufferSize );
+    memcpy( encodedPixels.Begin(), dstBuffer.get(), dstBufferSize );
   }
   return true;
 }
 
 
-JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
+JpegTransform ConvertExifOrientation(ExifData* exifData)
 {
-  JPGFORM_CODE transform = JPGFORM_NONE;
+  auto transform = JpegTransform::NONE;
   ExifEntry * const entry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION);
   int orientation = 0;
   if( entry )
@@ -623,42 +917,42 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
     {
       case 1:
       {
-        transform = JPGFORM_NONE;
+        transform = JpegTransform::NONE;
         break;
       }
       case 2:
       {
-        transform = JPGFORM_FLIP_H;
+        transform = JpegTransform::FLIP_HORIZONTAL;
         break;
       }
       case 3:
       {
-        transform = JPGFORM_FLIP_V;
+        transform = JpegTransform::FLIP_VERTICAL;
         break;
       }
       case 4:
       {
-        transform = JPGFORM_TRANSPOSE;
+        transform = JpegTransform::TRANSPOSE;
         break;
       }
       case 5:
       {
-        transform = JPGFORM_TRANSVERSE;
+        transform = JpegTransform::TRANSVERSE;
         break;
       }
       case 6:
       {
-        transform = JPGFORM_ROT_90;
+        transform = JpegTransform::ROTATE_90;
         break;
       }
       case 7:
       {
-        transform = JPGFORM_ROT_180;
+        transform = JpegTransform::ROTATE_180;
         break;
       }
       case 8:
       {
-        transform = JPGFORM_ROT_270;
+        transform = JpegTransform::ROTATE_270;
         break;
       }
       default:
@@ -674,13 +968,13 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
 
 bool TransformSize( int requiredWidth, int requiredHeight,
                     FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
-                    JPGFORM_CODE transform,
+                    JpegTransform transform,
                     int& preXformImageWidth, int& preXformImageHeight,
                     int& postXformImageWidth, int& postXformImageHeight )
 {
   bool success = true;
 
-  if( transform == JPGFORM_ROT_90 || transform == JPGFORM_ROT_270 )
+  if( transform == JpegTransform::ROTATE_90 || transform == JpegTransform::ROTATE_270 || transform == JpegTransform::ROTATE_180 || transform == JpegTransform::TRANSVERSE)
   {
     std::swap( requiredWidth, requiredHeight );
     std::swap( postXformImageWidth, postXformImageHeight );
@@ -766,8 +1060,8 @@ bool TransformSize( int requiredWidth, int requiredHeight,
       // Continue downscaling to below maximum texture size (if possible)
       scaleFactorIndex = i;
 
-      if( TJSCALED(postXformImageWidth,  (factors[i])) < MAX_TEXTURE_WIDTH &&
-          TJSCALED(postXformImageHeight, (factors[i])) < MAX_TEXTURE_HEIGHT )
+      if( TJSCALED(postXformImageWidth,  (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) &&
+          TJSCALED(postXformImageHeight, (factors[i])) < static_cast< int >( Dali::GetMaxTextureSize() ) )
       {
         // Current scale-factor downscales to below maximum texture size
         break;
@@ -787,10 +1081,9 @@ bool TransformSize( int requiredWidth, int requiredHeight,
   return success;
 }
 
-ExifData* LoadExifData( FILE* fp )
+ExifHandle LoadExifData( FILE* fp )
 {
-  ExifData*     exifData=NULL;
-  ExifLoader*   exifLoader;
+  auto exifData = MakeNullExifData();
   unsigned char dataBuffer[1024];
 
   if( fseek( fp, 0, SEEK_SET ) )
@@ -799,7 +1092,8 @@ ExifData* LoadExifData( FILE* fp )
   }
   else
   {
-    exifLoader = exif_loader_new ();
+    auto exifLoader = std::unique_ptr<ExifLoader, decltype(exif_loader_unref)*>{
+        exif_loader_new(), exif_loader_unref };
 
     while( !feof(fp) )
     {
@@ -808,14 +1102,13 @@ ExifData* LoadExifData( FILE* fp )
       {
         break;
       }
-      if( ! exif_loader_write( exifLoader, dataBuffer, size ) )
+      if( ! exif_loader_write( exifLoader.get(), dataBuffer, size ) )
       {
         break;
       }
     }
 
-    exifData = exif_loader_get_data( exifLoader );
-    exif_loader_unref( exifLoader );
+    exifData.reset( exif_loader_get_data( exifLoader.get() ) );
   }
 
   return exifData;
@@ -839,14 +1132,14 @@ bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsig
     unsigned int headerHeight;
     if( LoadJpegHeader( fp, headerWidth, headerHeight ) )
     {
-      JPGFORM_CODE transform = JPGFORM_NONE;
+      auto transform = JpegTransform::NONE;
 
       if( input.reorientationRequested )
       {
-        ExifAutoPtr exifData( LoadExifData( fp ) );
-        if( exifData.mData )
+        auto exifData = LoadExifData( fp );
+        if( exifData )
         {
-          transform = ConvertExifOrientation(exifData.mData);
+          transform = ConvertExifOrientation(exifData.get());
         }
 
         int preXformImageWidth = headerWidth;