Ignore non-fatal libJPEGTurbo decode warnings / errors
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / tizen / image-loaders / loader-jpeg-turbo.cpp
index a18e45a..455d1f7 100755 (executable)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2015 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.
@@ -21,6 +21,7 @@
 #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 <libexif/exif-data.h>
@@ -31,7 +32,6 @@
 #include <cstring>
 #include <setjmp.h>
 
-
 namespace Dali
 {
 using Integration::Bitmap;
@@ -93,6 +93,21 @@ namespace
     /* 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 ) )
+    {
+      return false;
+    }
+    return true;
+  }
+
+
   /** Simple struct to ensure xif data is deleted. */
   struct ExifAutoPtr
   {
@@ -168,7 +183,9 @@ 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);
-bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transform,
+bool TransformSize( int requiredWidth, int requiredHeight,
+                    FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
+                    JPGFORM_CODE transform,
                     int& preXformImageWidth, int& preXformImageHeight,
                     int& postXformImageWidth, int& postXformImageHeight );
 
@@ -238,22 +255,22 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
     return false;
   }
 
-  std::vector<unsigned char> jpegBuffer(0);
+  Vector<unsigned char> jpegBuffer;
   try
   {
-    jpegBuffer.reserve( jpegBufferSize );
+    jpegBuffer.Reserve( 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[0];
+  unsigned char * const jpegBufferPtr = jpegBuffer.Begin();
 
   // Pull the compressed JPEG image bytes out of a file and into memory:
   if( fread( jpegBufferPtr, 1, jpegBufferSize, fp ) != jpegBufferSize )
   {
-    DALI_LOG_WARNING("Error on image file read.");
+    DALI_LOG_WARNING("Error on image file read.\n");
     return false;
   }
 
@@ -295,7 +312,7 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
 
   if(preXformImageWidth == 0 || preXformImageHeight == 0)
   {
-    DALI_LOG_WARNING("Invalid Image!");
+    DALI_LOG_WARNING("Invalid Image!\n");
     return false;
   }
 
@@ -314,7 +331,10 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
   int scaledPostXformWidth  = postXformImageWidth;
   int scaledPostXformHeight = postXformImageHeight;
 
-  TransformSize( requiredWidth, requiredHeight, transform,
+  TransformSize( requiredWidth, requiredHeight,
+                 input.scalingParameters.scalingMode,
+                 input.scalingParameters.samplingMode,
+                 transform,
                  scaledPreXformWidth, scaledPreXformHeight,
                  scaledPostXformWidth, scaledPostXformHeight );
 
@@ -325,11 +345,19 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
   // Allow early cancellation before decoding:
   client.InterruptionPoint();
 
-  const int pitch = scaledPreXformWidth * DECODED_PIXEL_SIZE;
-  if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, pitch, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
+  if( tjDecompress2( autoJpg.GetHandle(), jpegBufferPtr, jpegBufferSize, bitmapPixelBuffer, scaledPreXformWidth, 0, scaledPreXformHeight, DECODED_PIXEL_LIBJPEG_TYPE, flags ) == -1 )
   {
-    DALI_LOG_ERROR("%s\n", tjGetErrorStr());
-    return false;
+    std::string errorString = tjGetErrorStr();
+
+    if( IsJpegErrorFatal( errorString ) )
+    {
+        DALI_LOG_ERROR("%s\n", errorString.c_str());
+        return false;
+    }
+    else
+    {
+        DALI_LOG_WARNING("%s\n", errorString.c_str());
+    }
   }
 
   const unsigned int  bufferWidth  = GetTextureDimension( scaledPreXformWidth );
@@ -344,19 +372,12 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
   bool result = false;
   switch(transform)
   {
-    case JPGFORM_FLIP_H:
-    case JPGFORM_FLIP_V:
-    case JPGFORM_TRANSPOSE:
-    case JPGFORM_TRANSVERSE: ///!ToDo: None of these are supported!
-    {
-      DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
-      break;
-    }
     case JPGFORM_NONE:
     {
       result = true;
       break;
     }
+    // 3 orientation changes for a camera held perpendicular to the ground or upside-down:
     case JPGFORM_ROT_180:
     {
       result = JpegRotate180(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
@@ -372,6 +393,16 @@ bool LoadBitmapFromJpeg( const ResourceLoadingClient& client, const ImageLoader:
       result = JpegRotate90(bitmapPixelBuffer, bufferWidth, bufferHeight, DECODED_PIXEL_SIZE);
       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:
+    {
+      DALI_LOG_WARNING( "Unsupported JPEG Orientation transformation: %x.\n", transform );
+      break;
+    }
   }
   return result;
 }
@@ -383,8 +414,9 @@ bool JpegRotate90(unsigned char *buffer, int width, int height, int bpp)
   int ix, iy = 0;
   iw = width;
   ih = height;
-  std::vector<unsigned char> data(width * height * bpp);
-  unsigned char *dataPtr = &data[0];
+  Vector<unsigned char> data;
+  data.Reserve(width * height * bpp);
+  unsigned char *dataPtr = data.Begin();
   memcpy(dataPtr, buffer, width * height * bpp);
   w = ih;
   ih = iw;
@@ -459,8 +491,9 @@ bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp)
 
   iw = width;
   ih = height;
-  std::vector<unsigned char> data(width * height * bpp);
-  unsigned char *dataPtr = &data[0];
+  Vector<unsigned char> data;
+  data.Reserve(width * height * bpp);
+  unsigned char *dataPtr = data.Begin();
   memcpy(dataPtr, buffer, width * height * bpp);
   w = ih;
   ih = iw;
@@ -497,7 +530,8 @@ bool JpegRotate270(unsigned char *buffer, int width, int height, int bpp)
   return true;
 }
 
-bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned char >& encodedPixels, const std::size_t width, const std::size_t height, const Pixel::Format pixelFormat, unsigned quality)
+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 )
   {
@@ -529,7 +563,7 @@ bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned
     }
     default:
     {
-      DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG." );
+      DALI_LOG_ERROR( "Unsupported pixel format for encoding to JPEG.\n" );
       return false;
     }
   }
