Merge "Added benchmark mode. Added some image processing operations."
diff --git a/libs/rs/java/ImageProcessing/AndroidManifest.xml b/libs/rs/java/ImageProcessing/AndroidManifest.xml
index b48d208..d6a2db4 100644
--- a/libs/rs/java/ImageProcessing/AndroidManifest.xml
+++ b/libs/rs/java/ImageProcessing/AndroidManifest.xml
@@ -6,7 +6,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
     <application android:label="Image Processing">
-        <activity android:name="ImageProcessingActivity">
+        <activity android:name="ImageProcessingActivity"
+                  android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/libs/rs/java/ImageProcessing/res/layout/main.xml b/libs/rs/java/ImageProcessing/res/layout/main.xml
index 6770c18..c6ec729 100644
--- a/libs/rs/java/ImageProcessing/res/layout/main.xml
+++ b/libs/rs/java/ImageProcessing/res/layout/main.xml
@@ -25,9 +25,147 @@
         android:id="@+id/display"
         android:layout_width="320dip"
         android:layout_height="266dip" />
-    
+
+    <Button
+        android:layout_marginBottom="170dip"
+        android:layout_width="wrap_content"
+        android:layout_height="40dip"
+        android:text="@string/benchmark"
+        android:onClick="benchmark"
+        android:layout_gravity="bottom"/>
+
+    <TextView
+        android:id="@+id/benchmarkText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="100dip"
+        android:layout_marginBottom="175dip"
+        android:layout_gravity="bottom"
+        android:text="@string/saturation"/>
+
+     <SeekBar
+        android:id="@+id/inSaturation"
+        android:layout_marginBottom="140dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inSaturationText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="142dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/saturation"/>
+
     <SeekBar
-        android:id="@+id/threshold"
+        android:id="@+id/inGamma"
+        android:layout_marginBottom="110dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inGammaText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="112dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/gamma"/>
+
+    <SeekBar
+        android:id="@+id/outWhite"
+        android:layout_marginBottom="80dip"
+        android:layout_marginLeft="170dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/outWhiteText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="220dip"
+        android:layout_marginBottom="82dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/out_white"/>
+
+    <SeekBar
+        android:id="@+id/inWhite"
+        android:layout_marginBottom="80dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="170dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inWhiteText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="82dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/in_white"/>
+
+    <SeekBar
+        android:id="@+id/outBlack"
+        android:layout_marginBottom="50dip"
+        android:layout_marginLeft="170dip"
+        android:layout_marginRight="10dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/outBlackText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="220dip"
+        android:layout_marginBottom="52dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/out_black"/>
+
+    <SeekBar
+        android:id="@+id/inBlack"
+        android:layout_marginBottom="50dip"
+        android:layout_marginLeft="10dip"
+        android:layout_marginRight="170dip"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+    <TextView
+        android:id="@+id/inBlackText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="52dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/in_black"/>
+
+    <SeekBar
+        android:id="@+id/radius"
         android:layout_marginBottom="10dip"
         android:layout_marginLeft="10dip"
         android:layout_marginRight="10dip"
@@ -35,4 +173,15 @@
         android:layout_height="wrap_content"
         android:layout_gravity="bottom" />
 
+     <TextView
+        android:id="@+id/blurText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:textSize="18sp"
+        android:layout_marginLeft="50dip"
+        android:layout_marginBottom="12dip"
+        android:textColor="#000"
+        android:layout_gravity="bottom"
+        android:text="@string/blur_description"/>
+
 </merge>
\ No newline at end of file
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold.rs b/libs/rs/java/ImageProcessing/res/raw/threshold.rs
index 8dc614e..0317088 100644
--- a/libs/rs/java/ImageProcessing/res/raw/threshold.rs
+++ b/libs/rs/java/ImageProcessing/res/raw/threshold.rs
@@ -4,46 +4,331 @@
 #include "../../../../scriptc/rs_math.rsh"
 #include "../../../../scriptc/rs_graphics.rsh"
 
+#define MAX_RADIUS 25
+
 int height;
 int width;
-float threshold;
+int radius;
 
 typedef struct c4u_s {
-    char r, g, b, a;
+    uint8_t r, g, b, a;
 } c4u_t;
 
-//rs_color4u * InPixel;
-//rs_color4u * OutPixel;
 c4u_t * InPixel;
 c4u_t * OutPixel;
+c4u_t * ScratchPixel;
 
-#pragma rs export_var(height, width, threshold, InPixel, OutPixel)
+float inBlack;
+float outBlack;
+float inWhite;
+float outWhite;
+float gamma;
+
+float saturation;
+
+float inWMinInB;
+float outWMinOutB;
+
+#pragma rs export_var(height, width, radius, InPixel, OutPixel, ScratchPixel, inBlack, outBlack, inWhite, outWhite, gamma, saturation)
+#pragma rs export_func(filter, processNoBlur, computeColorMatrix, computeGaussianWeights);
+
+// Store our coefficients here
+float gaussian[MAX_RADIUS * 2 + 1];
+float colorMat[4][4];
+
+void computeColorMatrix() {
+    // Saturation
+    // Linear weights
+    //float rWeight = 0.3086f;
+    //float gWeight = 0.6094f;
+    //float bWeight = 0.0820f;
+
+    // Gamma 2.2 weights (we haven't converted our image to linear space yet for perf reasons)
+    float rWeight = 0.299f;
+    float gWeight = 0.587f;
+    float bWeight = 0.114f;
+
+    float oneMinusS = 1.0f - saturation;
+
+    matrixLoadIdentity(colorMat);
+
+    colorMat[0][0] = oneMinusS * rWeight + saturation;
+    colorMat[0][1] = oneMinusS * rWeight;
+    colorMat[0][2] = oneMinusS * rWeight;
+    colorMat[1][0] = oneMinusS * gWeight;
+    colorMat[1][1] = oneMinusS * gWeight + saturation;
+    colorMat[1][2] = oneMinusS * gWeight;
+    colorMat[2][0] = oneMinusS * bWeight;
+    colorMat[2][1] = oneMinusS * bWeight;
+    colorMat[2][2] = oneMinusS * bWeight + saturation;
+
+    inWMinInB = inWhite - inBlack;
+    outWMinOutB = outWhite - outBlack;
+}
+
+void computeGaussianWeights() {
+    // Compute gaussian weights for the blur
+    // e is the euler's number
+    float e = 2.718281828459045f;
+    float pi = 3.1415926535897932f;
+    // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+    // x is of the form [-radius .. 0 .. radius]
+    // and sigma varies with radius.
+    // Based on some experimental radius values and sigma's
+    // we approximately fit sigma = f(radius) as
+    // sigma = radius * 0.4  + 0.6
+    // The larger the radius gets, the more our gaussian blur
+    // will resemble a box blur since with large sigma
+    // the gaussian curve begins to lose its shape
+    float sigma = 0.4f * (float)radius + 0.6f;
+
+    // Now compute the coefficints
+    // We will store some redundant values to save some math during
+    // the blur calculations
+    // precompute some values
+    float coeff1 = 1.0f / (sqrt( 2.0f * pi ) * sigma);
+    float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+
+    float normalizeFactor = 0.0f;
+    float floatR = 0.0f;
+    int r;
+    for(r = -radius; r <= radius; r ++) {
+        floatR = (float)r;
+        gaussian[r + radius] = coeff1 * pow(e, floatR * floatR * coeff2);
+        normalizeFactor += gaussian[r + radius];
+    }
+
+    //Now we need to normalize the weights because all our coefficients need to add up to one
+    normalizeFactor = 1.0f / normalizeFactor;
+    for(r = -radius; r <= radius; r ++) {
+        floatR = (float)r;
+        gaussian[r + radius] *= normalizeFactor;
+    }
+}
+
+// This needs to be inline
+void levelsSaturation(float4 *currentPixel) {
+    // Color matrix multiply
+    float tempX = colorMat[0][0] * currentPixel->x + colorMat[1][0] * currentPixel->y + colorMat[2][0] * currentPixel->z;
+    float tempY = colorMat[0][1] * currentPixel->x + colorMat[1][1] * currentPixel->y + colorMat[2][1] * currentPixel->z;
+    float tempZ = colorMat[0][2] * currentPixel->x + colorMat[1][2] * currentPixel->y + colorMat[2][2] * currentPixel->z;
+
+    currentPixel->x = tempX;
+    currentPixel->y = tempY;
+    currentPixel->z = tempZ;
+
+    // Clamp to 0..255
+    // Inline the code here to avoid funciton calls
+    currentPixel->x = currentPixel->x > 255.0f ? 255.0f : currentPixel->x;
+    currentPixel->y = currentPixel->y > 255.0f ? 255.0f : currentPixel->y;
+    currentPixel->z = currentPixel->z > 255.0f ? 255.0f : currentPixel->z;
+
+    currentPixel->x = currentPixel->x <= 0.0f ? 0.1f : currentPixel->x;
+    currentPixel->y = currentPixel->y <= 0.0f ? 0.1f : currentPixel->y;
+    currentPixel->z = currentPixel->z <= 0.0f ? 0.1f : currentPixel->z;
+
+    currentPixel->x = pow( (currentPixel->x - inBlack) / (inWMinInB), gamma) * (outWMinOutB) + outBlack;
+    currentPixel->y = pow( (currentPixel->y - inBlack) / (inWMinInB), gamma) * (outWMinOutB) + outBlack;
+    currentPixel->z = pow( (currentPixel->z - inBlack) / (inWMinInB), gamma) * (outWMinOutB) + outBlack;
+
+    currentPixel->x = currentPixel->x > 255.0f ? 255.0f : currentPixel->x;
+    currentPixel->y = currentPixel->y > 255.0f ? 255.0f : currentPixel->y;
+    currentPixel->z = currentPixel->z > 255.0f ? 255.0f : currentPixel->z;
+
+    currentPixel->x = currentPixel->x <= 0.0f ? 0.1f : currentPixel->x;
+    currentPixel->y = currentPixel->y <= 0.0f ? 0.1f : currentPixel->y;
+    currentPixel->z = currentPixel->z <= 0.0f ? 0.1f : currentPixel->z;
+}
+
+void processNoBlur() {
+    int w, h, r;
+    int count = 0;
+
+    float inWMinInB = inWhite - inBlack;
+    float outWMinOutB = outWhite - outBlack;
+    float4 currentPixel = 0;
+
+    for(h = 0; h < height; h ++) {
+        for(w = 0; w < width; w ++) {
+            c4u_t *input = InPixel + h*width + w;
+
+            currentPixel.x = (float)(input->r);
+            currentPixel.y = (float)(input->g);
+            currentPixel.z = (float)(input->b);
+
+            levelsSaturation(&currentPixel);
+
+            c4u_t *output = OutPixel + h*width + w;
+            output->r = (uint8_t)currentPixel.x;
+            output->g = (uint8_t)currentPixel.y;
+            output->b = (uint8_t)currentPixel.z;
+            output->a = input->a;
+        }
+    }
+    sendToClient(&count, 1, 4, 0);
+}
+
+void horizontalBlur() {
+    float4 blurredPixel = 0;
+    float4 currentPixel = 0;
+    // Horizontal blur
+    int w, h, r;
+    for(h = 0; h < height; h ++) {
+        for(w = 0; w < width; w ++) {
+
+            blurredPixel = 0;
+
+            for(r = -radius; r <= radius; r ++) {
+                // Stepping left and right away from the pixel
+                int validW = w + r;
+                // Clamp to zero and width max() isn't exposed for ints yet
+                if(validW < 0) {
+                    validW = 0;
+                }
+                if(validW > width - 1) {
+                    validW = width - 1;
+                }
+
+                c4u_t *input = InPixel + h*width + validW;
+
+                float weight = gaussian[r + radius];
+                currentPixel.x = (float)(input->r);
+                currentPixel.y = (float)(input->g);
+                currentPixel.z = (float)(input->b);
+                //currentPixel.w = (float)(input->a);
+
+                blurredPixel += currentPixel*weight;
+            }
+
+            c4u_t *output = ScratchPixel + h*width + w;
+            output->r = (uint8_t)blurredPixel.x;
+            output->g = (uint8_t)blurredPixel.y;
+            output->b = (uint8_t)blurredPixel.z;
+            //output->a = (uint8_t)blurredPixel.w;
+        }
+    }
+}
+
+void horizontalBlurLevels() {
+    float4 blurredPixel = 0;
+    float4 currentPixel = 0;
+    // Horizontal blur
+    int w, h, r;
+    for(h = 0; h < height; h ++) {
+        for(w = 0; w < width; w ++) {
+
+            blurredPixel = 0;
+
+            for(r = -radius; r <= radius; r ++) {
+                // Stepping left and right away from the pixel
+                int validW = w + r;
+                // Clamp to zero and width max() isn't exposed for ints yet
+                if(validW < 0) {
+                    validW = 0;
+                }
+                if(validW > width - 1) {
+                    validW = width - 1;
+                }
+
+                c4u_t *input = InPixel + h*width + validW;
+
+                float weight = gaussian[r + radius];
+                currentPixel.x = (float)(input->r);
+                currentPixel.y = (float)(input->g);
+                currentPixel.z = (float)(input->b);
+                //currentPixel.w = (float)(input->a);
+
+                blurredPixel += currentPixel*weight;
+            }
+
+            levelsSaturation(&blurredPixel);
+
+            c4u_t *output = ScratchPixel + h*width + w;
+            output->r = (uint8_t)blurredPixel.x;
+            output->g = (uint8_t)blurredPixel.y;
+            output->b = (uint8_t)blurredPixel.z;
+            //output->a = (uint8_t)blurredPixel.w;
+        }
+    }
+}
+
+void verticalBlur() {
+    float4 blurredPixel = 0;
+    float4 currentPixel = 0;
+    // Vertical blur
+    int w, h, r;
+    for(h = 0; h < height; h ++) {
+        for(w = 0; w < width; w ++) {
+
+            blurredPixel = 0;
+            for(r = -radius; r <= radius; r ++) {
+                int validH = h + r;
+                // Clamp to zero and width
+                if(validH < 0) {
+                    validH = 0;
+                }
+                if(validH > height - 1) {
+                    validH = height - 1;
+                }
+
+                c4u_t *input = ScratchPixel + validH*width + w;
+
+                float weight = gaussian[r + radius];
+
+                currentPixel.x = (float)(input->r);
+                currentPixel.y = (float)(input->g);
+                currentPixel.z = (float)(input->b);
+                //currentPixel.w = (float)(input->a);
+
+                blurredPixel += currentPixel*weight;
+            }
+
+            c4u_t *output = OutPixel + h*width + w;
+
+            output->r = (uint8_t)blurredPixel.x;
+            output->g = (uint8_t)blurredPixel.y;
+            output->b = (uint8_t)blurredPixel.z;
+            //output->a = (uint8_t)blurredPixel.w;
+        }
+    }
+}
 
 void filter() {
     debugP(0, (void *)height);
     debugP(0, (void *)width);
-    debugP(0, (void *)((int)threshold));
-    debugP(0, (void *)InPixel);
-    debugP(0, (void *)OutPixel);
+    debugP(0, (void *)radius);
 
-    rs_color4u *in = (rs_color4u *)InPixel;
-    rs_color4u *out = (rs_color4u *)OutPixel;
-    //const rs_color4u mask = {0,0,0,0xff};
+    debugPf(10, inBlack);
+    debugPf(11, outBlack);
+    debugPf(12, inWhite);
+    debugPf(13, outWhite);
+    debugPf(14, gamma);
+    debugPf(15, saturation);
 
-    int count = width * height;
-    int tf = threshold * 255 * 255;
-    int masks[2] = {0xffffffff, 0xff000000};
+    computeColorMatrix();
 
-    while (count--) {
-        int luminance = 54 * in->x +
-                        182 * in->y +
-                        18 * in->z;
-        int idx = ((uint32_t)(luminance - tf)) >> 31;
-        *((int *)out) = *((int *)in) & masks[idx];
-        in++;
-        out++;
+    if(radius == 0) {
+        processNoBlur();
+        return;
     }
 
+    computeGaussianWeights();
+
+    horizontalBlurLevels();
+    verticalBlur();
+
+    int count = 0;
+    sendToClient(&count, 1, 4, 0);
+}
+
+void filterBenchmark() {
+
+    computeGaussianWeights();
+
+    horizontalBlur();
+    verticalBlur();
+
+    int count = 0;
     sendToClient(&count, 1, 4, 0);
 }
 
diff --git a/libs/rs/java/ImageProcessing/res/raw/threshold_bc.bc b/libs/rs/java/ImageProcessing/res/raw/threshold_bc.bc
index 55d514c..58a93e6 100644
--- a/libs/rs/java/ImageProcessing/res/raw/threshold_bc.bc
+++ b/libs/rs/java/ImageProcessing/res/raw/threshold_bc.bc
Binary files differ
diff --git a/libs/rs/java/ImageProcessing/res/values/strings.xml b/libs/rs/java/ImageProcessing/res/values/strings.xml
new file mode 100644
index 0000000..cc5cc4d
--- /dev/null
+++ b/libs/rs/java/ImageProcessing/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2008 The Android Open Source Project
+*
+* 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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- General -->
+    <skip />
+    <!--slider label -->
+    <string name="blur_description">Blur Radius</string>
+    <string name="in_white">In White</string>
+    <string name="out_white">Out White</string>
+    <string name="in_black">In Black</string>
+    <string name="out_black">Out Black</string>
+    <string name="gamma">Gamma</string>
+    <string name="saturation">Saturation</string>
+    <string name="benchmark">Benchmark</string>
+
+</resources>
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
index aec5e23..5e36a78 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
@@ -31,13 +31,35 @@
 import android.view.SurfaceHolder;
 import android.widget.ImageView;
 import android.widget.SeekBar;
+import android.widget.TextView;
+import android.view.View;
 import java.lang.Math;
 
-public class ImageProcessingActivity extends Activity implements SurfaceHolder.Callback {
+public class ImageProcessingActivity extends Activity
+                                       implements SurfaceHolder.Callback,
+                                       SeekBar.OnSeekBarChangeListener {
     private Bitmap mBitmapIn;
     private Bitmap mBitmapOut;
+    private Bitmap mBitmapScratch;
     private ScriptC_Threshold mScript;
-    private float mThreshold = 0.5f;
+    private int mRadius = 0;
+    private SeekBar mRadiusSeekBar;
+
+    private float mInBlack = 0.0f;
+    private SeekBar mInBlackSeekBar;
+    private float mOutBlack = 0.0f;
+    private SeekBar mOutBlackSeekBar;
+    private float mInWhite = 255.0f;
+    private SeekBar mInWhiteSeekBar;
+    private float mOutWhite = 255.0f;
+    private SeekBar mOutWhiteSeekBar;
+    private float mGamma = 1.0f;
+    private SeekBar mGammaSeekBar;
+
+    private float mSaturation = 1.0f;
+    private SeekBar mSaturationSeekBar;
+
+    private TextView mBenchmarkResult;
 
     @SuppressWarnings({"FieldCanBeLocal"})
     private RenderScript mRS;
@@ -47,6 +69,8 @@
     private Allocation mInPixelsAllocation;
     @SuppressWarnings({"FieldCanBeLocal"})
     private Allocation mOutPixelsAllocation;
+    @SuppressWarnings({"FieldCanBeLocal"})
+    private Allocation mScratchPixelsAllocation;
 
     private SurfaceView mSurfaceView;
     private ImageView mDisplayView;
@@ -66,34 +90,217 @@
     }
 
     int in[];
+    int interm[];
     int out[];
-    private void javaFilter() {
-        final int w = mBitmapIn.getWidth();
-        final int h = mBitmapIn.getHeight();
-        final int count = w * h;
+    int MAX_RADIUS = 25;
+    // Store our coefficients here
+    float gaussian[];
+
+    private long javaFilter() {
+        final int width = mBitmapIn.getWidth();
+        final int height = mBitmapIn.getHeight();
+        final int count = width * height;
 
         if (in == null) {
             in = new int[count];
+            interm = new int[count];
             out = new int[count];
-            mBitmapIn.getPixels(in, 0, w, 0, 0, w, h);
+            gaussian = new float[MAX_RADIUS * 2 + 1];
+            mBitmapIn.getPixels(in, 0, width, 0, 0, width, height);
         }
 
-        int threshold = (int)(mThreshold * 255.f) * 255;
-        //long t = java.lang.System.currentTimeMillis();
+        long t = java.lang.System.currentTimeMillis();
 
-        for (int i = 0; i < count; i++) {
-            final int luminance = 54 * ((in[i] >> 0) & 0xff) +
-                                  182* ((in[i] >> 8) & 0xff) +
-                                  18 * ((in[i] >> 16) & 0xff);
-            if (luminance > threshold) {
-                out[i] = in[i];
-            } else {
-                out[i] = in[i] & 0xff000000;
+        int w, h, r;
+
+        float fRadius = (float)mRadius;
+        int radius = (int)mRadius;
+
+        // Compute gaussian weights for the blur
+        // e is the euler's number
+        float e = 2.718281828459045f;
+        float pi = 3.1415926535897932f;
+        // g(x) = ( 1 / sqrt( 2 * pi ) * sigma) * e ^ ( -x^2 / 2 * sigma^2 )
+        // x is of the form [-radius .. 0 .. radius]
+        // and sigma varies with radius.
+        // Based on some experimental radius values and sigma's
+        // we approximately fit sigma = f(radius) as
+        // sigma = radius * 0.4  + 0.6
+        // The larger the radius gets, the more our gaussian blur
+        // will resemble a box blur since with large sigma
+        // the gaussian curve begins to lose its shape
+        float sigma = 0.4f * fRadius + 0.6f;
+        // Now compute the coefficints
+        // We will store some redundant values to save some math during
+        // the blur calculations
+        // precompute some values
+        float coeff1 = 1.0f / (float)(Math.sqrt( 2.0f * pi ) * sigma);
+        float coeff2 = - 1.0f / (2.0f * sigma * sigma);
+        float normalizeFactor = 0.0f;
+        float floatR = 0.0f;
+        for(r = -radius; r <= radius; r ++) {
+            floatR = (float)r;
+            gaussian[r + radius] = coeff1 * (float)Math.pow(e, floatR * floatR * coeff2);
+            normalizeFactor += gaussian[r + radius];
+        }
+
+        //Now we need to normalize the weights because all our coefficients need to add up to one
+        normalizeFactor = 1.0f / normalizeFactor;
+        for(r = -radius; r <= radius; r ++) {
+            floatR = (float)r;
+            gaussian[r + radius] *= normalizeFactor;
+        }
+
+        float blurredPixelR = 0.0f;
+        float blurredPixelG = 0.0f;
+        float blurredPixelB = 0.0f;
+        float blurredPixelA = 0.0f;
+
+        for(h = 0; h < height; h ++) {
+            for(w = 0; w < width; w ++) {
+
+                blurredPixelR = 0.0f;
+                blurredPixelG = 0.0f;
+                blurredPixelB = 0.0f;
+                blurredPixelA = 0.0f;
+
+                for(r = -radius; r <= radius; r ++) {
+                    // Stepping left and right away from the pixel
+                    int validW = w + r;
+                    // Clamp to zero and width max() isn't exposed for ints yet
+                    if(validW < 0) {
+                        validW = 0;
+                    }
+                    if(validW > width - 1) {
+                        validW = width - 1;
+                    }
+
+                    int input = in[h*width + validW];
+
+                    int R = ((input >> 24) & 0xff);
+                    int G = ((input >> 16) & 0xff);
+                    int B = ((input >> 8) & 0xff);
+                    int A = (input & 0xff);
+
+                    float weight = gaussian[r + radius];
+
+                    blurredPixelR += (float)(R)*weight;
+                    blurredPixelG += (float)(G)*weight;
+                    blurredPixelB += (float)(B)*weight;
+                    blurredPixelA += (float)(A)*weight;
+                }
+
+                int R = (int)blurredPixelR;
+                int G = (int)blurredPixelG;
+                int B = (int)blurredPixelB;
+                int A = (int)blurredPixelA;
+
+                interm[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
             }
         }
-        //t = java.lang.System.currentTimeMillis() - t;
-        //android.util.Log.v("Img", "frame time ms " + t);
-        mBitmapOut.setPixels(out, 0, w, 0, 0, w, h);
+
+        for(h = 0; h < height; h ++) {
+            for(w = 0; w < width; w ++) {
+
+                blurredPixelR = 0.0f;
+                blurredPixelG = 0.0f;
+                blurredPixelB = 0.0f;
+                blurredPixelA = 0.0f;
+                for(r = -radius; r <= radius; r ++) {
+                    int validH = h + r;
+                    // Clamp to zero and width
+                    if(validH < 0) {
+                        validH = 0;
+                    }
+                    if(validH > height - 1) {
+                        validH = height - 1;
+                    }
+
+                    int input = interm[validH*width + w];
+
+                    int R = ((input >> 24) & 0xff);
+                    int G = ((input >> 16) & 0xff);
+                    int B = ((input >> 8) & 0xff);
+                    int A = (input & 0xff);
+
+                    float weight = gaussian[r + radius];
+
+                    blurredPixelR += (float)(R)*weight;
+                    blurredPixelG += (float)(G)*weight;
+                    blurredPixelB += (float)(B)*weight;
+                    blurredPixelA += (float)(A)*weight;
+                }
+
+                int R = (int)blurredPixelR;
+                int G = (int)blurredPixelG;
+                int B = (int)blurredPixelB;
+                int A = (int)blurredPixelA;
+
+                out[h*width + w] = (R << 24) | (G << 16) | (B << 8) | (A);
+            }
+        }
+
+        t = java.lang.System.currentTimeMillis() - t;
+        android.util.Log.v("Img", "Java frame time ms " + t);
+        mBitmapOut.setPixels(out, 0, width, 0, 0, width, height);
+        return t;
+    }
+
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+        if (fromUser) {
+
+            if(seekBar == mRadiusSeekBar) {
+                float fRadius = progress / 100.0f;
+                fRadius *= (float)(MAX_RADIUS);
+                mRadius = (int)fRadius;
+
+                mScript.set_radius(mRadius);
+            }
+            else if(seekBar == mInBlackSeekBar) {
+                mInBlack = (float)progress;
+                mScript.set_inBlack(mInBlack);
+            }
+            else if(seekBar == mOutBlackSeekBar) {
+                mOutBlack = (float)progress;
+                mScript.set_outBlack(mOutBlack);
+            }
+            else if(seekBar == mInWhiteSeekBar) {
+                mInWhite = (float)progress + 127.0f;
+                mScript.set_inWhite(mInWhite);
+            }
+            else if(seekBar == mOutWhiteSeekBar) {
+                mOutWhite = (float)progress + 127.0f;
+                mScript.set_outWhite(mOutWhite);
+            }
+            else if(seekBar == mGammaSeekBar) {
+                mGamma = (float)progress/100.0f;
+                mGamma = Math.max(mGamma, 0.1f);
+                mGamma = 1.0f / mGamma;
+                mScript.set_gamma(mGamma);
+            }
+            else if(seekBar == mSaturationSeekBar) {
+                mSaturation = (float)progress / 50.0f;
+                mScript.set_saturation(mSaturation);
+            }
+
+            long t = java.lang.System.currentTimeMillis();
+            if (true) {
+                mScript.invokable_Filter();
+            } else {
+                javaFilter();
+            }
+
+            t = java.lang.System.currentTimeMillis() - t;
+            android.util.Log.v("Img", "Renderscript frame time core ms " + t);
+
+            mDisplayView.invalidate();
+        }
+    }
+
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    public void onStopTrackingTouch(SeekBar seekBar) {
     }
 
     @Override
@@ -103,6 +310,7 @@
 
         mBitmapIn = loadBitmap(R.drawable.data);
         mBitmapOut = loadBitmap(R.drawable.data);
+        mBitmapScratch = loadBitmap(R.drawable.data);
 
         mSurfaceView = (SurfaceView) findViewById(R.id.surface);
         mSurfaceView.getHolder().addCallback(this);
@@ -110,31 +318,38 @@
         mDisplayView = (ImageView) findViewById(R.id.display);
         mDisplayView.setImageBitmap(mBitmapOut);
 
-        ((SeekBar) findViewById(R.id.threshold)).setOnSeekBarChangeListener(
-                new SeekBar.OnSeekBarChangeListener() {
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                if (fromUser) {
-                    mThreshold = progress / 100.0f;
-                    mScript.set_threshold(mThreshold);
+        mRadiusSeekBar = (SeekBar) findViewById(R.id.radius);
+        mRadiusSeekBar.setOnSeekBarChangeListener(this);
 
-                    long t = java.lang.System.currentTimeMillis();
-                    if (true) {
-                        mScript.invokable_Filter();
-                    } else {
-                        javaFilter();
-                        mDisplayView.invalidate();
-                    }
-                    t = java.lang.System.currentTimeMillis() - t;
-                    android.util.Log.v("Img", "frame time core ms " + t);
-                }
-            }
+        mInBlackSeekBar = (SeekBar)findViewById(R.id.inBlack);
+        mInBlackSeekBar.setOnSeekBarChangeListener(this);
+        mInBlackSeekBar.setMax(128);
+        mInBlackSeekBar.setProgress(0);
+        mOutBlackSeekBar = (SeekBar)findViewById(R.id.outBlack);
+        mOutBlackSeekBar.setOnSeekBarChangeListener(this);
+        mOutBlackSeekBar.setMax(128);
+        mOutBlackSeekBar.setProgress(0);
 
-            public void onStartTrackingTouch(SeekBar seekBar) {
-            }
+        mInWhiteSeekBar = (SeekBar)findViewById(R.id.inWhite);
+        mInWhiteSeekBar.setOnSeekBarChangeListener(this);
+        mInWhiteSeekBar.setMax(128);
+        mInWhiteSeekBar.setProgress(128);
+        mOutWhiteSeekBar = (SeekBar)findViewById(R.id.outWhite);
+        mOutWhiteSeekBar.setOnSeekBarChangeListener(this);
+        mOutWhiteSeekBar.setMax(128);
+        mOutWhiteSeekBar.setProgress(128);
 
-            public void onStopTrackingTouch(SeekBar seekBar) {
-            }
-        });
+        mGammaSeekBar = (SeekBar)findViewById(R.id.inGamma);
+        mGammaSeekBar.setOnSeekBarChangeListener(this);
+        mGammaSeekBar.setMax(150);
+        mGammaSeekBar.setProgress(100);
+
+        mSaturationSeekBar = (SeekBar)findViewById(R.id.inSaturation);
+        mSaturationSeekBar.setOnSeekBarChangeListener(this);
+        mSaturationSeekBar.setProgress(50);
+
+        mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
+        mBenchmarkResult.setText("Benchmark no yet run");
     }
 
     public void surfaceCreated(SurfaceHolder holder) {
@@ -154,13 +369,23 @@
 
         mInPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapIn);
         mOutPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapOut);
+        mScratchPixelsAllocation = Allocation.createBitmapRef(mRS, mBitmapScratch);
 
         mScript = new ScriptC_Threshold(mRS, getResources(), false);
         mScript.set_width(mBitmapIn.getWidth());
         mScript.set_height(mBitmapIn.getHeight());
-        mScript.set_threshold(mThreshold);
+        mScript.set_radius(mRadius);
+
+        mScript.set_inBlack(mInBlack);
+        mScript.set_outBlack(mOutBlack);
+        mScript.set_inWhite(mInWhite);
+        mScript.set_outWhite(mOutWhite);
+        mScript.set_gamma(mGamma);
+        mScript.set_saturation(mSaturation);
+
         mScript.bind_InPixel(mInPixelsAllocation);
         mScript.bind_OutPixel(mOutPixelsAllocation);
+        mScript.bind_ScratchPixel(mScratchPixelsAllocation);
     }
 
     private Bitmap loadBitmap(int resource) {
@@ -176,4 +401,28 @@
         source.recycle();
         return b;
     }
+
+    // button hook
+    public void benchmark(View v) {
+        android.util.Log.v("Img", "Benchmarking");
+        int oldRadius = mRadius;
+        mRadius = MAX_RADIUS;
+        mScript.set_radius(mRadius);
+
+        long t = java.lang.System.currentTimeMillis();
+
+        mScript.invokable_FilterBenchmark();
+
+        t = java.lang.System.currentTimeMillis() - t;
+        android.util.Log.v("Img", "Renderscript frame time core ms " + t);
+
+        long javaTime = javaFilter();
+
+        mBenchmarkResult.setText("RS: " + t + " ms  Java: " + javaTime + " ms");
+
+        mRadius = oldRadius;
+        mScript.set_radius(mRadius);
+
+        mScript.invokable_Filter();
+    }
 }
