Changed alpha mask scaling to use Lanczos.
[platform/core/uifw/dali-adaptor.git] / platform-abstractions / portable / image-operations.cpp
index 6a5ee4b..f759d39 100644 (file)
@@ -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.
 #include <cstring>
 #include <stddef.h>
 #include <cmath>
+#include <limits>
 #include <dali/integration-api/debug.h>
+#include <dali/public-api/common/dali-vector.h>
 #include <dali/public-api/math/vector2.h>
+#include <resampler.h>
+#include <image-loading.h>
 
 // INTERNAL INCLUDES
 
@@ -43,6 +47,11 @@ const uint8_t BORDER_FILL_VALUE( 0x00 );
 // A maximum size limit for newly created bitmaps. ( 1u << 16 ) - 1 is chosen as we are using 16bit words for dimensions.
 const unsigned int MAXIMUM_TARGET_BITMAP_SIZE( ( 1u << 16 ) - 1 );
 
+// Constants used by the ImageResampler.
+const float DEFAULT_SOURCE_GAMMA = 1.75f;   ///< Default source gamma value used in the Resampler() function. Partial gamma correction looks better on mips. Set to 1.0 to disable gamma correction.
+const float FILTER_SCALE = 1.f;             ///< Default filter scale value used in the Resampler() function. Filter scale - values < 1.0 cause aliasing, but create sharper looking mips.
+const Resampler::Filter FILTER_TYPE = Resampler::LANCZOS4; ///< Default filter used in the Resampler() function. Possible Lanczos filters are: lanczos3, lanczos4, lanczos6, lanczos12
+
 using Integration::Bitmap;
 using Integration::BitmapPtr;
 typedef unsigned char PixelBuffer;