@@ -569,9 +603,9 @@ bool EncodeToJpeg( const unsigned char* const pixelBuffer, std::vector< unsigned
     // 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.resize(dstBufferSize);
-    memcpy(&encodedPixels[0], dstBuffer, dstBufferSize);
+    AutoJpgMem cleaner( dstBuffer );
+    encodedPixels.Reserve( dstBufferSize );
+    memcpy( encodedPixels.Begin(), dstBuffer, dstBufferSize );
   }
   return true;
 }
@@ -630,7 +664,7 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
       default:
       {
         // Try to keep loading the file, but let app developer know there was something fishy:
-        DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.", entry );
+        DALI_LOG_WARNING( "Incorrect/Unknown Orientation setting found in EXIF header of JPEG image (%x). Orientation setting will be ignored.\n", entry );
         break;
       }
     }
@@ -638,7 +672,9 @@ JPGFORM_CODE ConvertExifOrientation(ExifData* exifData)
   return transform;
 }
 
-bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transform,
+bool TransformSize( int requiredWidth, int requiredHeight,
+                    FittingMode::Type fittingMode, SamplingMode::Type samplingMode,
+                    JPGFORM_CODE transform,
                     int& preXformImageWidth, int& preXformImageHeight,
                     int& postXformImageWidth, int& postXformImageHeight )
 {
@@ -650,6 +686,11 @@ bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transfor
     std::swap( postXformImageWidth, postXformImageHeight );
   }
 
+  // Apply the special rules for when there are one or two zeros in requested dimensions:
+  const ImageDimensions correctedDesired = Internal::Platform::CalculateDesiredDimensions( ImageDimensions( postXformImageWidth, postXformImageHeight), ImageDimensions( requiredWidth, requiredHeight ) );
+  requiredWidth = correctedDesired.GetWidth();
+  requiredHeight = correctedDesired.GetHeight();
+
   // Rescale image during decode using one of the decoder's built-in rescaling
   // ratios (expected to be powers of 2), keeping the final image at least as
   // wide and high as was requested:
