blob: 4f2f52ab656a50bd391519717e667f7b97a19d15 [file] [log] [blame]
/*
* Copyright (C) 2009 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.
*/
package com.android.rs.image;
import android.app.Activity;
import android.os.Bundle;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.renderscript.ScriptC;
import android.renderscript.RenderScript;
import android.renderscript.Type;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.Script;
import android.view.SurfaceView;
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,
SeekBar.OnSeekBarChangeListener {
private Bitmap mBitmapIn;
private Bitmap mBitmapOut;
private Bitmap mBitmapScratch;
private ScriptC_threshold mScript;
private ScriptC_vertical_blur mScriptVBlur;
private ScriptC_horizontal_blur mScriptHBlur;
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;
@SuppressWarnings({"FieldCanBeLocal"})
private Type mPixelType;
@SuppressWarnings({"FieldCanBeLocal"})
private Allocation mInPixelsAllocation;
@SuppressWarnings({"FieldCanBeLocal"})
private Allocation mOutPixelsAllocation;
@SuppressWarnings({"FieldCanBeLocal"})
private Allocation mScratchPixelsAllocation1;
private Allocation mScratchPixelsAllocation2;
private SurfaceView mSurfaceView;
private ImageView mDisplayView;
class FilterCallback extends RenderScript.RSMessageHandler {
private Runnable mAction = new Runnable() {
public void run() {
mDisplayView.invalidate();
}
};
@Override
public void run() {
mSurfaceView.removeCallbacks(mAction);
mSurfaceView.post(mAction);
}
}
int in[];
int interm[];
int out[];
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];
gaussian = new float[MAX_RADIUS * 2 + 1];
mBitmapIn.getPixels(in, 0, width, 0, 0, width, height);
}
long t = java.lang.System.currentTimeMillis();
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);
}
}
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;
mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
} else if (seekBar == mOutBlackSeekBar) {
mOutBlack = (float)progress;
mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
} else if (seekBar == mInWhiteSeekBar) {
mInWhite = (float)progress + 127.0f;
mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
} else if (seekBar == mOutWhiteSeekBar) {
mOutWhite = (float)progress + 127.0f;
mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
} else if (seekBar == mGammaSeekBar) {
mGamma = (float)progress/100.0f;
mGamma = Math.max(mGamma, 0.1f);
mGamma = 1.0f / mGamma;
mScriptVBlur.invoke_setGamma(mGamma);
} else if (seekBar == mSaturationSeekBar) {
mSaturation = (float)progress / 50.0f;
mScriptVBlur.invoke_setSaturation(mSaturation);
}
long t = java.lang.System.currentTimeMillis();
if (true) {
mScript.invoke_filter();
mOutPixelsAllocation.copyTo(mBitmapOut);
} else {
javaFilter();
mDisplayView.invalidate();
}
t = java.lang.System.currentTimeMillis() - t;
android.util.Log.v("Img", "Renderscript frame time core ms " + t);
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
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);
mDisplayView = (ImageView) findViewById(R.id.display);
mDisplayView.setImageBitmap(mBitmapOut);
mRadiusSeekBar = (SeekBar) findViewById(R.id.radius);
mRadiusSeekBar.setOnSeekBarChangeListener(this);
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);
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);
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("Result: not run");
}
public void surfaceCreated(SurfaceHolder holder) {
createScript();
mScript.invoke_filter();
mOutPixelsAllocation.copyTo(mBitmapOut);
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
private void createScript() {
mRS = RenderScript.create(this);
mRS.setMessageHandler(new FilterCallback());
mInPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
mOutPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapOut,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
Type.Builder tb = new Type.Builder(mRS, Element.F32_4(mRS));
tb.setX(mBitmapIn.getWidth());
tb.setY(mBitmapIn.getHeight());
mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
mScriptVBlur = new ScriptC_vertical_blur(mRS, getResources(), R.raw.vertical_blur);
mScriptHBlur = new ScriptC_horizontal_blur(mRS, getResources(), R.raw.horizontal_blur);
mScript = new ScriptC_threshold(mRS, getResources(), R.raw.threshold);
mScript.set_width(mBitmapIn.getWidth());
mScript.set_height(mBitmapIn.getHeight());
mScript.set_radius(mRadius);
mScriptVBlur.invoke_setLevels(mInBlack, mOutBlack, mInWhite, mOutWhite);
mScriptVBlur.invoke_setGamma(mGamma);
mScriptVBlur.invoke_setSaturation(mSaturation);
mScript.bind_InPixel(mInPixelsAllocation);
mScript.bind_OutPixel(mOutPixelsAllocation);
mScript.bind_ScratchPixel1(mScratchPixelsAllocation1);
mScript.bind_ScratchPixel2(mScratchPixelsAllocation2);
mScript.set_vBlurScript(mScriptVBlur);
mScript.set_hBlurScript(mScriptHBlur);
}
private Bitmap loadBitmap(int resource) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return copyBitmap(BitmapFactory.decodeResource(getResources(), resource, options));
}
private static Bitmap copyBitmap(Bitmap source) {
Bitmap b = Bitmap.createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
Canvas c = new Canvas(b);
c.drawBitmap(source, 0, 0, null);
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.invoke_filter();
mOutPixelsAllocation.copyTo(mBitmapOut);
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");
mBenchmarkResult.setText("Result: " + t + " ms");
mRadius = oldRadius;
mScript.set_radius(mRadius);
mScript.invoke_filter();
mOutPixelsAllocation.copyTo(mBitmapOut);
}
}