diff --git a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ScriptC_Threshold.java b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ScriptC_Threshold.java
index dbaa18c..dfed9e5 100644
--- a/libs/rs/java/ImageProcessing/src/com/android/rs/image/ScriptC_Threshold.java
+++ b/libs/rs/java/ImageProcessing/src/com/android/rs/image/ScriptC_Threshold.java
@@ -10,12 +10,23 @@
 {
     private final static int mFieldIndex_height = 0;
     private final static int mFieldIndex_width = 1;
-    private final static int mFieldIndex_threshold = 2;
+    private final static int mFieldIndex_radius = 2;
     private final static int mFieldIndex_InPixel = 3;
     private final static int mFieldIndex_OutPixel = 4;
+    private final static int mFieldIndex_ScratchPixel = 5;
+
+    private final static int mFieldIndex_inBlack = 6;
+    private final static int mFieldIndex_outBlack = 7;
+    private final static int mFieldIndex_inWhite = 8;
+    private final static int mFieldIndex_outWhite = 9;
+    private final static int mFieldIndex_gamma = 10;
+
+    private final static int mFieldIndex_saturation = 11;
+    private final static int mFieldIndex_hue = 12;
 
     private Allocation mField_InPixel;
     private Allocation mField_OutPixel;
+    private Allocation mField_ScratchPixel;
 
     public ScriptC_Threshold(RenderScript rs, Resources resources, boolean isRoot) {
         super(rs, resources, R.raw.threshold_bc, isRoot);
@@ -43,6 +54,15 @@
         bindAllocation(f, mFieldIndex_OutPixel);
         mField_OutPixel = f;
     }
+    public void bind_ScratchPixel(Allocation f) {
+        if (f != null) {
+            //if (f.getType().getElement() != Element.ATTRIB_COLOR_U8_4(mRS)) {
+                //throw new IllegalArgumentException("Element type mismatch.");
+            //}
+        }
+        bindAllocation(f, mFieldIndex_ScratchPixel);
+        mField_ScratchPixel = f;
+    }
     public Allocation get_OutPixel() {
         return mField_OutPixel;
     }
@@ -55,13 +75,41 @@
         setVar(mFieldIndex_width, v);
     }
 