@@ -658,40 +699,68 @@ bool TransformSize( int requiredWidth, int requiredHeight, JPGFORM_CODE transfor
   tjscalingfactor* factors = tjGetScalingFactors( &numFactors );
   if( factors == NULL )
   {
-    DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!");
+    DALI_LOG_WARNING("TurboJpeg tjGetScalingFactors error!\n");
     success = false;
   }
   else
   {
-    // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
-    int scaleFactorIndex( 0 );
-    for( int i = 1; i < numFactors; ++i )
+    // Internal jpeg downscaling is the same as our BOX_X sampling modes so only
+    // apply it if the application requested one of those:
+    // (use a switch case here so this code will fail to compile if other modes are added)
+    bool downscale = true;
+    switch( samplingMode )
     {
-      // if requested width or height set to 0, ignore value
-      // TJSCALED performs an integer-based ceil operation on (dim*factor)
-      if( (requiredWidth  && TJSCALED(postXformImageWidth , (factors[i])) > requiredWidth) ||
-          (requiredHeight && TJSCALED(postXformImageHeight, (factors[i])) > requiredHeight) )
+      case SamplingMode::BOX:
+      case SamplingMode::BOX_THEN_NEAREST:
+      case SamplingMode::BOX_THEN_LINEAR:
+      case SamplingMode::DONT_CARE:
       {
-        scaleFactorIndex = i;
+        downscale = true;
+        break;
       }
-      else
+      case SamplingMode::NO_FILTER:
+      case SamplingMode::NEAREST:
+      case SamplingMode::LINEAR:
       {
-        // This scaling would result in an image that was smaller than requested in both
-        // dimensions, so stop at the previous entry.
+        downscale = false;
         break;
       }
     }
 
-    // Workaround for libjpeg-turbo problem adding a green line on one edge
-    // when downscaling to 1/8 in each dimension. Prefer not to scale to less than
-    // 1/4 in each dimension:
-    if( 2 < scaleFactorIndex )
+    int scaleFactorIndex( 0 );
+    if( downscale )
     {
-      scaleFactorIndex = 2;
-      DALI_LOG_INFO( gLoaderFilter, Debug::General, "Down-scaling requested for image limited to 1/4.\n" );
+      // Find nearest supported scaling factor (factors are in sequential order, getting smaller)
+      for( int i = 1; i < numFactors; ++i )
+      {
+        bool widthLessRequired  = TJSCALED( postXformImageWidth,  factors[i]) < requiredWidth;
+        bool heightLessRequired = TJSCALED( postXformImageHeight, factors[i]) < requiredHeight;
+        // If either scaled dimension is smaller than the desired one, we were done at the last iteration
+        if ( (fittingMode == FittingMode::SCALE_TO_FILL) && (widthLessRequired || heightLessRequired) )
+        {
+          break;
+        }
+        // If both dimensions are smaller than the desired one, we were done at the last iteration:
+        if ( (fittingMode == FittingMode::SHRINK_TO_FIT) && ( widthLessRequired && heightLessRequired ) )
+        {
+          break;
+        }
+        // If the width is smaller than the desired one, we were done at the last iteration:
+        if ( fittingMode == FittingMode::FIT_WIDTH && widthLessRequired )
+        {
+          break;
+        }
+        // If the width is smaller than the desired one, we were done at the last iteration:
+        if ( fittingMode == FittingMode::FIT_HEIGHT && heightLessRequired )
+        {
+          break;
+        }
+        // This factor stays is within our fitting mode constraint so remember it:
+        scaleFactorIndex = i;
+      }
     }
 
-    // Regardless of requested size, downscale to avoid exceeding the maximum texture size
+    // Regardless of requested size, downscale to avoid exceeding the maximum texture size:
     for( int i = scaleFactorIndex; i < numFactors; ++i )
     {
       // Continue downscaling to below maximum texture size (if possible)
@@ -785,7 +854,7 @@ bool LoadJpegHeader( const ImageLoader::Input& input, unsigned int& width, unsig
         int postXformImageWidth = headerWidth;
         int postXformImageHeight = headerHeight;
 
-        success = TransformSize(requiredWidth, requiredHeight, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight);
+        success = TransformSize( requiredWidth, requiredHeight, input.scalingParameters.scalingMode, input.scalingParameters.samplingMode, transform, preXformImageWidth, preXformImageHeight, postXformImageWidth, postXformImageHeight );
         if(success)
         {
           width = postXformImageWidth;