[4.0] Add gaussian blur support in PixelBuffer 39/158539/1
authorRichard Huang <r.huang@samsung.com>
Tue, 24 Oct 2017 15:11:05 +0000 (16:11 +0100)
committerAdeel Kazmi <adeel.kazmi@samsung.com>
Wed, 1 Nov 2017 18:00:41 +0000 (18:00 +0000)
Change-Id: Ibc05b718340bf3f6c6e51746f9ee708a9d6ae137
Signed-off-by: Adeel Kazmi <adeel.kazmi@samsung.com>
adaptors/common/pixel-buffer-impl.cpp
adaptors/common/pixel-buffer-impl.h
adaptors/devel-api/adaptor-framework/pixel-buffer.cpp
adaptors/devel-api/adaptor-framework/pixel-buffer.h
automated-tests/src/dali-adaptor/utc-Dali-PixelBuffer.cpp
platform-abstractions/portable/gaussian-blur.cpp [new file with mode: 0644]
platform-abstractions/portable/gaussian-blur.h [new file with mode: 0644]
platform-abstractions/tizen/file.list

index 1de5382..24ea4bc 100644 (file)
@@ -25,6 +25,7 @@
 // INTERNAL INCLUDES
 #include "pixel-manipulation.h"
 #include "alpha-mask.h"
+#include "gaussian-blur.h"
 #include <platform-abstractions/portable/image-operations.h>
 
 namespace Dali
@@ -309,6 +310,22 @@ PixelBufferPtr PixelBuffer::NewResize( const PixelBuffer& inBuffer, ImageDimensi
   return outBuffer;
 }
 
+void PixelBuffer::ApplyGaussianBlur( const float blurRadius )
+{
+  // This method only works for pixel buffer in RGBA format.
+  if( mWidth > 0 && mHeight > 0 && mPixelFormat == Pixel::RGBA8888 )
+  {
+    if ( blurRadius > Math::MACHINE_EPSILON_1 )
+    {
+      PerformGaussianBlurRGBA( *this, blurRadius );
+    }
+  }
+  else
+  {
+    DALI_LOG_ERROR( "Trying to apply gaussian blur to an empty pixel buffer or a pixel buffer not in RGBA format" );
+  }
+}
+
 }// namespace Adaptor
 }// namespace Internal
 }// namespace Dali
index ec7ee2b..3484416 100644 (file)
@@ -150,6 +150,13 @@ public:
    */
   void ApplyMask( const PixelBuffer& mask, float contentScale, bool cropToMask );
 
+  /**
+   * Apply a Gaussian blur to the current buffer with the given radius.
+   *
+   * @param[in] blurRadius The radius for Gaussian blur
+   */
+  void ApplyGaussianBlur( const float blurRadius );
+
 private:
   /*
    * Undefined copy constructor.
index 0b366d0..b767d49 100644 (file)
@@ -101,6 +101,11 @@ void PixelBuffer::ApplyMask( PixelBuffer mask, float contentScale, bool cropToMa
   GetImplementation(*this).ApplyMask( GetImplementation( mask ), contentScale, cropToMask );
 }
 
+void PixelBuffer::ApplyGaussianBlur( const float blurRadius )
+{
+  GetImplementation(*this).ApplyGaussianBlur( blurRadius );
+}
+
 } // namespace Devel
 
 } // namespace Dali
index 91c7161..dfe228b 100644 (file)
@@ -170,6 +170,15 @@ public:
    */
   void ApplyMask( PixelBuffer mask, float contentScale=1.0f, bool cropToMask=false );
 