@@ -152,7 +161,7 @@ inline void DebugAssertDualScanlineParameters( const uint8_t * const scanline1,
   DALI_ASSERT_DEBUG( scanline2 && "Null pointer." );
   DALI_ASSERT_DEBUG( outputScanline && "Null pointer." );
   DALI_ASSERT_DEBUG( ((scanline1 >= scanline2 + widthInComponents) || (scanline2 >= scanline1 + widthInComponents )) && "Scanlines alias." );
-  DALI_ASSERT_DEBUG( ((((void*)outputScanline) >= (void*)(scanline2 + widthInComponents)) || (((void*)scanline2) >= (void*)(scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
+  DALI_ASSERT_DEBUG( ((outputScanline >= (scanline2 + widthInComponents)) || (scanline2 >= (scanline1 + widthInComponents))) && "Scanline 2 aliases output." );
 }
 
 /**
@@ -441,24 +450,29 @@ BitmapPtr MakeBitmap( const uint8_t * const pixels, Pixel::Format pixelFormat, u
  */
 ImageDimensions CalculateDesiredDimensions( unsigned int bitmapWidth, unsigned int bitmapHeight, unsigned int requestedWidth, unsigned int requestedHeight )
 {
+  unsigned int maxSize = Dali::GetMaxTextureSize();
+
   // If no dimensions have been requested, default to the source ones:
   if( requestedWidth == 0 && requestedHeight == 0 )
   {
-    return ImageDimensions( bitmapWidth, bitmapHeight );
+    return ImageDimensions( std::min( bitmapWidth, maxSize ), std::min( bitmapHeight, maxSize ) );
   }
 
   // If both dimensions have values requested, use them both:
   if( requestedWidth != 0 && requestedHeight != 0 )
   {
-    return ImageDimensions( requestedWidth, requestedHeight );
+    return ImageDimensions( std::min( requestedWidth, maxSize ), std::min( requestedHeight, maxSize ) );
   }
 
   // Only one of the dimensions has been requested. Calculate the other from
   // the requested one and the source image aspect ratio:
   if( requestedWidth != 0 )
   {
+    requestedWidth = std::min( requestedWidth, maxSize );
     return ImageDimensions( requestedWidth, bitmapHeight / float(bitmapWidth) * requestedWidth + 0.5f );
   }
+
+  requestedHeight = std::min( requestedHeight, maxSize );
   return ImageDimensions( bitmapWidth / float(bitmapHeight) * requestedHeight + 0.5f, requestedHeight );
 }
 
@@ -1169,8 +1183,8 @@ inline void PointSampleAddressablePixels( const uint8_t * inPixels,
   DALI_ASSERT_DEBUG( ((desiredWidth <= inputWidth && desiredHeight <= inputHeight) ||
       outPixels >= inPixels + inputWidth * inputHeight * sizeof(PIXEL) || outPixels <= inPixels - desiredWidth * desiredHeight * sizeof(PIXEL)) &&
       "The input and output buffers must not overlap for an upscaling.");
-  DALI_ASSERT_DEBUG( ((uint64_t) inPixels)  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
-  DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+  DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+  DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels ) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
 
   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
   {
@@ -1412,8 +1426,8 @@ inline void LinearSampleGeneric( const unsigned char * __restrict__ inPixels,
                      "Input and output buffers cannot overlap.");
   if( DEBUG_ASSERT_ALIGNMENT )
   {
-    DALI_ASSERT_DEBUG( ((uint64_t) inPixels)  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
-    DALI_ASSERT_DEBUG( ((uint64_t) outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+    DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( inPixels )  % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
+    DALI_ASSERT_DEBUG( reinterpret_cast< uint64_t >( outPixels) % sizeof(PIXEL) == 0 && "Pixel pointers need to be aligned to the size of the pixels (E.g., 4 bytes for RGBA, 2 bytes for RGB565, ...)." );
   }
 
   if( inputWidth < 1u || inputHeight < 1u || desiredWidth < 1u || desiredHeight < 1u )
@@ -1509,6 +1523,200 @@ void LinearSample4BPP( const unsigned char * __restrict__ inPixels,
   LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>( inPixels, inputDimensions, outPixels, desiredDimensions );
 }
 
+void LanczosSample( const unsigned char * __restrict__ inPixels,
+                    ImageDimensions inputDimensions,
+                    unsigned char * __restrict__ outPixels,
+                    ImageDimensions desiredDimensions,
+                    int numChannels, bool hasAlpha )
+{
+  // Got from the test.cpp of the ImageResampler lib.
+  const float ONE_DIV_255 = 1.0f / 255.0f;
+  const int MAX_UNSIGNED_CHAR = std::numeric_limits<uint8_t>::max();
+  const int LINEAR_TO_SRGB_TABLE_SIZE = 4096;
+  const int ALPHA_CHANNEL = hasAlpha ? (numChannels-1) : 0;
+
+  static bool loadColorSpaces = true;
+  static float srgbToLinear[MAX_UNSIGNED_CHAR + 1];
+  static unsigned char linearToSrgb[LINEAR_TO_SRGB_TABLE_SIZE];
+
+  if( loadColorSpaces ) // Only create the color space conversions on the first execution
+  {
+    loadColorSpaces = false;
+
+    for( int i = 0; i <= MAX_UNSIGNED_CHAR; ++i )
+    {
+      srgbToLinear[i] = pow( static_cast<float>( i ) * ONE_DIV_255, DEFAULT_SOURCE_GAMMA );
+    }
+
+    const float invLinearToSrgbTableSize = 1.0f / static_cast<float>( LINEAR_TO_SRGB_TABLE_SIZE );
+    const float invSourceGamma = 1.0f / DEFAULT_SOURCE_GAMMA;
+
+    for( int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i )
+    {
+      int k = static_cast<int>( 255.0f * pow( static_cast<float>( i ) * invLinearToSrgbTableSize, invSourceGamma ) + 0.5f );
+      if( k < 0 )
+      {
+        k = 0;
+      }
+      else if( k > MAX_UNSIGNED_CHAR )
+      {
+        k = MAX_UNSIGNED_CHAR;
+      }
+      linearToSrgb[i] = static_cast<unsigned char>( k );
+    }
+  }
+
+  Resampler* resamplers[numChannels];
+  Vector<float> samples[numChannels];
+
+  const int srcWidth = inputDimensions.GetWidth();
+  const int srcHeight = inputDimensions.GetHeight();
+  const int dstWidth = desiredDimensions.GetWidth();
+  const int dstHeight = desiredDimensions.GetHeight();
+
+  // Now create a Resampler instance for each component to process. The first instance will create new contributor tables, which are shared by the resamplers
+  // used for the other components (a memory and slight cache efficiency optimization).
+  resamplers[0] = new Resampler( srcWidth,
+                                 srcHeight,
+                                 dstWidth,
+                                 dstHeight,
+                                 Resampler::BOUNDARY_CLAMP,
+                                 0.0f,           // sample_low,
+                                 1.0f,           // sample_high. Clamp output samples to specified range, or disable clamping if sample_low >= sample_high.
+                                 FILTER_TYPE,    // The type of filter. Currently Lanczos.
+                                 NULL,           // Pclist_x,
+                                 NULL,           // Pclist_y. Optional pointers to contributor lists from another instance of a Resampler.
+                                 FILTER_SCALE,   // src_x_ofs,
+                                 FILTER_SCALE ); // src_y_ofs. Offset input image by specified amount (fractional values okay).
+  samples[0].Resize( srcWidth );
+  for( int i = 1; i < numChannels; ++i )
+  {
+    resamplers[i] = new Resampler( srcWidth,
+                                   srcHeight,
+                                   dstWidth,
+                                   dstHeight,
+                                   Resampler::BOUNDARY_CLAMP,
+                                   0.0f,
+                                   1.0f,
+                                   FILTER_TYPE,
+                                   resamplers[0]->get_clist_x(),
+                                   resamplers[0]->get_clist_y(),
+                                   FILTER_SCALE,
+                                   FILTER_SCALE );
+    samples[i].Resize( srcWidth );
+  }
+
+  const int srcPitch = srcWidth * numChannels;
+  const int dstPitch = dstWidth * numChannels;
+  int dstY = 0;
+
+  for( int srcY = 0; srcY < srcHeight; ++srcY )
+  {
+    const unsigned char* pSrc = &inPixels[srcY * srcPitch];
+
+    for( int x = 0; x < srcWidth; ++x )
+    {
+      for( int c = 0; c < numChannels; ++c )
+      {
+        if( c == ALPHA_CHANNEL && hasAlpha )
+        {
+          samples[c][x] = *pSrc++ * ONE_DIV_255;
+        }
+        else
+        {
+          samples[c][x] = srgbToLinear[*pSrc++];
+        }
+      }
+    }
+
+    for( int c = 0; c < numChannels; ++c )
+    {
+      if( !resamplers[c]->put_line( &samples[c][0] ) )
+      {
+        DALI_ASSERT_DEBUG( !"Out of memory" );
+      }
+    }
+
+    for(;;)
+    {
+      int compIndex;
+      for( compIndex = 0; compIndex < numChannels; ++compIndex )
+      {
+        const float* pOutputSamples = resamplers[compIndex]->get_line();
+        if( !pOutputSamples )
+        {
+          break;
+        }
+
+        const bool isAlphaChannel = ( compIndex == ALPHA_CHANNEL && hasAlpha );
+        DALI_ASSERT_DEBUG( dstY < dstHeight );
+        unsigned char* pDst = &outPixels[dstY * dstPitch + compIndex];
+
+        for( int x = 0; x < dstWidth; ++x )
+        {
+          if( isAlphaChannel )
+          {
+            int c = static_cast<int>( 255.0f * pOutputSamples[x] + 0.5f );
+            if( c < 0 )
+            {
+              c = 0;
+            }
+            else if( c > MAX_UNSIGNED_CHAR )
+            {
+              c = MAX_UNSIGNED_CHAR;
+            }
+            *pDst = static_cast<unsigned char>( c );
+          }
+          else
+          {
+            int j = static_cast<int>( LINEAR_TO_SRGB_TABLE_SIZE * pOutputSamples[x] + 0.5f );
+            if( j < 0 )
+            {
+              j = 0;
+            }
+            else if( j >= LINEAR_TO_SRGB_TABLE_SIZE )
+            {
+              j = LINEAR_TO_SRGB_TABLE_SIZE - 1;
+            }
+            *pDst = linearToSrgb[j];
+          }
+
+          pDst += numChannels;
+        }
+      }
+      if( compIndex < numChannels )
+      {
+        break;
+      }
+
+      ++dstY;
+    }
+  }
+
+  // Delete the resamplers.
+  for( int i = 0; i < numChannels; ++i )
+  {
+    delete resamplers[i];
+  }
+}
+
+void LanczosSample4BPP( const unsigned char * __restrict__ inPixels,
+                        ImageDimensions inputDimensions,
+                        unsigned char * __restrict__ outPixels,
+                        ImageDimensions desiredDimensions )
+{
+  LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 4, true );
+}
+
+void LanczosSample1BPP( const unsigned char * __restrict__ inPixels,
+                        ImageDimensions inputDimensions,
+                        unsigned char * __restrict__ outPixels,
+                        ImageDimensions desiredDimensions )
+{
+  // For L8 images
+  LanczosSample( inPixels, inputDimensions, outPixels, desiredDimensions, 1, false );
+}
+
 // Dispatch to a format-appropriate linear sampling function:
 void LinearSample( const unsigned char * __restrict__ inPixels,
                    ImageDimensions inDimensions,