In my android application, i want to apply image warp effect provided in Photo Warp and Photo Deformer application. For that i used BitmapMesh. The problem is that, it's not saving warped image. Whenever i touch image, it refresh the image and doesn't save my previously warped image.I want to save that image whenever user perform warp operation. Here i am posting my code. Here i am using "BitmapMesh" activity to perform warp effect on image.
Please help me to solve this problem.
Thanks.
Code:
BitmapMesh Activity:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Bundle;
import android.util.FloatMath;
import android.view.MotionEvent;
import android.view.View;
public class BitmapMesh extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int WIDTH = 20;
private static final int HEIGHT = 20;
private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private final Bitmap mBitmap;
private final float[] mVerts = new float[COUNT * 2];
private final float[] mOrig = new float[COUNT * 2];
private final Matrix mMatrix = new Matrix();
private final Matrix mInverse = new Matrix();
private static void setXY(float[] array, int index, float x, float y) {
array[index * 2 + 0] = x;
array[index * 2 + 1] = y;
}
public SampleView(Context context) {
super(context);
setFocusable(true);
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.image1);
float w = mBitmap.getWidth();
float h = mBitmap.getHeight();
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = h * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = w * x / WIDTH;
setXY(mVerts, index, fx, fy);
setXY(mOrig, index, fx, fy);
index += 1;
}
}
mMatrix.setTranslate(10, 10);
mMatrix.invert(mInverse);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.concat(mMatrix);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, mVerts, 0, null, 0,
null);
}
private void warp(float cx, float cy) {
final float K = 10000;
float[] src = mOrig;
float[] dst = mVerts;
for (int i = 0; i < COUNT * 2; i += 2) {
float x = src[i + 0];
float y = src[i + 1];
float dx = cx - x;
float dy = cy - y;
float dd = dx * dx + dy * dy;
float d = FloatMath.sqrt(dd);
float pull = K / (dd + 0.000001f);
pull /= (d + 0.000001f);
// android.util.Log.d("skia", "index " + i + " dist=" + d +
// " pull=" + pull);
if (pull >= 1) {
dst[i + 0] = cx;
dst[i + 1] = cy;
} else {
dst[i + 0] = x + dx * pull;
dst[i + 1] = y + dy * pull;
}
}
}
private int mLastWarpX = -9999; // don't match a touch coordinate
private int mLastWarpY;
#Override
public boolean onTouchEvent(MotionEvent event) {
float[] pt = { event.getX(), event.getY() };
mInverse.mapPoints(pt);
int x = (int) pt[0];
int y = (int) pt[1];
if (mLastWarpX != x || mLastWarpY != y) {
mLastWarpX = x;
mLastWarpY = y;
warp(pt[0], pt[1]);
invalidate();
}
return true;
}
}
}
Graphics Activity:
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
class GraphicsActivity extends Activity {
// set to true to test Picture
private static final boolean TEST_PICTURE = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public void setContentView(View view) {
if (TEST_PICTURE) {
ViewGroup vg = new PictureLayout(this);
vg.addView(view);
view = vg;
}
super.setContentView(view);
}
}
PictureLayout.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Picture;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
class PictureLayout extends ViewGroup {
private final Picture mPicture = new Picture();
public PictureLayout(Context context) {
super(context);
}
public PictureLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void addView(View child) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child);
}
#Override
public void addView(View child, int index) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index);
}
#Override
public void addView(View child, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, params);
}
#Override
public void addView(View child, int index, LayoutParams params) {
if (getChildCount() > 1) {
throw new IllegalStateException(
"PictureLayout can host only one direct child");
}
super.addView(child, index, params);
}
#Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getChildCount();
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
maxWidth += getPaddingLeft() + getPaddingRight();
maxHeight += getPaddingTop() + getPaddingBottom();
Drawable drawable = getBackground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSize(maxWidth, widthMeasureSpec),
resolveSize(maxHeight, heightMeasureSpec));
}
private void drawPict(Canvas canvas, int x, int y, int w, int h, float sx,
float sy) {
canvas.save();
canvas.translate(x, y);
canvas.clipRect(0, 0, w, h);
canvas.scale(0.5f, 0.5f);
canvas.scale(sx, sy, w, h);
canvas.drawPicture(mPicture);
canvas.restore();
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(mPicture.beginRecording(getWidth(), getHeight()));
mPicture.endRecording();
int x = getWidth() / 2;
int y = getHeight() / 2;
if (false) {
canvas.drawPicture(mPicture);
} else {
drawPict(canvas, 0, 0, x, y, 1, 1);
drawPict(canvas, x, 0, x, y, -1, 1);
drawPict(canvas, 0, y, x, y, 1, -1);
drawPict(canvas, x, y, x, y, -1, -1);
}
}
#Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
location[0] = getLeft();
location[1] = getTop();
dirty.set(0, 0, getWidth(), getHeight());
return getParent();
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int count = super.getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final int childLeft = getPaddingLeft();
final int childTop = getPaddingTop();
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
}
//Little changes in this piece of code
float[] dst; //Global
public SampleView(Context context) {
super(context);
setFocusable(true);
mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.image1);
float w = mBitmap.getWidth();
float h = mBitmap.getHeight();
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = h * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = w * x / WIDTH;
setXY(mVerts, index, fx, fy);
setXY(mOrig, index, fx, fy);
index += 1;
dst=mVerts;//Assign dst here just once
}
}
mMatrix.setTranslate(10, 10);
mMatrix.invert(mInverse);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.concat(mMatrix);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, mVerts, 0, null, 0,
null);
}
private void warp(float cx, float cy) {
final float K = 10000;
float[] src = dst; //now you are applying wrap effect on the last effected pixels
for (int i = 0; i < COUNT * 2; i += 2) {
float x = src[i + 0];
float y = src[i + 1];
float dx = cx - x;
float dy = cy - y;
float dd = dx * dx + dy * dy;
float d = FloatMath.sqrt(dd);
float pull = K / (dd + 0.000001f);
pull /= (d + 0.000001f);
// android.util.Log.d("skia", "index " + i + " dist=" + d +
// " pull=" + pull);
if (pull >= 1) {
dst[i + 0] = cx;
dst[i + 1] = cy;
} else {
dst[i + 0] = x + dx * pull;
dst[i + 1] = y + dy * pull;
}
}
}
Related
I've created a custom view that draws a dial gauge. If I set a static value for the angle of the needle, the gauge draws as expected (see first image below). If I attempt to set the angle of the needle through runOnUiThread, then I have weird needle artifacts (see second image).
I am calling canvas.drawColor(color.BLACK) each time onDraw() is called, so I am not sure how the artifacts are being carried over. My best guess is that I am not handling threading correctly in the method that calls runOnUiThread().
DialGaugeView class:
package net.dynu.kubie.redneksldhlr;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
public class DialGaugeView extends View {
private int height, width, min = 0;
private int numberFontSize, titleFontSize = 0;
private float dialEdgeStroke, dialEdgeRadius = 0;
private float dialFaceRadius = 0;
private float tickMajorStroke, tickSweep, tickAngleStart, tickAngleEnd, tickEdgeRadius, tickInnerMajorRadius, tickInnerMinorRadius = 0;
private int tickMajorCount = 0;
private float tickMinorStroke = 0;
private int tickMinorCount = 0;
private int tickTotalCount = 0;
private int numberMin, numberMax = 0;
private float numberRadius = 0;
private float arrowTipRadius, arrowRearRadius = 0;
private float arrowCenterRadius, arrowPinRadius = 0;
private float sensorInput = 0;
private float temp = 0;
private Paint paint;
private boolean isInit;
private float titleRadius = 0;
private Rect rect = new Rect();
private Path path = new Path();
private String titleStr = "";
//Variables common to drawing functions
private float center_x, center_y = 0;
private float x1, x2, x3, y1, y2, y3 = 0;
private float angle = 0;
private int number = 0;
private String str = "";
public DialGaugeView(Context context) {
super(context);
}
public DialGaugeView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.DialGaugeView,
0, 0);
try {
tickMajorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMajor, 5);
tickMinorCount = a.getInteger(R.styleable.DialGaugeView_tickCountMinor, 0);
tickSweep = a.getFloat(R.styleable.DialGaugeView_tickSweep, 270) / 2;
numberMin = a.getInteger(R.styleable.DialGaugeView_displayMin, 0);
numberMax = a.getInteger(R.styleable.DialGaugeView_displayMax, 1);
titleStr = a.getString(R.styleable.DialGaugeView_displayTitle);
} finally {
a.recycle();
}
}
public DialGaugeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public int getTickMajorCount() {
return tickMajorCount;
}
public void setTickMajorCount(int i) {
tickMajorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public int getTickMinorCount() {
return tickMinorCount;
}
public void setTickMinorCount(int i) {
tickMinorCount = i;
isInit = false;
invalidate();
requestLayout();
}
public float getTickSweep() {
return tickSweep;
}
public void setTickSweep(float i) {
tickSweep = i / 2;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMin() {
return numberMin;
}
public void setDisplayMin(int i) {
numberMin = i;
isInit = false;
invalidate();
requestLayout();
}
public int getDisplayMax() {
return numberMax;
}
public void setDisplayMax(int i) {
numberMax = i;
isInit = false;
invalidate();
requestLayout();
}
public float getSensorInput() {
return sensorInput;
}
public void setSensorInput(float i) {
sensorInput = i;
isInit = false;
invalidate();
requestLayout();
}
public String getTitle() {
return titleStr;
}
public void setTitle(String i) {
titleStr = i;
invalidate();
requestLayout();
}
private void initGauge() {
//Common variables
height = getHeight();
width = getWidth();
min = Math.min(height, width);
center_x = width / 2;
center_y = height / 2;
paint = new Paint();
isInit = true;
//Dial face variables
dialEdgeStroke = min / 20;
dialEdgeRadius = min / 2 - dialEdgeStroke / 2;
dialFaceRadius = dialEdgeRadius - dialEdgeStroke / 2;
//Tick variables
//tickMajorCount = 7; //TODO - Class input needed
//tickMinorCount = 1; //TODO - Class input needed
//tickSweep = 270 / 2; //TODO - Class input needed //Degrees +/- from 90 degrees
tickEdgeRadius = dialFaceRadius - dialEdgeStroke / 2;
tickInnerMajorRadius = (float) (tickEdgeRadius - dialEdgeStroke * 1.5);
tickInnerMinorRadius = ((tickEdgeRadius + tickInnerMajorRadius) / 2);
tickMajorStroke = min / 75;
tickAngleStart = 90 + tickSweep;
tickAngleEnd = 90 - tickSweep;
tickMinorStroke = tickMajorStroke / 2;
tickTotalCount = tickMajorCount + (tickMajorCount - 1) * tickMinorCount;
//Numeral variables
//numberMin = 0; //TODO - Class input needed
//numberMax = 120; //TODO - Class input needed
numberRadius = (tickInnerMajorRadius); // - (tickEdgeRadius - tickInnerMajorRadius) * 1.25);
numberFontSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, min / 40,
getResources().getDisplayMetrics());
//Title variables
titleRadius = tickInnerMajorRadius;
titleFontSize = numberFontSize;
//Arrow variables
//sensorInput = (float) (numberMax * .4965); //TODO - Class input needed
arrowTipRadius = tickEdgeRadius;
arrowRearRadius = arrowTipRadius / 2;
arrowCenterRadius = (arrowTipRadius * 1 / 8);
arrowPinRadius = (arrowTipRadius * 1 / 24);
}
#Override
protected void onDraw(Canvas canvas) {
if (!isInit) {
initGauge();
}
canvas.drawColor(Color.BLACK);
drawDialFace(canvas);
drawTicks(canvas);
drawNumeral(canvas);
drawTitle(canvas);
drawArrow(canvas);
postInvalidateDelayed(500);
invalidate();
requestLayout();
}
private void drawDialFace(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStrokeWidth(dialEdgeStroke);
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialEdgeRadius, paint);
paint.reset();
paint.setColor(getResources().getColor(android.R.color.white));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
canvas.drawCircle(center_x, center_y, dialFaceRadius, paint);
}
private void drawTicks(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.black));
paint.setStyle(Paint.Style.STROKE);
paint.setAntiAlias(true);
for(int i = 0; i < tickTotalCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickTotalCount - 1) + tickAngleStart) / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * tickEdgeRadius);
y1 = (float) (center_y - Math.sin(angle) * tickEdgeRadius);
if((i % (tickMinorCount + 1)) == 0) {
paint.setStrokeWidth(tickMajorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMajorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMajorRadius);
} else {
paint.setStrokeWidth(tickMinorStroke);
x2 = (float) (center_x + Math.cos(angle) * tickInnerMinorRadius);
y2 = (float) (center_y - Math.sin(angle) * tickInnerMinorRadius);
}
canvas.drawLine(x1, y1, x2, y2, paint);
}
}
private void drawNumeral(Canvas canvas) {
paint.reset();
paint.setTextSize(numberFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
for(int i = 0; i < tickMajorCount; i++) {
angle = (float) (((tickAngleEnd - tickAngleStart) * i / (tickMajorCount - 1) + tickAngleStart) / 180 * Math.PI);
number = (numberMax - numberMin) * i / (tickMajorCount - 1) + numberMin;
str = String.valueOf(number);
paint.getTextBounds(str, 0, str.length(), rect);
double c = Math.cos(angle);
double s = Math.sin(angle);
if(rect.width() * Math.abs(s) < rect.height() * Math.abs(c)) {
x2 = (float) (Math.signum(c) * rect.width() / 2);
y2 = (float) (Math.tan(angle) * x2);
} else {
y2 = (float) (Math.signum(s) * rect.height() / 2);
x2 = (float) (1 / Math.tan(angle) * y2);//Math.cotg(angle) * y;
}
x1 = (float) (center_x + Math.cos(angle) * numberRadius - rect.width() / 2 - x2 * 1.25);
y1 = (float) (center_y - Math.sin(angle) * numberRadius + rect.height() / 2 + y2 * 1.25);
canvas.drawText(str, x1, y1, paint);
}
}
private void drawTitle(Canvas canvas) {
paint.reset();
paint.setTextSize(titleFontSize);
paint.setColor(getResources().getColor(android.R.color.black));
angle = (float) ((270.0 / 180) * Math.PI);
paint.getTextBounds(titleStr, 0, str.length(), rect);
x1 = (float) (center_x + Math.cos(angle) * titleRadius - rect.width() / 2);
y1 = (float) (center_y - Math.sin(angle) * titleRadius + rect.height() / 3);
canvas.drawText(titleStr, x1, y1, paint);
}
private void drawArrow(Canvas canvas) {
paint.reset();
paint.setColor(getResources().getColor(android.R.color.holo_red_dark));
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
//Calculate (x,y) coordinates for the arrow path.
temp = (tickAngleEnd - tickAngleStart) * sensorInput / numberMax + tickAngleStart;
angle = (float) (temp / 180 * Math.PI);
x1 = (float) (center_x + Math.cos(angle) * arrowTipRadius);
y1 = (float) (center_y - Math.sin(angle) * arrowTipRadius);
angle = (float) ((temp + 170) / 180 * Math.PI);
x2 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y2 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
angle = (float) ((temp - 170) / 180 * Math.PI);
x3 = (float) (center_x + Math.cos(angle) * arrowRearRadius);
y3 = (float) (center_y - Math.sin(angle) * arrowRearRadius);
//Draw arrow path using calculated coordinates.
path.moveTo(x1, y1);
path.lineTo(x2, y2);
path.lineTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
//Calculate (x,y) coordinates for dial center.
x1 = center_x;
y1 = center_y;
canvas.drawCircle(x1, y1, arrowCenterRadius, paint);
paint.setColor(getResources().getColor(android.R.color.darker_gray));
canvas.drawCircle(x1, y1, arrowPinRadius, paint);
}
}
My MainActivity:
package net.dynu.kubie.redneksldhlr;
import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hideSystemUI();
startGenerating();
}
#Override
protected void onResume() {
super.onResume();
hideSystemUI();
//demoData();
}
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
hideSystemUI();
//demoData();
}
}
private void hideSystemUI() {
// Enables regular immersive mode.
// For "lean back" mode, remove SYSTEM_UI_FLAG_IMMERSIVE.
// Or for "sticky immersive," replace it with SYSTEM_UI_FLAG_IMMERSIVE_STICKY
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE
// Set the content to appear under the system bars so that the
// content doesn't resize when the system bars hide and show.
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// Hide the nav bar and status bar
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN);
}
private void startGenerating() {
DoSomethingThread randomWork = new DoSomethingThread();
randomWork.start();
}
public class DoSomethingThread extends Thread {
private static final String TAG = "DoSomethingThread";
private static final int DELAY = 15000; // 5 seconds
private static final int RANDOM_MULTIPLIER = 120;
#Override
public void run() {
//Log.v(TAG, "doing work in Random Number Thread");
while (true) {
float randNum = (float) (Math.random() * RANDOM_MULTIPLIER);
// need to publish the random number back on the UI at this point in the code through the publishProgress(randNum) call
publishProgress(randNum);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
// Log.v(TAG, "Interrupting and stopping the Random Number Thread");
return;
}
}
}
}
private void publishProgress(float randNum) {
//Log.v(TAG, "reporting back from the Random Number Thread");
//final String text = String.format(getString(R.string.service_msg), randNum);
//final String text = Integer.toString(randNum);
final float text = randNum;
runOnUiThread(new Runnable() {
#Override
public void run() {
updateResults(text);
}
});
}
public void updateResults(float results) {
//TextView myTextView = (TextView) findViewById(R.id.testTextView);
DialGaugeView myDial = findViewById(R.id.my_gauge);
myDial.setSensorInput(results);
}
}
I eventually realized that my issue wasn't artifacts at all. My arrow is being drawn as a series of paths. If I do not reset the path prior to adding coordinates for the new arrow, the previous arrow(s) just get dragged along. Adding path.reset(); prior to the addition of the first arrow coordinate solved my problem.
//Draw arrow path using calculated coordinates.
path.reset();
path.moveTo(x1, y1);
path.moveTo(x2, y2);
path.moveTo(x3, y3);
path.close();
canvas.drawPath(path, paint);
I am developing face app like "Aging Booth". I have successfully detected face and applied wrinkle on it.
Now I want to warp face like as following example
Can any one help me to solve this problem, by guiding me which technique I should use, OR any source code to warp image like this.
I try to solve it using following code.
But it is not giving required result. So any one please edit it to get required result OR any other technique....
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import android.widget.LinearLayout;
public class MainActivity extends Activity {
////////////////////////////////////////////////////////
ImageView img;
Bitmap face;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
LinearLayout ll01 = (LinearLayout) findViewById(R.id.linearLayout1);
SampleView sv = new SampleView(this);
ll01.addView(sv);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static class SampleView extends View {
static int WIDTH = 8; // sections
static int HEIGHT = 8;
static int COUNT = (WIDTH + 1) * (HEIGHT + 1); // total verts count
Bitmap mBitmap; // declaring a bitmap
float[] matrixVertsMoved = new float[COUNT * 2]; // declaring an array with double amount of vert count, one for x and one for y
float[] matrixOriganal = new float[COUNT * 2];
float clickX;
float clickY;
static void setXY(float[] array, int index, float x, float y) {
array[index * 2 + 0] = x;
array[index * 2 + 1] = y;
}
///
public SampleView(Context context) {
super(context);
setFocusable(true);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.w);
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = mBitmap.getHeight() * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = mBitmap.getWidth() * x / WIDTH;
setXY(matrixVertsMoved, index, fx, fy);
setXY(matrixOriganal, index, fx, fy);
index += 1;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, matrixVertsMoved, 0, null, 0, null);
Paint p1 = new Paint();
p1.setColor(0x660000FF);
Paint p2 = new Paint();
p2.setColor(0x99FF0000);
Paint p3 = new Paint();
p3.setColor(0xFFFFFB00);
for (int i = 0; i < COUNT * 2; i += 2) {
float x = matrixOriganal[i + 0];
float y = matrixOriganal[i + 1];
canvas.drawCircle(x, y, 4, p1);
float x1 = matrixOriganal[i + 0];
float y1 = matrixOriganal[i + 1];
float x2 = matrixVertsMoved[i + 0];
float y2 = matrixVertsMoved[i + 1];
canvas.drawLine(x1, y1, x2, y2, p1);
}
for (int i = 0; i < COUNT * 2; i += 2) {
float x = matrixVertsMoved[i + 0];
float y = matrixVertsMoved[i + 1];
canvas.drawCircle(x, y, 4, p2);
}
canvas.drawCircle(clickX, clickY, 6, p3);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private void smudge() {
for (int i = 0; i < COUNT * 2; i += 2) {
float xOriginal = matrixOriganal[i + 0];
float yOriginal = matrixOriganal[i + 1];
float dist_click_to_origin_x = clickX - xOriginal; // distance from current vertex in the original matrix to the place clicked.
float dist_click_to_origin_y = clickY - yOriginal;
float kv_kat = dist_click_to_origin_x * dist_click_to_origin_x + dist_click_to_origin_y * dist_click_to_origin_y;
float pull = (1000000 / kv_kat / (float) Math.sqrt(kv_kat));
if (pull >= 1) {
matrixVertsMoved[i + 0] = clickX;
matrixVertsMoved[i + 1] = clickY;
} else {
matrixVertsMoved[i + 0] = xOriginal + dist_click_to_origin_x * pull;
matrixVertsMoved[i + 1] = yOriginal + dist_click_to_origin_y * pull;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#Override
public boolean onTouchEvent(MotionEvent event) {
clickX = event.getX();
clickY = event.getY();
smudge(); // change the matrix.
invalidate(); // calls a redraw on the canvas.
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
public Bitmap ExtendBitmap(Bitmap normalImage){
int w = normalImage.getWidth();
int h = normalImage.getHeight();
int thirdWidht = w / 3;
//create a new blank image
Bitmap stretchImage = Bitmap.createBitmap(w + thirdWidht, h, Bitmap.Config.ARGB_8888 );
Canvas c = new Canvas(stretchImage);
//draw left bit
c.drawBitmap(normalImage, new Rect(0,0,thirdWidht,h), new Rect(0,0,thirdWidht,h), null);
//draw stretched middle bit
c.drawBitmap(normalImage, new Rect(thirdWidht,0,thirdWidht * 2, h), new Rect(thirdWidht,0,thirdWidht * 3,h), null);
//draw right bit
c.drawBitmap(normalImage, new Rect(thirdWidht * 2,0,w,h), new Rect(thirdWidht * 3,0,w + thirdWidht,h), null);
return stretchImage;
}
I want to implement the Ringdroid waveform in my android app.But for some songs,the waveform created after choosing the song is larger than the screen size and for songs the waveform width is smaller than the mobile screen width .
Plz suggest me the change in code of Ringdroid ,so that every time the waveform of song totally covers the width of screen.
This is the link of Ringdroid project
https://github.com/google/ringdroid
After a whole lot of searching and surfing about it. I tried it myself and got the desired output with it
This is the WaveformView class of Ringdroid
package com.ringdroid;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.ringdroid.soundfile.SoundFile;
/**
* WaveformView is an Android view that displays a visual representation
* of an audio waveform. It retrieves the frame gains from a CheapSoundFile
* object and recomputes the shape contour at several zoom levels.
*
* This class doesn't handle selection or any of the touch interactions
* directly, so it exposes a listener interface. The class that embeds
* this view should add itself as a listener and make the view scroll
* and respond to other events appropriately.
*
* WaveformView doesn't actually handle selection, but it will just display
* the selected part of the waveform in a different color.
*/
public class WaveformView extends View {
public interface WaveformListener {
public void waveformTouchStart(float x);
public void waveformTouchMove(float x);
public void waveformTouchEnd();
public void waveformFling(float x);
public void waveformDraw();
public void waveformZoomIn();
public void waveformZoomOut();
};
// Colors
private Paint mGridPaint;
private Paint mSelectedLinePaint;
private Paint mUnselectedLinePaint;
private Paint mUnselectedBkgndLinePaint;
private Paint mBorderLinePaint;
private Paint mPlaybackLinePaint;
private Paint mTimecodePaint;
private SoundFile mSoundFile;
private int[] mLenByZoomLevel;
private double[][] mValuesByZoomLevel;
private double[] mZoomFactorByZoomLevel;
private int[] mHeightsAtThisZoomLevel;
private int mZoomLevel;
private int mNumZoomLevels;
private int mSampleRate;
private int mSamplesPerFrame;
private int mOffset;
private int mSelectionStart;
private int mSelectionEnd;
private int mPlaybackPos;
private float mDensity;
private float mInitialScaleSpan;
private WaveformListener mListener;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private boolean mInitialized;
public WaveformView(Context context, AttributeSet attrs) {
super(context, attrs);
// We don't want keys, the markers get these
setFocusable(false);
Resources res = getResources();
mGridPaint = new Paint();
mGridPaint.setAntiAlias(false);
mGridPaint.setColor(res.getColor(R.color.grid_line));
mSelectedLinePaint = new Paint();
mSelectedLinePaint.setAntiAlias(false);
mSelectedLinePaint.setColor(res.getColor(R.color.waveform_selected));
mUnselectedLinePaint = new Paint();
mUnselectedLinePaint.setAntiAlias(false);
mUnselectedLinePaint.setColor(res.getColor(R.color.waveform_unselected));
mUnselectedBkgndLinePaint = new Paint();
mUnselectedBkgndLinePaint.setAntiAlias(false);
mUnselectedBkgndLinePaint.setColor(res.getColor(R.color.waveform_unselected_bkgnd_overlay));
mBorderLinePaint = new Paint();
mBorderLinePaint.setAntiAlias(true);
mBorderLinePaint.setStrokeWidth(1.5f);
mBorderLinePaint.setPathEffect(new DashPathEffect(new float[] { 3.0f, 2.0f }, 0.0f));
mBorderLinePaint.setColor(res.getColor(R.color.selection_border));
mPlaybackLinePaint = new Paint();
mPlaybackLinePaint.setAntiAlias(false);
mPlaybackLinePaint.setColor(res.getColor(R.color.playback_indicator));
mTimecodePaint = new Paint();
mTimecodePaint.setTextSize(12);
mTimecodePaint.setAntiAlias(true);
mTimecodePaint.setColor(res.getColor(R.color.timecode));
mTimecodePaint.setShadowLayer(2, 1, 1, res.getColor(R.color.timecode_shadow));
mGestureDetector = new GestureDetector(
context,
new GestureDetector.SimpleOnGestureListener() {
public boolean onFling(MotionEvent e1, MotionEvent e2, float vx, float vy) {
mListener.waveformFling(vx);
return true;
}
}
);
mScaleGestureDetector = new ScaleGestureDetector(
context,
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
public boolean onScaleBegin(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleBegin " + d.getCurrentSpanX());
mInitialScaleSpan = Math.abs(d.getCurrentSpanX());
return true;
}
public boolean onScale(ScaleGestureDetector d) {
float scale = Math.abs(d.getCurrentSpanX());
Log.v("Ringdroid", "Scale " + (scale - mInitialScaleSpan));
if (scale - mInitialScaleSpan > 40) {
mListener.waveformZoomIn();
mInitialScaleSpan = scale;
}
if (scale - mInitialScaleSpan < -40) {
mListener.waveformZoomOut();
mInitialScaleSpan = scale;
}
return true;
}
public void onScaleEnd(ScaleGestureDetector d) {
Log.v("Ringdroid", "ScaleEnd " + d.getCurrentSpanX());
}
}
);
mSoundFile = null;
mLenByZoomLevel = null;
mValuesByZoomLevel = null;
mHeightsAtThisZoomLevel = null;
mOffset = 0;
mPlaybackPos = -1;
mSelectionStart = 0;
mSelectionEnd = 0;
mDensity = 1.0f;
mInitialized = false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
mScaleGestureDetector.onTouchEvent(event);
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mListener.waveformTouchStart(event.getX());
break;
case MotionEvent.ACTION_MOVE:
mListener.waveformTouchMove(event.getX());
break;
case MotionEvent.ACTION_UP:
mListener.waveformTouchEnd();
break;
}
return true;
}
public boolean hasSoundFile() {
return mSoundFile != null;
}
public void setSoundFile(SoundFile soundFile) {
mSoundFile = soundFile;
mSampleRate = mSoundFile.getSampleRate();
mSamplesPerFrame = mSoundFile.getSamplesPerFrame();
computeDoublesForAllZoomLevels();
mHeightsAtThisZoomLevel = null;
}
public boolean isInitialized() {
return mInitialized;
}
public int getZoomLevel() {
return mZoomLevel;
}
public void setZoomLevel(int zoomLevel) {
while (mZoomLevel > zoomLevel) {
zoomIn();
}
while (mZoomLevel < zoomLevel) {
zoomOut();
}
}
public boolean canZoomIn() {
return (mZoomLevel > 0);
}
public void zoomIn() {
if (canZoomIn()) {
mZoomLevel--;
mSelectionStart *= 2;
mSelectionEnd *= 2;
mHeightsAtThisZoomLevel = null;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter *= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
invalidate();
}
}
public boolean canZoomOut() {
return (mZoomLevel < mNumZoomLevels - 1);
}
public void zoomOut() {
if (canZoomOut()) {
mZoomLevel++;
mSelectionStart /= 2;
mSelectionEnd /= 2;
int offsetCenter = mOffset + getMeasuredWidth() / 2;
offsetCenter /= 2;
mOffset = offsetCenter - getMeasuredWidth() / 2;
if (mOffset < 0)
mOffset = 0;
mHeightsAtThisZoomLevel = null;
invalidate();
}
}
public int maxPos() {
return mLenByZoomLevel[mZoomLevel];
}
public int secondsToFrames(double seconds) {
return (int)(1.0 * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public int secondsToPixels(double seconds) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(z * seconds * mSampleRate / mSamplesPerFrame + 0.5);
}
public double pixelsToSeconds(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (pixels * (double)mSamplesPerFrame / (mSampleRate * z));
}
public int millisecsToPixels(int msecs) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)((msecs * 1.0 * mSampleRate * z) /
(1000.0 * mSamplesPerFrame) + 0.5);
}
public int pixelsToMillisecs(int pixels) {
double z = mZoomFactorByZoomLevel[mZoomLevel];
return (int)(pixels * (1000.0 * mSamplesPerFrame) /
(mSampleRate * z) + 0.5);
}
public void setParameters(int start, int end, int offset) {
mSelectionStart = start;
mSelectionEnd = end;
mOffset = offset;
}
public int getStart() {
return mSelectionStart;
}
public int getEnd() {
return mSelectionEnd;
}
public int getOffset() {
return mOffset;
}
public void setPlayback(int pos) {
mPlaybackPos = pos;
}
public void setListener(WaveformListener listener) {
mListener = listener;
}
public void recomputeHeights(float density) {
mHeightsAtThisZoomLevel = null;
mDensity = density;
mTimecodePaint.setTextSize((int)(12 * density));
invalidate();
}
protected void drawWaveformLine(Canvas canvas,
int x, int y0, int y1,
Paint paint) {
canvas.drawLine(x, y0, x, y1, paint);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSoundFile == null)
return;
if (mHeightsAtThisZoomLevel == null)
computeIntsForThisZoomLevel();
// Draw waveform
int measuredWidth = getMeasuredWidth();
int measuredHeight = getMeasuredHeight();
int start = mOffset;
int width = mHeightsAtThisZoomLevel.length - start;
int ctr = measuredHeight / 2;
if (width > measuredWidth)
width = measuredWidth;
// Draw grid
double onePixelInSecs = pixelsToSeconds(1);
boolean onlyEveryFiveSecs = (onePixelInSecs > 1.0 / 50.0);
double fractionalSecs = mOffset * onePixelInSecs;
int integerSecs = (int) fractionalSecs;
int i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
int integerSecsNew = (int) fractionalSecs;
if (integerSecsNew != integerSecs) {
integerSecs = integerSecsNew;
if (!onlyEveryFiveSecs || 0 == (integerSecs % 5)) {
canvas.drawLine(i, 0, i, measuredHeight, mGridPaint);
}
}
}
// Draw waveform
for (i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, i,
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
if (i + start == mPlaybackPos) {
canvas.drawLine(i, 0, i, measuredHeight, mPlaybackLinePaint);
}
}
// If we can see the right edge of the waveform, draw the
// non-waveform area to the right as unselected
for (i = width; i < measuredWidth; i++) {
drawWaveformLine(canvas, i, 0, measuredHeight,
mUnselectedBkgndLinePaint);
}
// Draw borders
canvas.drawLine(
mSelectionStart - mOffset + 0.5f, 30,
mSelectionStart - mOffset + 0.5f, measuredHeight,
mBorderLinePaint);
canvas.drawLine(
mSelectionEnd - mOffset + 0.5f, 0,
mSelectionEnd - mOffset + 0.5f, measuredHeight - 30,
mBorderLinePaint);
// Draw timecode
double timecodeIntervalSecs = 1.0;
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 5.0;
}
if (timecodeIntervalSecs / onePixelInSecs < 50) {
timecodeIntervalSecs = 15.0;
}
// Draw grid
fractionalSecs = mOffset * onePixelInSecs;
int integerTimecode = (int) (fractionalSecs / timecodeIntervalSecs);
i = 0;
while (i < width) {
i++;
fractionalSecs += onePixelInSecs;
integerSecs = (int) fractionalSecs;
int integerTimecodeNew = (int) (fractionalSecs /
timecodeIntervalSecs);
if (integerTimecodeNew != integerTimecode) {
integerTimecode = integerTimecodeNew;
// Turn, e.g. 67 seconds into "1:07"
String timecodeMinutes = "" + (integerSecs / 60);
String timecodeSeconds = "" + (integerSecs % 60);
if ((integerSecs % 60) < 10) {
timecodeSeconds = "0" + timecodeSeconds;
}
String timecodeStr = timecodeMinutes + ":" + timecodeSeconds;
float offset = (float) (
0.5 * mTimecodePaint.measureText(timecodeStr));
canvas.drawText(timecodeStr,
i - offset,
(int)(12 * mDensity),
mTimecodePaint);
}
}
if (mListener != null) {
mListener.waveformDraw();
}
}
/**
* Called once when a new sound file is added
*/
private void computeDoublesForAllZoomLevels() {
int numFrames = mSoundFile.getNumFrames();
int[] frameGains = mSoundFile.getFrameGains();
double[] smoothedGains = new double[numFrames];
if (numFrames == 1) {
smoothedGains[0] = frameGains[0];
} else if (numFrames == 2) {
smoothedGains[0] = frameGains[0];
smoothedGains[1] = frameGains[1];
} else if (numFrames > 2) {
smoothedGains[0] = (double)(
(frameGains[0] / 2.0) +
(frameGains[1] / 2.0));
for (int i = 1; i < numFrames - 1; i++) {
smoothedGains[i] = (double)(
(frameGains[i - 1] / 3.0) +
(frameGains[i ] / 3.0) +
(frameGains[i + 1] / 3.0));
}
smoothedGains[numFrames - 1] = (double)(
(frameGains[numFrames - 2] / 2.0) +
(frameGains[numFrames - 1] / 2.0));
}
// Make sure the range is no more than 0 - 255
double maxGain = 1.0;
for (int i = 0; i < numFrames; i++) {
if (smoothedGains[i] > maxGain) {
maxGain = smoothedGains[i];
}
}
double scaleFactor = 1.0;
if (maxGain > 255.0) {
scaleFactor = 255 / maxGain;
}
// Build histogram of 256 bins and figure out the new scaled max
maxGain = 0;
int gainHist[] = new int[256];
for (int i = 0; i < numFrames; i++) {
int smoothedGain = (int)(smoothedGains[i] * scaleFactor);
if (smoothedGain < 0)
smoothedGain = 0;
if (smoothedGain > 255)
smoothedGain = 255;
if (smoothedGain > maxGain)
maxGain = smoothedGain;
gainHist[smoothedGain]++;
}
// Re-calibrate the min to be 5%
double minGain = 0;
int sum = 0;
while (minGain < 255 && sum < numFrames / 20) {
sum += gainHist[(int)minGain];
minGain++;
}
// Re-calibrate the max to be 99%
sum = 0;
while (maxGain > 2 && sum < numFrames / 100) {
sum += gainHist[(int)maxGain];
maxGain--;
}
// Compute the heights
double[] heights = new double[numFrames];
double range = maxGain - minGain;
for (int i = 0; i < numFrames; i++) {
double value = (smoothedGains[i] * scaleFactor - minGain) / range;
if (value < 0.0)
value = 0.0;
if (value > 1.0)
value = 1.0;
heights[i] = value * value;
}
mNumZoomLevels = 5;
mLenByZoomLevel = new int[5];
mZoomFactorByZoomLevel = new double[5];
mValuesByZoomLevel = new double[5][];
// Level 0 is doubled, with interpolated values
mLenByZoomLevel[0] = numFrames * 2;
mZoomFactorByZoomLevel[0] = 2.0;
mValuesByZoomLevel[0] = new double[mLenByZoomLevel[0]];
if (numFrames > 0) {
mValuesByZoomLevel[0][0] = 0.5 * heights[0];
mValuesByZoomLevel[0][1] = heights[0];
}
for (int i = 1; i < numFrames; i++) {
mValuesByZoomLevel[0][2 * i] = 0.5 * (heights[i - 1] + heights[i]);
mValuesByZoomLevel[0][2 * i + 1] = heights[i];
}
// Level 1 is normal
mLenByZoomLevel[1] = numFrames;
mValuesByZoomLevel[1] = new double[mLenByZoomLevel[1]];
mZoomFactorByZoomLevel[1] = 1.0;
for (int i = 0; i < mLenByZoomLevel[1]; i++) {
mValuesByZoomLevel[1][i] = heights[i];
}
// 3 more levels are each halved
for (int j = 2; j < 5; j++) {
mLenByZoomLevel[j] = mLenByZoomLevel[j - 1] / 2;
mValuesByZoomLevel[j] = new double[mLenByZoomLevel[j]];
mZoomFactorByZoomLevel[j] = mZoomFactorByZoomLevel[j - 1] / 2.0;
for (int i = 0; i < mLenByZoomLevel[j]; i++) {
mValuesByZoomLevel[j][i] =
0.5 * (mValuesByZoomLevel[j - 1][2 * i] +
mValuesByZoomLevel[j - 1][2 * i + 1]);
}
}
if (numFrames > 5000) {
mZoomLevel = 3;
} else if (numFrames > 1000) {
mZoomLevel = 2;
} else if (numFrames > 300) {
mZoomLevel = 1;
} else {
mZoomLevel = 0;
}
mInitialized = true;
}
/**
* Called the first time we need to draw when the zoom level has changed
* or the screen is resized
*/
private void computeIntsForThisZoomLevel() {
int halfHeight = (getMeasuredHeight() / 2) - 1;
mHeightsAtThisZoomLevel = new int[mLenByZoomLevel[mZoomLevel]];
for (int i = 0; i < mLenByZoomLevel[mZoomLevel]; i++) {
mHeightsAtThisZoomLevel[i] =
(int)(mValuesByZoomLevel[mZoomLevel][i] * halfHeight);
}
}
}
The change is here in this part of code
DisplayMetrics displaymetrics = getContext().getResources().getDisplayMetrics();
int ScreenWidth= displaymetrics.widthPixels;
// Draw waveform
for ( i = 0; i < width; i++) {
Paint paint;
if (i + start >= mSelectionStart &&
i + start < mSelectionEnd) {
paint = mSelectedLinePaint;
} else {
drawWaveformLine(canvas, ((ScreenWidth/width)*i), 0, measuredHeight,
mUnselectedBkgndLinePaint);
paint = mUnselectedLinePaint;
}
drawWaveformLine(
canvas, ((ScreenWidth/width)*i),
ctr - mHeightsAtThisZoomLevel[start + i],
ctr + 1 + mHeightsAtThisZoomLevel[start + i],
paint);
you have to change the x-axis of draw line method according to your screen size
import java.util.LinkedList;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceView;
/**
* A view that displays audio data on the screen as a waveform.
*/
public class WaveformView extends SurfaceView {
// The number of buffer frames to keep around (for a nice fade-out
// visualization.
private static final int HISTORY_SIZE = 6;
// To make quieter sounds still show up well on the display, we use
// +/- 8192 as the amplitude that reaches the top/bottom of the view
// instead of +/- 32767. Any samples that have magnitude higher than this
// limit will simply be clipped during drawing.
private static final float MAX_AMPLITUDE_TO_DRAW = 8192.0f;
// The queue that will hold historical audio data.
private LinkedList<short[]> mAudioData;
private Paint mPaint;
public WaveformView(Context context) {
this(context, null, 0);
}
public WaveformView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveformView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mAudioData = new LinkedList<short[]>();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(0);
mPaint.setAntiAlias(true);
}
/**
* Updates the waveform view with a new "frame" of samples and renders it.
* The new frame gets added to the front of the rendering queue, pushing the
* previous frames back, causing them to be faded out visually.
*
* #param buffer the most recent buffer of audio samples.
*/
public synchronized void updateAudioData(short[] buffer) {
short[] newBuffer;
// We want to keep a small amount of history in the view to provide a nice
// fading effect. We use a linked list that we treat as a queue for this.
if (mAudioData.size() == HISTORY_SIZE) {
newBuffer = mAudioData.removeFirst();
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
} else {
newBuffer = buffer.clone();
}
mAudioData.addLast(newBuffer);
// Update the display.
Canvas canvas = getHolder().lockCanvas();
if (canvas != null) {
drawWaveform(canvas);
getHolder().unlockCanvasAndPost(canvas);
}
}
/**
* Repaints the view's surface.
*
* #param canvas the {#link Canvas} object on which to draw.
*/
private void drawWaveform(Canvas canvas) {
// Clear the screen each time because SurfaceView won't do this for us.
canvas.drawColor(Color.BLACK);
float width = getWidth();
float height = getHeight();
float centerY = height / 2;
// We draw the history from oldest to newest so that the older audio
// data is further back and darker than the most recent data.
int colorDelta = 255 / (HISTORY_SIZE + 1);
int brightness = colorDelta;
for (short[] buffer : mAudioData) {
mPaint.setColor(Color.argb(brightness, 128, 255, 192));
float lastX = -1;
float lastY = -1;
// For efficiency, we don't draw all of the samples in the buffer,
// but only the ones that align with pixel boundaries.
for (int x = 0; x < width; x++) {
int index = (int) ((x / width) * buffer.length);
short sample = buffer[index];
float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
if (lastX != -1) {
canvas.drawLine(lastX, lastY, x, y, mPaint);
}
lastX = x;
lastY = y;
}
brightness += colorDelta;
}
}
}
I see scale with only 0 written! As in the picture and two strange zeros out of the schema: (GALAXY NEXUS 4.2.1)
As in the example at http://mindtherobot.com/blog/272/android-custom-ui-making-a-vintage-thermometer/comment-page-1/#comment-106122%29 this is my custom view:
package com.wikibuyers.barometro;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LightingColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
public class Barometro extends View implements SensorEventListener{
private static final String TAG = Barometro.class.getSimpleName();
private Handler handler;
// drawing tools
private RectF rimRect;
private Paint rimPaint;
private Paint rimCirclePaint;
private RectF faceRect;
private Bitmap faceTexture;
private Paint facePaint;
private Paint rimShadowPaint;
private Paint scalePaint;
private RectF scaleRect;
private Paint titlePaint;
private Path titlePath;
private Paint logoPaint;
private Bitmap logo;
private Matrix logoMatrix;
private float logoScale;
private Paint handPaint;
private Path handPath;
private Paint handScrewPaint;
private Paint backgroundPaint;
// end drawing tools
private Bitmap background; // holds the cached static part
// scale configuration
private static final int totalNicks = 100;
private static final float degreesPerNick = 360.0f / totalNicks;
private static final int centerDegree = 40; // the one in the top center (12 o'clock)
private static final int minDegrees = -30;
private static final int maxDegrees = 110;
// hand dynamics -- all are angular expressed in F degrees
private boolean handInitialized = false;
private float handPosition = centerDegree;
private float handTarget = centerDegree;
private float handVelocity = 0.0f;
private float handAcceleration = 0.0f;
private long lastHandMoveTime = -1L;
public Barometro(Context context) {
super(context);
init();
}
public Barometro(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public Barometro(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
attachToSensor();
}
#Override
protected void onDetachedFromWindow() {
detachFromSensor();
super.onDetachedFromWindow();
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
Parcelable superState = bundle.getParcelable("superState");
super.onRestoreInstanceState(superState);
handInitialized = bundle.getBoolean("handInitialized");
handPosition = bundle.getFloat("handPosition");
handTarget = bundle.getFloat("handTarget");
handVelocity = bundle.getFloat("handVelocity");
handAcceleration = bundle.getFloat("handAcceleration");
lastHandMoveTime = bundle.getLong("lastHandMoveTime");
}
#Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
Bundle state = new Bundle();
state.putParcelable("superState", superState);
state.putBoolean("handInitialized", handInitialized);
state.putFloat("handPosition", handPosition);
state.putFloat("handTarget", handTarget);
state.putFloat("handVelocity", handVelocity);
state.putFloat("handAcceleration", handAcceleration);
state.putLong("lastHandMoveTime", lastHandMoveTime);
return state;
}
#SuppressLint({ "NewApi", "InlinedApi" })
private void init() {
handler = new Handler();
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
initDrawingTools();
}
private String getTitle() {
return "wikibuyers.com";
}
private SensorManager getSensorManager() {
return (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
}
private void attachToSensor() {
SensorManager sensorManager = getSensorManager();
List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_PRESSURE);
if (sensors.size() > 0) {
Sensor sensor = sensors.get(0);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
} else {
Log.e(TAG, "No pressure sensor found");
}
}
private void detachFromSensor() {
SensorManager sensorManager = getSensorManager();
sensorManager.unregisterListener(this);
}
private void initDrawingTools() {
rimRect = new RectF(0.1f, 0.1f, 0.9f, 0.9f);
// the linear gradient is a bit skewed for realism
rimPaint = new Paint();
rimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
rimPaint.setShader(new LinearGradient(0.40f, 0.0f, 0.60f, 1.0f,
Color.rgb(0xf0, 0xf5, 0xf0),
Color.rgb(0x30, 0x31, 0x30),
Shader.TileMode.CLAMP));
rimCirclePaint = new Paint();
rimCirclePaint.setAntiAlias(true);
rimCirclePaint.setStyle(Paint.Style.STROKE);
rimCirclePaint.setColor(Color.argb(0x4f, 0x33, 0x36, 0x33));
rimCirclePaint.setStrokeWidth(0.005f);
float rimSize = 0.02f;
faceRect = new RectF();
faceRect.set(rimRect.left + rimSize, rimRect.top + rimSize,
rimRect.right - rimSize, rimRect.bottom - rimSize);
faceTexture = BitmapFactory.decodeResource(getContext().getResources(),
R.drawable.plastic);
BitmapShader paperShader = new BitmapShader(faceTexture,
Shader.TileMode.MIRROR,
Shader.TileMode.MIRROR);
Matrix paperMatrix = new Matrix();
facePaint = new Paint();
facePaint.setFilterBitmap(true);
paperMatrix.setScale(1.0f / faceTexture.getWidth(),
1.0f / faceTexture.getHeight());
paperShader.setLocalMatrix(paperMatrix);
facePaint.setStyle(Paint.Style.FILL);
facePaint.setShader(paperShader);
rimShadowPaint = new Paint();
rimShadowPaint.setShader(new RadialGradient(0.5f, 0.5f, faceRect.width() / 2.0f,
new int[] { 0x00000000, 0x00000500, 0x50000500 },
new float[] { 0.96f, 0.96f, 0.99f },
Shader.TileMode.MIRROR));
rimShadowPaint.setStyle(Paint.Style.FILL);
scalePaint = new Paint();
scalePaint.setStyle(Paint.Style.STROKE);
scalePaint.setColor(0x9f004d0f);
scalePaint.setStrokeWidth(0.005f);
scalePaint.setAntiAlias(true);
scalePaint.setTextSize(0.045f);
scalePaint.setTypeface(Typeface.SANS_SERIF);
scalePaint.setTextScaleX(0.8f);
scalePaint.setTextAlign(Paint.Align.CENTER);
float scalePosition = 0.10f;
scaleRect = new RectF();
scaleRect.set(faceRect.left + scalePosition, faceRect.top + scalePosition,
faceRect.right - scalePosition, faceRect.bottom - scalePosition);
titlePaint = new Paint();
titlePaint.setColor(0xaf946109);
titlePaint.setAntiAlias(true);
titlePaint.setTypeface(Typeface.DEFAULT_BOLD);
titlePaint.setTextAlign(Paint.Align.CENTER);
titlePaint.setTextSize(0.05f);
titlePaint.setTextScaleX(0.8f);
titlePath = new Path();
titlePath.addArc(new RectF(0.24f, 0.24f, 0.76f, 0.76f), -180.0f, -180.0f);
logoPaint = new Paint();
logoPaint.setFilterBitmap(true);
logo = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.logo);
logoMatrix = new Matrix();
logoScale = (1.0f / logo.getWidth()) * 0.3f;;
logoMatrix.setScale(logoScale, logoScale);
handPaint = new Paint();
handPaint.setAntiAlias(true);
handPaint.setColor(0xff392f2c);
handPaint.setShadowLayer(0.01f, -0.005f, -0.005f, 0x7f000000);
handPaint.setStyle(Paint.Style.FILL);
handPath = new Path();
handPath.moveTo(0.5f, 0.5f + 0.2f);
handPath.lineTo(0.5f - 0.010f, 0.5f + 0.2f - 0.007f);
handPath.lineTo(0.5f - 0.002f, 0.5f - 0.32f);
handPath.lineTo(0.5f + 0.002f, 0.5f - 0.32f);
handPath.lineTo(0.5f + 0.010f, 0.5f + 0.2f - 0.007f);
handPath.lineTo(0.5f, 0.5f + 0.2f);
handPath.addCircle(0.5f, 0.5f, 0.025f, Path.Direction.CW);
handScrewPaint = new Paint();
handScrewPaint.setAntiAlias(true);
handScrewPaint.setColor(0xff493f3c);
handScrewPaint.setStyle(Paint.Style.FILL);
backgroundPaint = new Paint();
backgroundPaint.setFilterBitmap(true);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Log.d(TAG, "Width spec: " + MeasureSpec.toString(widthMeasureSpec));
Log.d(TAG, "Height spec: " + MeasureSpec.toString(heightMeasureSpec));
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int chosenWidth = chooseDimension(widthMode, widthSize);
int chosenHeight = chooseDimension(heightMode, heightSize);
int chosenDimension = Math.min(chosenWidth, chosenHeight);
setMeasuredDimension(chosenDimension, chosenDimension);
}
private int chooseDimension(int mode, int size) {
if (mode == MeasureSpec.AT_MOST || mode == MeasureSpec.EXACTLY) {
return size;
} else { // (mode == MeasureSpec.UNSPECIFIED)
return getPreferredSize();
}
}
// in case there is no size specified
private int getPreferredSize() {
return 300;
}
private void drawRim(Canvas canvas) {
// first, draw the metallic body
canvas.drawOval(rimRect, rimPaint);
// now the outer rim circle
canvas.drawOval(rimRect, rimCirclePaint);
}
private void drawFace(Canvas canvas) {
canvas.drawOval(faceRect, facePaint);
// draw the inner rim circle
canvas.drawOval(faceRect, rimCirclePaint);
// draw the rim shadow inside the face
canvas.drawOval(faceRect, rimShadowPaint);
}
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}
private int nickToDegree(int nick) {
int rawDegree = ((nick < totalNicks / 2) ? nick : (nick - totalNicks)) * 2;
int shiftedDegree = rawDegree + centerDegree;
return shiftedDegree;
}
private float degreeToAngle(float degree) {
return (degree - centerDegree) / 2.0f * degreesPerNick;
}
private void drawTitle(Canvas canvas) {
String title = getTitle();
canvas.drawTextOnPath(title, titlePath, 0.0f,0.0f, titlePaint);
}
private void drawLogo(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.translate(0.5f - logo.getWidth() * logoScale / 2.0f,
0.5f - logo.getHeight() * logoScale / 2.0f);
int color = 0x00000000;
float position = getRelativePressurePosition();
if (position < 0) {
color |= (int) ((0xf0) * -position); // blue
} else {
color |= ((int) ((0xf0) * position)) << 16; // red
}
//Log.d(TAG, "*** " + Integer.toHexString(color));
LightingColorFilter logoFilter = new LightingColorFilter(0xff338822, color);
logoPaint.setColorFilter(logoFilter);
canvas.drawBitmap(logo, logoMatrix, logoPaint);
canvas.restore();
}
private void drawHand(Canvas canvas) {
if (handInitialized) {
float handAngle = degreeToAngle(handPosition);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.rotate(handAngle, 0.5f, 0.5f);
canvas.drawPath(handPath, handPaint);
canvas.restore();
canvas.drawCircle(0.5f, 0.5f, 0.01f, handScrewPaint);
}
}
private void drawBackground(Canvas canvas) {
if (background == null) {
Log.w(TAG, "Background not created");
} else {
canvas.drawBitmap(background, 0, 0, backgroundPaint);
}
}
#Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
float scale = (float) getWidth();
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(scale, scale);
drawLogo(canvas);
drawHand(canvas);
canvas.restore();
if (handNeedsToMove()) {
moveHand();
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
Log.d(TAG, "Size changed to " + w + "x" + h);
regenerateBackground();
}
private void regenerateBackground() {
// free the old bitmap
if (background != null) {
background.recycle();
}
background = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas backgroundCanvas = new Canvas(background);
float scale = (float) getWidth();
backgroundCanvas.scale(scale, scale);
drawRim(backgroundCanvas);
drawFace(backgroundCanvas);
drawScale(backgroundCanvas);
drawTitle(backgroundCanvas);
}
private boolean handNeedsToMove() {
return Math.abs(handPosition - handTarget) > 0.001f;
}
private void moveHand() {
if (! handNeedsToMove()) {
return;
}
if (lastHandMoveTime != -1L) {
long currentTime = System.currentTimeMillis();
float delta = (currentTime - lastHandMoveTime) / 1000.0f;
float direction = Math.signum(handVelocity);
if (Math.abs(handVelocity) < 90.0f) {
handAcceleration = 5.0f * (handTarget - handPosition);
} else {
handAcceleration = 0.0f;
}
handPosition += handVelocity * delta;
handVelocity += handAcceleration * delta;
if ((handTarget - handPosition) * direction < 0.01f * direction) {
handPosition = handTarget;
handVelocity = 0.0f;
handAcceleration = 0.0f;
lastHandMoveTime = -1L;
} else {
lastHandMoveTime = System.currentTimeMillis();
}
invalidate();
} else {
lastHandMoveTime = System.currentTimeMillis();
moveHand();
}
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.values.length > 0) {
float pressureAtm = sensorEvent.values[0]/20;
//Log.i(TAG, "*** Temperature: " + temperatureC);
float pressureMmg = (9.0f / 5.0f) * pressureAtm + 32.0f;
setHandTarget(pressureAtm);
} else {
Log.w(TAG, "Empty sensor event received");
}
}
private float getRelativePressurePosition() {
if (handPosition < centerDegree) {
return - (centerDegree - handPosition) / (float) (centerDegree - minDegrees);
} else {
return (handPosition - centerDegree) / (float) (maxDegrees - centerDegree);
}
}
private void setHandTarget(float pressure) {
if (pressure < minDegrees) {
pressure = minDegrees;
} else if (pressure > maxDegrees) {
pressure = maxDegrees;
}
handTarget = pressure;
handInitialized = true;
invalidate();
}
}
This is focus on drawScale() method:
private void drawScale(Canvas canvas) {
canvas.drawOval(scaleRect, scalePaint);
canvas.save(Canvas.MATRIX_SAVE_FLAG);
for (int i = 0; i < totalNicks; ++i) {
float y1 = scaleRect.top;
float y2 = y1 - 0.020f;
canvas.drawLine(0.5f, y1, 0.5f, y2, scalePaint);
if ((i % 5) == 0) {
int value = nickToDegree(i);
if ((value >= minDegrees) && (value <= maxDegrees)) {
String valueString = Integer.toString(value);
canvas.drawText(valueString, 0.5f, y2 - 0.015f, scalePaint);
}
}
canvas.rotate(degreesPerNick, 0.5f, 0.5f);
}
canvas.restore();
}
I've some sample code that comes with Android that distorts a bitmap image(Bitmapmesh.java). I'm wanting a circle placed on my image that gives a fisheye effect. I'm new to android and especially graphics, is it possible to create this effect in the bitmapmesh sample?
I'm not sure where to start with this so any pointers would be appreciated. Can anyone give me a high level view of what's involved, eg i'd like to place a circle on the image firstly. i've placed buttons over images before that seem to float, this was done by using a relative layout then adding child buttons. what i'm tring to do now is different and will probably involve calling some onDraw method? i also have an algorithm that does the distortion, i'm just not sure how to apply this to the image.
Below is the bitmapmesh code. Can anyone talk me through where to start, even if it's just placing the circle on the image first, then i can tackle implementing the effect.
thanks mat
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import android.content.Context;
import android.graphics.;
import android.os.Bundle;
import android.os.Environment;
import android.view.;
import android.util.FloatMath;
public class BitMapFishEye extends GraphicsActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new SampleView(this));
}
private static class SampleView extends View {
private static final int WIDTH = 20;
private static final int HEIGHT = 20;
private static final int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private final Bitmap mBitmap;
private final float[] mVerts = new float[COUNT*2];
private final float[] mOrig = new float[COUNT*2];
private final Matrix mMatrix = new Matrix();
private final Matrix mInverse = new Matrix();
private File tempFile;
private byte[] imageArray;
private static void setXY(float[] array, int index, float x, float y) {
array[index*2 + 0] = x;
array[index*2 + 1] = y;
}
public SampleView(Context context) {
super(context);
setFocusable(true);
/* mBitmap = BitmapFactory.decodeResource(getResources(),
R.drawable.beach);*/
tempFile = new File(Environment.getExternalStorageDirectory().
getAbsolutePath() + "/"+"image.jpg");
imageArray = new byte[(int)tempFile.length()];
try{
InputStream is = new FileInputStream(tempFile);
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);
int i = 0;
while (dis.available() > 0) {
imageArray[i] = dis.readByte();
i++;
}
dis.close();
} catch (Exception e) {
e.printStackTrace();
}
BitmapFactory.Options bfo = new BitmapFactory.Options();
bfo.inSampleSize = 5;
mBitmap = BitmapFactory.decodeByteArray(imageArray, 0, imageArray.length, bfo);
float w = mBitmap.getWidth();
float h = mBitmap.getHeight();
// construct our mesh
int index = 0;
for (int y = 0; y <= HEIGHT; y++) {
float fy = h * y / HEIGHT;
for (int x = 0; x <= WIDTH; x++) {
float fx = w * x / WIDTH;
setXY(mVerts, index, fx, fy);
setXY(mOrig, index, fx, fy);
index += 1;
}
}
mMatrix.setTranslate(10, 10);
mMatrix.invert(mInverse);
}
#Override protected void onDraw(Canvas canvas) {
canvas.drawColor(0xFFCCCCCC);
canvas.concat(mMatrix);
canvas.drawBitmapMesh(mBitmap, WIDTH, HEIGHT, mVerts, 0,
null, 0, null);
}
private void warp(float cx, float cy) {
final float K = 10000;
float[] src = mOrig;
float[] dst = mVerts;
for (int i = 0; i < COUNT*2; i += 2) {
float x = src[i+0];
float y = src[i+1];
float dx = cx - x;
float dy = cy - y;
float dd = dx*dx + dy*dy;
float d = FloatMath.sqrt(dd);
float pull = K / (dd + 0.000001f);
pull /= (d + 0.000001f);
// android.util.Log.d("skia", "index " + i + " dist=" + d + " pull=" + pull);
if (pull >= 1) {
dst[i+0] = cx;
dst[i+1] = cy;
} else {
dst[i+0] = x + dx * pull;
dst[i+1] = y + dy * pull;
}
}
}
private int mLastWarpX = -9999; // don't match a touch coordinate
private int mLastWarpY;
#Override public boolean onTouchEvent(MotionEvent event) {
float[] pt = { event.getX(), event.getY() };
mInverse.mapPoints(pt);
int x = (int)pt[0];
int y = (int)pt[1];
if (mLastWarpX != x || mLastWarpY != y) {
mLastWarpX = x;
mLastWarpY = y;
warp(pt[0], pt[1]);
invalidate();
}
return true;
}
}
}