+  /**
+   * Apply a Gaussian blur to this pixel data with the given radius.
+   *
+   * @note A bigger radius will yield a blurrier image. Only works for pixel data in RGBA format.
+   *
+   * @param[in] blurRadius The radius for Gaussian blur. A value of 0 or negative value indicates no blur.
+   */
+  void ApplyGaussianBlur( const float blurRadius );
+
 public:
 
   /**
index 24a17d0..709b2c9 100644 (file)
@@ -717,3 +717,36 @@ int UtcDaliPixelBufferMask09(void)
 
   END_TEST;
 }
+
+int UtcDaliPixelBufferGaussianBlur(void)
+{
+  TestApplication application;
+
+  Devel::PixelBuffer imageData = Devel::PixelBuffer::New( 10, 10, Pixel::RGBA8888 );
+  FillCheckerboard(imageData);
+
+  DALI_TEST_EQUALS( imageData.GetWidth(), 10, TEST_LOCATION ) ;
+  DALI_TEST_EQUALS( imageData.GetHeight(), 10, TEST_LOCATION ) ;
+
+  unsigned char* buffer = imageData.GetBuffer();
+
+  // Test that an even pixel in the odd row has full alpha value
+  DALI_TEST_EQUALS( buffer[43], 0xffu, TEST_LOCATION );
+
+  // Test that an even pixel in the even row has no alpha value
+  DALI_TEST_EQUALS( buffer[55], 0x00u, TEST_LOCATION );
+
+  imageData.ApplyGaussianBlur( 0.0f );
+
+  // Test that the pixels' alpha values are not changed because there is no blur
+  DALI_TEST_EQUALS( buffer[43], 0xffu, TEST_LOCATION );
+  DALI_TEST_EQUALS( buffer[55], 0x00u, TEST_LOCATION );
+
+  imageData.ApplyGaussianBlur( 1.0f );
+
+  // Test that the pixels' alpha values are changed after applying gaussian blur
+  DALI_TEST_EQUALS( buffer[43], 0x7Au, TEST_LOCATION );
+  DALI_TEST_EQUALS( buffer[55], 0x7Eu, TEST_LOCATION );
+
+  END_TEST;
+}
diff --git a/platform-abstractions/portable/gaussian-blur.cpp b/platform-abstractions/portable/gaussian-blur.cpp
new file mode 100644 (file)
index 0000000..443d175
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// EXTERNAL INCLUDES
+#include <memory.h>
+
+// INTERNAL INCLUDES
+#include "gaussian-blur.h"
+#include "pixel-buffer-impl.h"
+
+namespace Dali
+{
+
+namespace Internal
+{
+
+namespace Adaptor
+{
+
+
+
+void ConvoluteAndTranspose( unsigned char* inBuffer,
+                            unsigned char* outBuffer,
+                            const unsigned int bufferWidth,
+                            const unsigned int bufferHeight,
+                            const float blurRadius )
+{
+  // Calculate the weights for gaussian blur
+  int radius = static_cast<int>( std::ceil( blurRadius ) );
+  int rows = radius * 2 + 1;
+
+  float sigma = ( blurRadius < Math::MACHINE_EPSILON_1 ) ? 0.0f : blurRadius * 0.4f + 0.6f; // The same equation used by Android
+  float sigma22 = 2.0f * sigma * sigma;
+  float sqrtSigmaPi2 = std::sqrt( 2.0f * Math::PI ) * sigma;
+  float radius2 = radius * radius;
+  float normalizeFactor = 0.0f;
+
+  float* weightMatrix = new float[rows];
+  int index = 0;
+
+  for ( int row = -radius; row <= radius; row++ )
+  {
+    float distance = row * row;
+    if ( distance > radius2 )
+    {
+      weightMatrix[index] = 0.0f;
+    }
+    else
+    {
+      weightMatrix[index] = static_cast<float>( std::exp( -( distance ) / sigma22 ) / sqrtSigmaPi2 );
+    }
+    normalizeFactor += weightMatrix[index];
+    index++;
+  }
+
+  for ( int i = 0; i < rows; i++ )
+  {
+    weightMatrix[i] /= normalizeFactor;
+  }
+
+  // Perform the convolution and transposition using the weights
+  int columns = rows;
+  int columns2 = columns / 2;
+  for ( unsigned int y = 0; y < bufferHeight; y++ )
+  {
+    unsigned int targetPixelIndex = y;
+    unsigned int ioffset = y * bufferWidth;
+    for ( unsigned int x = 0; x < bufferWidth; x++ )
+    {
+      float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f;
+      int weightColumnOffset = columns2;
+      for ( int column = -columns2; column <= columns2; column++ )
+      {
+        float weight = weightMatrix[weightColumnOffset + column];
+        if ( fabsf( weight ) > Math::MACHINE_EPSILON_1 )
+        {
+          int ix = x + column;
+          ix = std::max( 0, std::min( ix, static_cast<int>( bufferWidth - 1 ) ) );
+          unsigned int sourcePixelIndex = ioffset + ix;
+          r += weight * inBuffer[sourcePixelIndex*4];
+          g += weight * inBuffer[sourcePixelIndex*4+1];
+          b += weight * inBuffer[sourcePixelIndex*4+2];
+          a += weight * inBuffer[sourcePixelIndex*4+3];
+        }
+      }
+
+      outBuffer[targetPixelIndex*4] = std::max( 0, std::min( static_cast<int>( r + 0.5f ), 255 ) );
+      outBuffer[targetPixelIndex*4+1] = std::max( 0, std::min( static_cast<int>( g + 0.5f ), 255 ) );
+      outBuffer[targetPixelIndex*4+2] = std::max( 0, std::min( static_cast<int>( b + 0.5f ), 255 ) );
+      outBuffer[targetPixelIndex*4+3] = std::max( 0, std::min( static_cast<int>( a + 0.5f ), 255 ) );
+
+      targetPixelIndex += bufferHeight;
+    }
+  }
+
+  delete [] weightMatrix;
+}
+
+void PerformGaussianBlurRGBA( PixelBuffer& buffer, const float blurRadius )
+{
+  unsigned int bufferWidth = buffer.GetWidth();
+  unsigned int bufferHeight = buffer.GetHeight();
+
+  // Create a temporary buffer for the two-pass blur
+  PixelBufferPtr softShadowImageBuffer = PixelBuffer::New( bufferWidth, bufferHeight, Pixel::RGBA8888 );
+  memcpy( softShadowImageBuffer->GetBuffer(), buffer.GetBuffer(), 4u * bufferWidth * bufferHeight );
+
+  // We perform the blur first but write its output image buffer transposed, so that we
+  // can just do it in two passes. The first pass blurs horizontally and transposes, the
+  // second pass does the same, but as the image is now transposed, it's really doing a
+  // vertical blur. The second transposition makes the image the right way up again. This
+  // is much faster than doing a 2D convolution.
+  ConvoluteAndTranspose( buffer.GetBuffer(), softShadowImageBuffer->GetBuffer(), bufferWidth, bufferHeight, blurRadius );
+  ConvoluteAndTranspose( softShadowImageBuffer->GetBuffer(), buffer.GetBuffer(), bufferHeight, bufferWidth, blurRadius );
+
+  // On leaving scope, softShadowImageBuffer will get destroyed.
+}
+
+} //namespace Adaptor
+
+}// namespace Internal
+
+}// namespace Dali
diff --git a/platform-abstractions/portable/gaussian-blur.h b/platform-abstractions/portable/gaussian-blur.h
new file mode 100644 (file)
index 0000000..d55308d
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef DALI_INTERNAL_ADAPTOR_GAUSSIAN_BLUR_H
+#define DALI_INTERNAL_ADAPTOR_GAUSSIAN_BLUR_H
+
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pixel-buffer-impl.h"
+
+namespace Dali
+{
+
+namespace Internal
+{
+
+namespace Adaptor
+{
+
+/**
+ * Perform a one dimension Gaussian blur convolution and write its output buffer transposed.
+ *
+ * @param[in] inBuffer The input buffer with the source image
+ * @param[in] outBuffer The output buffer with the Gaussian blur applied and transposed
+ * @param[in] bufferWidth The width of the buffer
+ * @param[in] bufferHeight The height of the buffer
+ * @param[in] blurRadius The radius for Gaussian blur
+ */
+void ConvoluteAndTranspose( unsigned char* inBuffer, unsigned char* outBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight, const float blurRadius );
+
+/**
+ * Perform Gaussian blur on a buffer.
+ *
+ * A Gaussian blur is generated by replacing each pixel’s color values with the average of the surrounding pixels’
+ * colors. This region is a circle with the given radius. Thus, a bigger radius yields a blurrier image.
+ *
+ * @note The pixel format of the buffer must be RGBA8888
+ *
+ * @param[in] buffer The buffer to apply the Gaussian blur to
+ * @param[in] blurRadius The radius for Gaussian blur
+ */
+void PerformGaussianBlurRGBA( PixelBuffer& buffer, const float blurRadius );
+
+} //namespace Adaptor
+
+} //namespace Internal
+
+} //namespace Dali
+
+#endif // DALI_INTERNAL_ADAPTOR_GAUSSIAN_BLUR_H
index bb4e908..5233f6e 100755 (executable)
@@ -19,7 +19,8 @@ tizen_platform_abstraction_src_files = \
   \
   $(portable_platform_abstraction_src_dir)/image-operations.cpp \
   $(portable_platform_abstraction_src_dir)/pixel-manipulation.cpp \
-  $(portable_platform_abstraction_src_dir)/alpha-mask.cpp
+  $(portable_platform_abstraction_src_dir)/alpha-mask.cpp \
+  $(portable_platform_abstraction_src_dir)/gaussian-blur.cpp
 
 # Add public headers here: