Support YUV decoding for JPEG
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-jpeg-turbo.cpp
index 0571582..dc39f24 100644 (file)
@@ -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)*>;
 
@@ -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);
 
@@ -530,68 +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;
-
-  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_END)))
-  {
-    DALI_LOG_ERROR("Error seeking to end of file\n");
-    return false;
-  }
+  std::vector<Dali::Devel::PixelBuffer> pixelBuffers;
 
-  long         positionIndicator = ftell(fp);
-  unsigned int jpegBufferSize    = 0u;
-  if(positionIndicator > -1L)
+  bool result = DecodeJpeg(input, pixelBuffers, false);
+  if(!result && pixelBuffers.empty())
   {
-    jpegBufferSize = static_cast<unsigned int>(positionIndicator);
+    bitmap.Reset();
   }
-
-  if(DALI_UNLIKELY(0u == jpegBufferSize))
+  else
   {
-    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;
+    bitmap = pixelBuffers[0];
   }
+  return result;
+}
 
-  Vector<unsigned char> jpegBuffer;
-  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();
+bool LoadPlanesFromJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers)
+{
+  return DecodeJpeg(input, pixelBuffers, true);
+}
 
-  // 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;
-  }
+bool DecodeJpeg(const Dali::ImageLoader::Input& input, std::vector<Dali::Devel::PixelBuffer>& pixelBuffers, bool decodeToYuv)
+{
+  Vector<uint8_t> jpegBuffer;
+  unsigned int    jpegBufferSize = 0u;
 
-  if(DALI_UNLIKELY(fseek(fp, 0, SEEK_SET)))
+  if(!LoadJpegFile(input, jpegBuffer, jpegBufferSize))
   {
-    DALI_LOG_ERROR("Error seeking to start of file\n");
+    DALI_LOG_ERROR("LoadJpegFile failed\n");
     return false;
   }
 
   auto jpeg = MakeJpegDecompressor();
-
   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);
@@ -601,46 +784,16 @@ 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());
-
-  if(DALI_LIKELY(exifData))
-  {
-    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(DALI_UNLIKELY(preXformImageWidth == 0 || preXformImageHeight == 0))
   {
@@ -664,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(DALI_UNLIKELY(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;