Support YUV decoding for JPEG
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-jpeg-turbo.cpp
index ba57d0f..dc39f24 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
@@ -113,6 +113,22 @@ bool IsJpegErrorFatal(const std::string& errorMessage)
   return true;
 }
 
+bool IsJpegDecodingFailed()
+{
+  std::string errorString = tjGetErrorStr();
+
+  if(DALI_UNLIKELY(IsJpegErrorFatal(errorString)))
+  {
+    DALI_LOG_ERROR("%s\n", errorString.c_str());
+    return true;
+  }
+  else
+  {
+    DALI_LOG_WARNING("%s\n", errorString.c_str());
+    return false;
+  }
+}
+
 // helpers for safe exif memory handling
 using ExifHandle = std::unique_ptr<ExifData, decltype(exif_data_free)*>;
 
@@ -375,7 +391,7 @@ void Transpose(PixelArray buffer, int width, int height)
 {
   using PixelT = PixelType<N>;
   Vector<PixelT> data;
-  data.Resize(width * height);
+  data.ResizeUninitialized(width * height);
   auto dataPtr = data.Begin();
 
   auto original = reinterpret_cast<PixelT*>(buffer);
@@ -397,7 +413,7 @@ void Rotate90(PixelArray buffer, int width, int height)
 {
   using PixelT = PixelType<N>;
   Vector<PixelT> data;
-  data.Resize(width * height);
+  data.ResizeUninitialized(width * height);
   auto dataPtr = data.Begin();
 
   auto original = reinterpret_cast<PixelT*>(buffer);
@@ -426,7 +442,7 @@ void Transverse(PixelArray buffer, int width, int height)
 {
   using PixelT = PixelType<N>;
   Vector<PixelT> data;
-  data.Resize(width * height);
+  data.ResizeUninitialized(width * height);
   auto dataPtr = data.Begin();
 
   auto original = reinterpret_cast<PixelT*>(buffer);
@@ -449,7 +465,7 @@ void Rotate270(PixelArray buffer, int width, int height)
 {
   using PixelT = PixelType<N>;
   Vector<PixelT> data;
-  data.Resize(width * height);
+  data.ResizeUninitialized(width * height);
   auto dataPtr = data.Begin();
 
   auto original = reinterpret_cast<PixelT*>(buffer);
@@ -476,12 +492,202 @@ void Rotate270(PixelArray buffer, int width, int height)
   }
 }
 
+void GetJpegPixelFormat(int jpegColorspace, TJPF& pixelLibJpegType, Pixel::Format& pixelFormat)
+{
+  pixelLibJpegType = TJPF_RGB;
+  pixelFormat      = Pixel::RGB888;
+
+  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;
+    }
+  }
+}
+
+bool TransformBitmap(int scaledPreXformWidth, int scaledPreXformHeight, JpegTransform transform, uint8_t* bitmapPixelBuffer, Pixel::Format pixelFormat)
+{
+  const unsigned int bufferWidth  = Dali::TizenPlatform::GetTextureDimension(scaledPreXformWidth);
+  const unsigned int bufferHeight = Dali::TizenPlatform::GetTextureDimension(scaledPreXformHeight);
+
+  bool result = false;
+
+  switch(transform)
+  {
+    case JpegTransform::NONE:
+    {
+      result = true;
+      break;
+    }
+    // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
+    case JpegTransform::ROTATE_180:
+    {
+      static auto rotate180Functions = TransformFunctionArray{
+        &Rotate180<1>,
+        &Rotate180<3>,
+        &Rotate180<4>,
+      };
+      result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    case JpegTransform::ROTATE_270:
+    {
+      static auto rotate270Functions = TransformFunctionArray{
+        &Rotate270<1>,
+        &Rotate270<3>,
+        &Rotate270<4>,
+      };
+      result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    case JpegTransform::ROTATE_90:
+    {
+      static auto rotate90Functions = TransformFunctionArray{
+        &Rotate90<1>,
+        &Rotate90<3>,
+        &Rotate90<4>,
+      };
+      result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    case JpegTransform::FLIP_VERTICAL:
+    {
+      static auto flipVerticalFunctions = TransformFunctionArray{
+        &FlipVertical<1>,
+        &FlipVertical<3>,
+        &FlipVertical<4>,
+      };
+      result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
+    case JpegTransform::FLIP_HORIZONTAL:
+    {
+      static auto flipHorizontalFunctions = TransformFunctionArray{
+        &FlipHorizontal<1>,
+        &FlipHorizontal<3>,
+        &FlipHorizontal<4>,
+      };
+      result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    case JpegTransform::TRANSPOSE:
+    {
+      static auto transposeFunctions = TransformFunctionArray{
+        &Transpose<1>,
+        &Transpose<3>,
+        &Transpose<4>,
+      };
+      result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    case JpegTransform::TRANSVERSE:
+    {
+      static auto transverseFunctions = TransformFunctionArray{
+        &Transverse<1>,
+        &Transverse<3>,
+        &Transverse<4>,
+      };
+      result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
+      break;
+    }
+    default:
+    {
+      DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
+      break;
+    }
+  }
+  return result;
+}
+
+bool LoadJpegFile(const Dali::ImageLoader::Input& input, Vector<uint8_t>& jpegBuffer, unsigned int& jpegBufferSize)
+{
+  FILE* const fp = input.file;
+
+  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END)))
+  {
+    DALI_LOG_ERROR("Error seeking to end of file\n");
+    return false;
+  }
+
+  long positionIndicator = ftell(fp);
+  jpegBufferSize         = 0u;
+  if(positionIndicator > -1L)
+  {
+    jpegBufferSize = static_cast<unsigned int>(positionIndicator);
+  }
+
+  if(DALI_UNLIKELY(0u == jpegBufferSize))
+  {
+    DALI_LOG_ERROR("Jpeg buffer size error\n");
+    return false;
+  }
+
+  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+  {
+    DALI_LOG_ERROR("Error seeking to start of file\n");
+    return false;
+  }
+
+  try
+  {
+    jpegBuffer.ResizeUninitialized(jpegBufferSize);
+  }
+  catch(...)
+  {
+    DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
+    return false;
+  }
+  unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
+
+  // Pull the compressed JPEG image bytes out of a file and into memory:
+  if(DALI_UNLIKELY(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize))
+  {
+    DALI_LOG_ERROR("Error on image file read.\n");
+    return false;
+  }
+
+  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+  {
+    DALI_LOG_ERROR("Error seeking to start of file\n");
+    return false;
+  }
+
+  return true;
+}
+
 } // namespace
 
 namespace Dali
 {
 namespace TizenPlatform
 {
+bool          DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv);
 JpegTransform ConvertExifOrientation(ExifData* exifData);
 bool          TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fittingMode, SamplingMode::Type samplingMode, JpegTransform transform, int& preXformImageWidth, int& preXformImageHeight, int& postXformImageWidth, int& postXformImageHeight);
 
@@ -497,8 +703,9 @@ bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
 
   // On error exit from the JPEG lib, control will pass via JpegErrorHandler
   // into this branch body for cleanup and error return:
-  if(setjmp(jerr.jumpBuffer))
+  if(DALI_UNLIKELY(setjmp(jerr.jumpBuffer)))
   {
+    DALI_LOG_ERROR("setjmp failed\n");
     jpeg_destroy_decompress(&cinfo);
     return false;
   }
@@ -512,8 +719,9 @@ bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
   jpeg_stdio_src(&cinfo, fp);
 
   // Check header to see if it is  JPEG file
-  if(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK)
+  if(DALI_UNLIKELY(jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK))
   {
+    DALI_LOG_ERROR("jpeg_read_header failed\n");
     width = height = 0;
     jpeg_destroy_decompress(&cinfo);
     return false;
@@ -528,66 +736,45 @@ bool LoadJpegHeader(FILE* fp, unsigned int& width, unsigned int& height)
 
 bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
 {
-  const int   flags = 0;
-  FILE* const fp    = input.file;
+  std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
 
-  if(fseek(fp, 0, SEEK_END))
+  bool result = DecodeJpeg(input, pixelBuffers, false);
+  if(!result && pixelBuffers.empty())
   {
-    DALI_LOG_ERROR("Error seeking to end of file\n");
-    return false;
+    bitmap.Reset();
   }
-
-  long         positionIndicator = ftell(fp);
-  unsigned int jpegBufferSize    = 0u;
-  if(positionIndicator > -1L)
-  {
-    jpegBufferSize = static_cast<unsigned int>(positionIndicator);
-  }
-
-  if(0u == jpegBufferSize)
+  else
   {
-    return false;
+    bitmap = pixelBuffers[0];
   }
+  return result;
+}
 
-  if(fseek(fp, 0, SEEK_SET))
-  {
-    DALI_LOG_ERROR("Error seeking to start of file\n");
-    return false;
-  }
+bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
+{
+  return DecodeJpeg(input, pixelBuffers, true);
+}
 
-  Vector<unsigned char> jpegBuffer;
-  try
-  {
-    jpegBuffer.Resize(jpegBufferSize);
-  }
-  catch(...)
-  {
-    DALI_LOG_ERROR("Could not allocate temporary memory to hold JPEG file of size %uMB.\n", jpegBufferSize / 1048576U);
-    return false;
-  }
-  unsigned char* const jpegBufferPtr = jpegBuffer.Begin();
+bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
+{
+  Vector<uint8_t> jpegBuffer;
+  unsigned int    jpegBufferSize = 0u;
 
-  // Pull the compressed JPEG image bytes out of a file and into memory:
-  if(fread(jpegBufferPtr, 1, jpegBufferSize, fp) != jpegBufferSize)
+  if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
   {
-    DALI_LOG_WARNING("Error on image file read.\n");
+    DALI_LOG_ERROR("LoadJpegFile failed\n");
     return false;
   }
 
-  if(fseek(fp, 0, SEEK_SET))
-  {
-    DALI_LOG_ERROR("Error seeking to start of file\n");
-  }
-
   auto jpeg = MakeJpegDecompressor();
-
-  if(!jpeg)
+  if(DALI_UNLIKELY(!jpeg))
   {
     DALI_LOG_ERROR("%s\n", tjGetErrorStr());
     return false;
   }
 
-  auto transform = JpegTransform::NONE;
+  uint8_t* const jpegBufferPtr = jpegBuffer.Begin();
+  auto           transform     = JpegTransform::NONE;
 
   // extract exif data
   auto exifData = MakeExifDataFromData(jpegBufferPtr, jpegBufferSize);
@@ -597,47 +784,20 @@ bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::Pixe
     transform = ConvertExifOrientation(exifData.get());
   }
 
-  std::unique_ptr<Property::Map> exifMap;
-  exifMap.reset(new Property::Map());
-
-  for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
-  {
-    auto content = exifData->ifd[k];
-    for(auto i = 0u; i < content->count; ++i)
-    {
-      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;
-
-  // 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)
+  if(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
   {
-    DALI_LOG_WARNING("Invalid Image!\n");
+    DALI_LOG_ERROR("Invalid Image!\n");
     return false;
   }
 
@@ -657,153 +817,108 @@ bool LoadBitmapFromJpeg(const Dali::ImageLoader::Input& input, Dali::Devel::Pixe
 
   TransformSize(requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, scaledPreXformWidth, scaledPreXformHeight, scaledPostXformWidth, scaledPostXformHeight);
 
-  // Colorspace conversion options
-  TJPF          pixelLibJpegType = TJPF_RGB;
-  Pixel::Format pixelFormat      = Pixel::RGB888;
-#ifndef DALI_PROFILE_UBUNTU
-  switch(jpegColorspace)
+  bool result = false;
+
+  // Now we support YUV420 only
+  if(decodeToYuv && chrominanceSubsampling == TJSAMP_420 && transform == JpegTransform::NONE)
   {
-    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:
+    unsigned char* planes[3];
+
+    // Allocate buffers for each plane and decompress the jpeg buffer into the buffers
+    for(int i = 0; i < 3; i++)
     {
-      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);
+      auto planeSize = tjPlaneSizeYUV(i, scaledPostXformWidth, 0, scaledPostXformHeight, chrominanceSubsampling);
 
-  // set metadata
-  GetImplementation(bitmap).SetMetadata(std::move(exifMap));
+      unsigned char* buffer = static_cast<unsigned char*>(malloc(planeSize));
+      if(!buffer)
+      {
+        DALI_LOG_ERROR("Buffer allocation is failed [%d]\n", planeSize);
+        pixelBuffers.clear();
+        return false;
+      }
 
-  auto bitmapPixelBuffer = bitmap.GetBuffer();
+      int           width, height, planeWidth;
+      Pixel::Format pixelFormat = Pixel::RGB888;
 
-  if(tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags) == -1)
-  {
-    std::string errorString = tjGetErrorStr();
+      if(i == 0)
+      {
+        // luminance plane
+        width       = scaledPostXformWidth;
+        height      = scaledPostXformHeight;
+        planeWidth  = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
+        pixelFormat = Pixel::L8;
+      }
+      else
+      {
+        // chrominance plane
+        width       = tjPlaneWidth(i, scaledPostXformWidth, chrominanceSubsampling);
+        height      = tjPlaneHeight(i, scaledPostXformHeight, chrominanceSubsampling);
+        planeWidth  = width;
+        pixelFormat = (i == 1 ? Pixel::CHROMINANCE_U : Pixel::CHROMINANCE_V);
+      }
 
-    if(IsJpegErrorFatal(errorString))
-    {
-      DALI_LOG_ERROR("%s\n", errorString.c_str());
-      return false;
+      Internal::Adaptor::PixelBufferPtr internal = Internal::Adaptor::PixelBuffer::New(buffer, planeSize, width, height, planeWidth, pixelFormat);
+      Dali::Devel::PixelBuffer          bitmap   = Devel::PixelBuffer(internal.Get());
+      planes[i]                                  = buffer;
+      pixelBuffers.push_back(bitmap);
     }
-    else
+
+    const int flags = 0;
+
+    int decodeResult = tjDecompressToYUVPlanes(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char**>(&planes), scaledPostXformWidth, nullptr, scaledPostXformHeight, flags);
+    if(decodeResult == -1 && IsJpegDecodingFailed())
     {
-      DALI_LOG_WARNING("%s\n", errorString.c_str());
+      pixelBuffers.clear();
+      return false;
     }
+
+    result = true;
   }
+  else
+  {
+    // Colorspace conversion options
+    TJPF          pixelLibJpegType = TJPF_RGB;
+    Pixel::Format pixelFormat      = Pixel::RGB888;
 
-  const unsigned int bufferWidth  = GetTextureDimension(scaledPreXformWidth);
-  const unsigned int bufferHeight = GetTextureDimension(scaledPreXformHeight);
+    GetJpegPixelFormat(jpegColorspace, pixelLibJpegType, pixelFormat);
 
-  bool result = false;
-  switch(transform)
-  {
-    case JpegTransform::NONE:
-    {
-      result = true;
-      break;
-    }
-    // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
-    case JpegTransform::ROTATE_180:
-    {
-      static auto rotate180Functions = TransformFunctionArray{
-        &Rotate180<1>,
-        &Rotate180<3>,
-        &Rotate180<4>,
-      };
-      result = Transform(rotate180Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    case JpegTransform::ROTATE_270:
-    {
-      static auto rotate270Functions = TransformFunctionArray{
-        &Rotate270<1>,
-        &Rotate270<3>,
-        &Rotate270<4>,
-      };
-      result = Transform(rotate270Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    case JpegTransform::ROTATE_90:
-    {
-      static auto rotate90Functions = TransformFunctionArray{
-        &Rotate90<1>,
-        &Rotate90<3>,
-        &Rotate90<4>,
-      };
-      result = Transform(rotate90Functions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    case JpegTransform::FLIP_VERTICAL:
-    {
-      static auto flipVerticalFunctions = TransformFunctionArray{
-        &FlipVertical<1>,
-        &FlipVertical<3>,
-        &FlipVertical<4>,
-      };
-      result = Transform(flipVerticalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    // Less-common orientation changes, since they don't correspond to a camera's physical orientation:
-    case JpegTransform::FLIP_HORIZONTAL:
-    {
-      static auto flipHorizontalFunctions = TransformFunctionArray{
-        &FlipHorizontal<1>,
-        &FlipHorizontal<3>,
-        &FlipHorizontal<4>,
-      };
-      result = Transform(flipHorizontalFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    case JpegTransform::TRANSPOSE:
-    {
-      static auto transposeFunctions = TransformFunctionArray{
-        &Transpose<1>,
-        &Transpose<3>,
-        &Transpose<4>,
-      };
-      result = Transform(transposeFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
-    }
-    case JpegTransform::TRANSVERSE:
+    // Allocate a bitmap and decompress the jpeg buffer into its pixel buffer:
+    Dali::Devel::PixelBuffer bitmap = Dali::Devel::PixelBuffer::New(scaledPostXformWidth, scaledPostXformHeight, pixelFormat);
+
+    // Set metadata
+    if(DALI_LIKELY(exifData))
     {
-      static auto transverseFunctions = TransformFunctionArray{
-        &Transverse<1>,
-        &Transverse<3>,
-        &Transverse<4>,
-      };
-      result = Transform(transverseFunctions, bitmapPixelBuffer, bufferWidth, bufferHeight, pixelFormat);
-      break;
+      std::unique_ptr<Property::Map> exifMap = std::make_unique<Property::Map>();
+
+      for(auto k = 0u; k < EXIF_IFD_COUNT; ++k)
+      {
+        auto content = exifData->ifd[k];
+        for(auto i = 0u; i < content->count; ++i)
+        {
+          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));
+          }
+        }
+      }
+
+      GetImplementation(bitmap).SetMetadata(std::move(exifMap));
     }
-    default:
+
+    auto      bitmapPixelBuffer = bitmap.GetBuffer();
+    const int flags             = 0;
+
+    int decodeResult = tjDecompress2(jpeg.get(), jpegBufferPtr, jpegBufferSize, reinterpret_cast<unsigned char*>(bitmapPixelBuffer), scaledPreXformWidth, 0, scaledPreXformHeight, pixelLibJpegType, flags);
+    if(decodeResult == -1 && IsJpegDecodingFailed())
     {
-      DALI_LOG_ERROR("Unsupported JPEG Orientation transformation: %x.\n", transform);
-      break;
+      return false;
     }
+    pixelBuffers.push_back(bitmap);
+
+    // Transform bitmap
+    result = TransformBitmap(scaledPreXformWidth, scaledPreXformHeight, transform, bitmapPixelBuffer, pixelFormat);
   }
 
   return result;
@@ -861,7 +976,7 @@ bool EncodeToJpeg(const unsigned char* const pixelBuffer, Vector<unsigned char>&
   // Initialise a JPEG codec:
   {
     auto jpeg = MakeJpegCompressor();
-    if(!jpeg)
+    if(DALI_UNLIKELY(!jpeg))
     {
       DALI_LOG_ERROR("JPEG Compressor init failed: %s\n", tjGetErrorStr());
       return false;
@@ -876,23 +991,23 @@ bool EncodeToJpeg(const unsigned char* const pixelBuffer, Vector<unsigned char>&
     unsigned long dstBufferSize = 0;
     const int     flags         = 0;
 
-    if(tjCompress2(jpeg.get(),
-                   const_cast<unsigned char*>(pixelBuffer),
-                   width,
-                   0,
-                   height,
-                   jpegPixelFormat,
-                   SetPointer(dstBuffer),
-                   &dstBufferSize,
-                   TJSAMP_444,
-                   quality,
-                   flags))
+    if(DALI_UNLIKELY(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;
     }
 
-    encodedPixels.Resize(dstBufferSize);
+    encodedPixels.ResizeUninitialized(dstBufferSize);
     memcpy(encodedPixels.Begin(), dstBuffer.get(), dstBufferSize);
   }
   return true;
@@ -979,7 +1094,7 @@ bool TransformSize(int requiredWidth, int requiredHeight, FittingMode::Type fitt
 
   int              numFactors = 0;
   tjscalingfactor* factors    = tjGetScalingFactors(&numFactors);
-  if(factors == NULL)
+  if(DALI_UNLIKELY(factors == NULL))
   {
     DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
     success = false;
@@ -1074,7 +1189,7 @@ ExifHandle LoadExifData(FILE* fp)
   auto          exifData = MakeNullExifData();
   unsigned char dataBuffer[1024];
 
-  if(fseek(fp, 0, SEEK_SET))
+  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
   {
     DALI_LOG_ERROR("Error seeking to start of file\n");
   }
@@ -1114,7 +1229,7 @@ bool LoadJpegHeader(const Dali::ImageLoader::Input& input, unsigned int& width,
   unsigned int headerHeight;
 
   success = LoadJpegHeader(fp, headerWidth, headerHeight);
-  if(success)
+  if(DALI_LIKELY(success))
   {
     auto transform = JpegTransform::NONE;