-    public void set_threshold(float v) {
-        setVar(mFieldIndex_threshold, v);
+    public void set_radius(int v) {
+        setVar(mFieldIndex_radius, v);
     }
 
-    private final static int mInvokableIndex_Filter = 0;
+    public void set_inBlack(float v) {
+        setVar(mFieldIndex_inBlack, v);
+    }
+    public void set_outBlack(float v) {
+        setVar(mFieldIndex_outBlack, v);
+    }
+    public void set_inWhite(float v) {
+        setVar(mFieldIndex_inWhite, v);
+    }
+    public void set_outWhite(float v) {
+        setVar(mFieldIndex_outWhite, v);
+    }
+    public void set_gamma(float v) {
+        setVar(mFieldIndex_gamma, v);
+    }
+
+    public void set_saturation(float v) {
+        setVar(mFieldIndex_saturation, v);
+    }
+    public void set_hue(float v) {
+        setVar(mFieldIndex_hue, v);
+    }
+
+    private final static int mInvokableIndex_Filter = 2;
     public void invokable_Filter() {
         invokeData(mInvokableIndex_Filter);
     }
+
+    private final static int mInvokableIndex_FilterBenchmark = 3;
+    public void invokable_FilterBenchmark() {
+        invokeData(mInvokableIndex_FilterBenchmark);
+    }
 }