I'm new in Android programming, and I've tried to write a simple app recently, just for practice! In this one, I want to color an image on user's tap, but I don't know how to start it. I've read different topics which say to use the "Flood Fill" algorithm. I've found it on the web, but I don't know how to put it into my simple app.
The code I've found:
private void FloodFill(Bitmap bmp, Point pt, int targetColor, int replacementColor)
{
Queue<Point> q = new LinkedList<Point>();
q.add(pt);
while (q.size() > 0) {
Point n = q.poll();
if (bmp.getPixel(n.x, n.y) != targetColor)
continue;
Point w = n, e = new Point(n.x + 1, n.y);
while ((w.x > 0) && (bmp.getPixel(w.x, w.y) == targetColor)) {
bmp.setPixel(w.x, w.y, replacementColor);
if ((w.y > 0) && (bmp.getPixel(w.x, w.y - 1) == targetColor))
q.add(new Point(w.x, w.y - 1));
if ((w.y < bmp.getHeight() - 1) && (bmp.getPixel(w.x, w.y + 1) == targetColor))
q.add(new Point(w.x, w.y + 1));
w.x--;
}
while ((e.x < bmp.getWidth() - 1) && (bmp.getPixel(e.x, e.y) == targetColor)) {
bmp.setPixel(e.x, e.y, replacementColor);
if ((e.y > 0) && (bmp.getPixel(e.x, e.y - 1) == targetColor))
q.add(new Point(e.x, e.y - 1));
if ((e.y < bmp.getHeight() - 1) && (bmp.getPixel(e.x, e.y + 1) == targetColor))
q.add(new Point(e.x, e.y + 1));
e.x++;
}
}
}
I know how to draw lines on the screen following user's finger on touch event, but I'd also like to know how to fill a given image with some color, for example this one:
A little lion!
I saw these other questions on stack overflow:
First topic
Second topic
Third topic
It seems so easy to do, but I can't! Can you show me a little example please? I'd like to know how to set the canvas, the image to color, and how to do it.
android using flood fill algorithm getting out of memory exception. Check the link has an example.
You need the the co-ordinates of x and y touch and you can use asynctask to floofill a closed area. Use a progressdialog untill the floodfill fills the closed area with replacement color.
Note: I have faced problem when coloring large closed are. It took lot of time. I am not sure if using asynctask is the beast way. I hope someone can clarify on that part
You can modify the below according to your needs.
final Point p1 = new Point();
p1.x=(int) x; //x co-ordinate where the user touches on the screen
p1.y=(int) y; //y co-ordinate where the user touches on the screen
FloodFill f= new FloodFill();
f.floodFill(bmp,pt,targetColor,replacementColor);
FloodFill algorithm to fill a closed area
public class FloodFill {
public void floodFill(Bitmap image, Point node, int targetColor,
int replacementColor) {
int width = image.getWidth();
int height = image.getHeight();
int target = targetColor;
int replacement = replacementColor;
if (target != replacement) {
Queue<Point> queue = new LinkedList<Point>();
do {
int x = node.x;
int y = node.y;
while (x > 0 && image.getPixel(x - 1, y) == target) {
x--;
}
boolean spanUp = false;
boolean spanDown = false;
while (x < width && image.getPixel(x, y) == target) {
image.setPixel(x, y, replacement);
if (!spanUp && y > 0 && image.getPixel(x, y - 1) == target) {
queue.add(new Point(x, y - 1));
spanUp = true;
} else if (spanUp && y > 0
&& image.getPixel(x, y - 1) != target) {
spanUp = false;
}
if (!spanDown && y < height - 1
&& image.getPixel(x, y + 1) == target) {
queue.add(new Point(x, y + 1));
spanDown = true;
} else if (spanDown && y < height - 1
&& image.getPixel(x, y + 1) != target) {
spanDown = false;
}
x++;
}
} while ((node = queue.poll()) != null);
}
}
}
Edit:
Edit 8-7-2014 :
Filling a small closed area works fine with the above flood fill algorithm. However for large area the algorithm works slow and consumes lot of memory. Recently i came across a post which uses QueueLinear Flood Fill which is way faster that the above.
Source :
http://www.codeproject.com/Articles/16405/Queue-Linear-Flood-Fill-A-Fast-Flood-Fill-Algorith
Code :
public class QueueLinearFloodFiller {
protected Bitmap image = null;
protected int[] tolerance = new int[] { 0, 0, 0 };
protected int width = 0;
protected int height = 0;
protected int[] pixels = null;
protected int fillColor = 0;
protected int[] startColor = new int[] { 0, 0, 0 };
protected boolean[] pixelsChecked;
protected Queue<FloodFillRange> ranges;
// Construct using an image and a copy will be made to fill into,
// Construct with BufferedImage and flood fill will write directly to
// provided BufferedImage
public QueueLinearFloodFiller(Bitmap img) {
copyImage(img);
}
public QueueLinearFloodFiller(Bitmap img, int targetColor, int newColor) {
useImage(img);
setFillColor(newColor);
setTargetColor(targetColor);
}
public void setTargetColor(int targetColor) {
startColor[0] = Color.red(targetColor);
startColor[1] = Color.green(targetColor);
startColor[2] = Color.blue(targetColor);
}
public int getFillColor() {
return fillColor;
}
public void setFillColor(int value) {
fillColor = value;
}
public int[] getTolerance() {
return tolerance;
}
public void setTolerance(int[] value) {
tolerance = value;
}
public void setTolerance(int value) {
tolerance = new int[] { value, value, value };
}
public Bitmap getImage() {
return image;
}
public void copyImage(Bitmap img) {
// Copy data from provided Image to a BufferedImage to write flood fill
// to, use getImage to retrieve
// cache data in member variables to decrease overhead of property calls
width = img.getWidth();
height = img.getHeight();
image = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(image);
canvas.drawBitmap(img, 0, 0, null);
pixels = new int[width * height];
image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
public void useImage(Bitmap img) {
// Use a pre-existing provided BufferedImage and write directly to it
// cache data in member variables to decrease overhead of property calls
width = img.getWidth();
height = img.getHeight();
image = img;
pixels = new int[width * height];
image.getPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
protected void prepare() {
// Called before starting flood-fill
pixelsChecked = new boolean[pixels.length];
ranges = new LinkedList<FloodFillRange>();
}
// Fills the specified point on the bitmap with the currently selected fill
// color.
// int x, int y: The starting coords for the fill
public void floodFill(int x, int y) {
// Setup
prepare();
if (startColor[0] == 0) {
// ***Get starting color.
int startPixel = pixels[(width * y) + x];
startColor[0] = (startPixel >> 16) & 0xff;
startColor[1] = (startPixel >> 8) & 0xff;
startColor[2] = startPixel & 0xff;
}
// ***Do first call to floodfill.
LinearFill(x, y);
// ***Call floodfill routine while floodfill ranges still exist on the
// queue
FloodFillRange range;
while (ranges.size() > 0) {
// **Get Next Range Off the Queue
range = ranges.remove();
// **Check Above and Below Each Pixel in the Floodfill Range
int downPxIdx = (width * (range.Y + 1)) + range.startX;
int upPxIdx = (width * (range.Y - 1)) + range.startX;
int upY = range.Y - 1;// so we can pass the y coord by ref
int downY = range.Y + 1;
for (int i = range.startX; i <= range.endX; i++) {
// *Start Fill Upwards
// if we're not above the top of the bitmap and the pixel above
// this one is within the color tolerance
if (range.Y > 0 && (!pixelsChecked[upPxIdx])
&& CheckPixel(upPxIdx))
LinearFill(i, upY);
// *Start Fill Downwards
// if we're not below the bottom of the bitmap and the pixel
// below this one is within the color tolerance
if (range.Y < (height - 1) && (!pixelsChecked[downPxIdx])
&& CheckPixel(downPxIdx))
LinearFill(i, downY);
downPxIdx++;
upPxIdx++;
}
}
image.setPixels(pixels, 0, width, 1, 1, width - 1, height - 1);
}
// Finds the furthermost left and right boundaries of the fill area
// on a given y coordinate, starting from a given x coordinate, filling as
// it goes.
// Adds the resulting horizontal range to the queue of floodfill ranges,
// to be processed in the main loop.
// int x, int y: The starting coords
protected void LinearFill(int x, int y) {
// ***Find Left Edge of Color Area
int lFillLoc = x; // the location to check/fill on the left
int pxIdx = (width * y) + x;
while (true) {
// **fill with the color
pixels[pxIdx] = fillColor;
// **indicate that this pixel has already been checked and filled
pixelsChecked[pxIdx] = true;
// **de-increment
lFillLoc--; // de-increment counter
pxIdx--; // de-increment pixel index
// **exit loop if we're at edge of bitmap or color area
if (lFillLoc < 0 || (pixelsChecked[pxIdx]) || !CheckPixel(pxIdx)) {
break;
}
}
lFillLoc++;
// ***Find Right Edge of Color Area
int rFillLoc = x; // the location to check/fill on the left
pxIdx = (width * y) + x;
while (true) {
// **fill with the color
pixels[pxIdx] = fillColor;
// **indicate that this pixel has already been checked and filled
pixelsChecked[pxIdx] = true;
// **increment
rFillLoc++; // increment counter
pxIdx++; // increment pixel index
// **exit loop if we're at edge of bitmap or color area
if (rFillLoc >= width || pixelsChecked[pxIdx] || !CheckPixel(pxIdx)) {
break;
}
}
rFillLoc--;
// add range to queue
FloodFillRange r = new FloodFillRange(lFillLoc, rFillLoc, y);
ranges.offer(r);
}
// Sees if a pixel is within the color tolerance range.
protected boolean CheckPixel(int px) {
int red = (pixels[px] >>> 16) & 0xff;
int green = (pixels[px] >>> 8) & 0xff;
int blue = pixels[px] & 0xff;
return (red >= (startColor[0] - tolerance[0])
&& red <= (startColor[0] + tolerance[0])
&& green >= (startColor[1] - tolerance[1])
&& green <= (startColor[1] + tolerance[1])
&& blue >= (startColor[2] - tolerance[2]) && blue <= (startColor[2] + tolerance[2]));
}
// Represents a linear range to be filled and branched from.
protected class FloodFillRange {
public int startX;
public int endX;
public int Y;
public FloodFillRange(int startX, int endX, int y) {
this.startX = startX;
this.endX = endX;
this.Y = y;
}
}
}
Thanks to the stackoverflow's users I've gotten to the right solution!
I wanted to know how to use the flood fill algorithm and integrate it in a simple Android project, and this is what I did:
Java code:
import java.util.LinkedList;
import java.util.Queue;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
public class Main extends Activity {
private RelativeLayout dashBoard;
private MyView myView;
public ImageView image;
Button b_red, b_blue, b_green, b_orange, b_clear;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(R.layout.activity_main);
findViewById(R.id.dashBoard);
b_red = (Button) findViewById(R.id.b_red);
b_blue = (Button) findViewById(R.id.b_blue);
b_green = (Button) findViewById(R.id.b_green);
b_orange = (Button) findViewById(R.id.b_orange);
b_red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
myView.changePaintColor(0xFFFF0000);
}
});
b_blue.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
myView.changePaintColor(0xFF0000FF);
}
});
b_green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
myView.changePaintColor(0xFF00FF00);
}
});
b_orange.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
myView.changePaintColor(0xFFFF9900);
}
});
dashBoard = (RelativeLayout) findViewById(R.id.dashBoard);
dashBoard.addView(myView);
}
public class MyView extends View {
private Paint paint;
private Path path;
public Bitmap mBitmap;
public ProgressDialog pd;
final Point p1 = new Point();
public Canvas canvas;
//Bitmap mutableBitmap ;
public MyView(Context context) {
super(context);
this.paint = new Paint();
this.paint.setAntiAlias(true);
pd = new ProgressDialog(context);
this.paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(5f);
mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.forme).copy(Bitmap.Config.ARGB_8888, true);
this.path = new Path();
}
#Override
protected void onDraw(Canvas canvas) {
this.canvas = canvas;
this.paint.setColor(Color.RED);
canvas.drawBitmap(mBitmap, 0, 0, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
p1.x = (int) x;
p1.y = (int) y;
final int sourceColor = mBitmap.getPixel((int) x, (int) y);
final int targetColor = paint.getColor();
new TheTask(mBitmap, p1, sourceColor, targetColor).execute();
invalidate();
}
return true;
}
public void clear() {
path.reset();
invalidate();
}
public int getCurrentPaintColor() {
return paint.getColor();
}
public void changePaintColor(int color){
this.paint.setColor(color);
}
class TheTask extends AsyncTask<Void, Integer, Void> {
Bitmap bmp;
Point pt;
int replacementColor, targetColor;
public TheTask(Bitmap bm, Point p, int sc, int tc) {
this.bmp = bm;
this.pt = p;
this.replacementColor = tc;
this.targetColor = sc;
pd.setMessage("Filling....");
pd.show();
}
#Override
protected void onPreExecute() {
pd.show();
}
#Override
protected void onProgressUpdate(Integer... values) {
}
#Override
protected Void doInBackground(Void... params) {
FloodFill f = new FloodFill();
f.floodFill(bmp, pt, targetColor, replacementColor);
return null;
}
#Override
protected void onPostExecute(Void result) {
pd.dismiss();
invalidate();
}
}
}
// flood fill
public class FloodFill {
public void floodFill(Bitmap image, Point node, int targetColor, int replacementColor) {
int width = image.getWidth();
int height = image.getHeight();
int target = targetColor;
int replacement = replacementColor;
if (target != replacement) {
Queue<Point> queue = new LinkedList<Point>();
do {
int x = node.x;
int y = node.y;
while (x > 0 && image.getPixel(x - 1, y) == target) {
x--;
}
boolean spanUp = false;
boolean spanDown = false;
while (x < width && image.getPixel(x, y) == target) {
image.setPixel(x, y, replacement);
if (!spanUp && y > 0 && image.getPixel(x, y - 1) == target) {
queue.add(new Point(x, y - 1));
spanUp = true;
} else if (spanUp && y > 0 && image.getPixel(x, y - 1) != target) {
spanUp = false;
}
if (!spanDown && y < height - 1 && image.getPixel(x, y + 1) == target) {
queue.add(new Point(x, y + 1));
spanDown = true;
} else if (spanDown && y < (height - 1) && image.getPixel(x, y + 1) != target) {
spanDown = false;
}
x++;
}
} while ((node = queue.poll()) != null);
}
}
}
}
And this is the XML code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/drawingLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".Main" >
<RelativeLayout
android:id="#+id/dashBoard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#+id/b_red"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="10dp" >
</RelativeLayout>
<Button
android:id="#+id/b_red"
android:layout_width="65dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:background="#FF0000" />
<Button
android:id="#+id/b_green"
android:layout_width="65dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/b_red"
android:background="#00FF00" />
<Button
android:id="#+id/b_blue"
android:layout_width="65dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/b_green"
android:background="#0000FF" />
<Button
android:id="#+id/b_orange"
android:layout_width="65dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_toRightOf="#+id/b_blue"
android:background="#FF9900" />
<Button
android:id="#+id/button5"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:text="Clear" />
</RelativeLayout>
I hope it to be helpfull for you!!!
Have a nice day!!!
Related
I'm working with an android studio project. I'm trying to select user touched pixel on an image, as shown Fig 1.
I want to add (X) on a pixel touched by the user, how can I make it by modifying bitmap ?
This is image.
If I understand your question correctly, you want to draw X in each time the user click on Bitmap, here's what you need :
ImageView img;
int clickCount = 0;
Bitmap src;
int colorTarget = Color.BLACK;
#SuppressLint("ClickableViewAccessibility")
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img = findViewById(R.id.img);
img.setOnTouchListener((v, event) -> {
int x = (int) event.getX();
int y = (int) event.getY();
if (event.getAction() == MotionEvent.ACTION_DOWN) {
drawXByPosition(img, src, x, y);
} else if (event.getAction() == MotionEvent.ACTION_UP) {
clickCount++;
if (clickCount % 2 == 0) colorTarget = Color.BLACK;
else colorTarget = Color.WHITE;
}
return true;
});
}
private void drawXByPosition(ImageView iv, Bitmap bm, int x, int y) {
if (x < 0 || y < 0 || x > iv.getWidth() || y > iv.getHeight())
Toast.makeText(getApplicationContext(), "Outside of ImageView", Toast.LENGTH_SHORT).show();
else {
int projectedX = (int) ((double) x * ((double) bm.getWidth() / (double) iv.getWidth()));
int projectedY = (int) ((double) y * ((double) bm.getHeight() / (double) iv.getHeight()));
src = drawX(src, "X", 44, colorTarget, projectedX, projectedY);
img.setImageBitmap(src);
}
}
public Bitmap drawX(final Bitmap src,
final String content,
final float textSize,
#ColorInt final int color,
final float x,
final float y) {
Bitmap ret = src.copy(src.getConfig(), true);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
Canvas canvas = new Canvas(ret);
paint.setColor(color);
paint.setTextSize(textSize);
Rect bounds = new Rect();
paint.getTextBounds(content, 0, content.length(), bounds);
canvas.drawText(content, x, y + textSize, paint);
return ret;
}
I have a CustomView and an Image view. The CustomView is a ball that moves around the screen and bounces off the walls. The Image is a quarter circle that you can rotate in a circle on touch. I am trying to make my game so that when the filled pixels from the CustomView cross paths with the Filled pixels from the ImageView a collision is detected. The problem that I am having is I do not know how to retrieve where the filled pixels are on each view.
Here is my XML code
<com.leytontaylor.bouncyballz.AnimatedView
android:id="#+id/anim_view"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
/>
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/quartCircle"
android:layout_gravity="center_horizontal"
android:src="#drawable/quartercircle"
android:scaleType="matrix"/>
My MainActivity
public class MainActivity extends AppCompatActivity {
private static Bitmap imageOriginal, imageScaled;
private static Matrix matrix;
private ImageView dialer;
private int dialerHeight, dialerWidth;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// load the image only once
if (imageOriginal == null) {
imageOriginal = BitmapFactory.decodeResource(getResources(), R.drawable.quartercircle);
}
// initialize the matrix only once
if (matrix == null) {
matrix = new Matrix();
} else {
// not needed, you can also post the matrix immediately to restore the old state
matrix.reset();
}
dialer = (ImageView) findViewById(R.id.quartCircle);
dialer.setOnTouchListener(new MyOnTouchListener());
dialer.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// method called more than once, but the values only need to be initialized one time
if (dialerHeight == 0 || dialerWidth == 0) {
dialerHeight = dialer.getHeight();
dialerWidth = dialer.getWidth();
// resize
Matrix resize = new Matrix();
resize.postScale((float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getWidth(), (float) Math.min(dialerWidth, dialerHeight) / (float) imageOriginal.getHeight());
imageScaled = Bitmap.createBitmap(imageOriginal, 0, 0, imageOriginal.getWidth(), imageOriginal.getHeight(), resize, false);
// translate to the image view's center
float translateX = dialerWidth / 2 - imageScaled.getWidth() / 2;
float translateY = dialerHeight / 2 - imageScaled.getHeight() / 2;
matrix.postTranslate(translateX, translateY);
dialer.setImageBitmap(imageScaled);
dialer.setImageMatrix(matrix);
}
}
});
}
MyOnTouchListener class:
private class MyOnTouchListener implements View.OnTouchListener {
private double startAngle;
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startAngle = getAngle(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
double currentAngle = getAngle(event.getX(), event.getY());
rotateDialer((float) (startAngle - currentAngle));
startAngle = currentAngle;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
private double getAngle(double xTouch, double yTouch) {
double x = xTouch - (dialerWidth / 2d);
double y = dialerHeight - yTouch - (dialerHeight / 2d);
switch (getQuadrant(x, y)) {
case 1:
return Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 2:
return 180 - Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
case 3:
return 180 + (-1 * Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI);
case 4:
return 360 + Math.asin(y / Math.hypot(x, y)) * 180 / Math.PI;
default:
return 0;
}
}
/**
* #return The selected quadrant.
*/
private static int getQuadrant(double x, double y) {
if (x >= 0) {
return y >= 0 ? 1 : 4;
} else {
return y >= 0 ? 2 : 3;
}
}
/**
* Rotate the dialer.
*
* #param degrees The degrees, the dialer should get rotated.
*/
private void rotateDialer(float degrees) {
matrix.postRotate(degrees, dialerWidth / 2, dialerHeight / 2);
dialer.setImageMatrix(matrix);
}
And my AnimatedView
public class AnimatedView extends ImageView {
private Context mContext;
int x = -1;
int y = -1;
private int xVelocity = 10;
private int yVelocity = 5;
private Handler h;
private final int FRAME_RATE = 60;
public AnimatedView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
h = new Handler();
}
private Runnable r = new Runnable() {
#Override
public void run() {
invalidate();
}
};
protected void onDraw(Canvas c) {
BitmapDrawable ball = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.smallerball);
if (x<0 && y <0) {
x = this.getWidth()/2;
y = this.getHeight()/2;
} else {
x += xVelocity;
y += yVelocity;
if ((x > this.getWidth() - ball.getBitmap().getWidth()) || (x < 0)) {
xVelocity = xVelocity*-1;
}
if ((y > this.getHeight() - ball.getBitmap().getHeight()) || (y < 0)) {
yVelocity = yVelocity*-1;
}
}
c.drawBitmap(ball.getBitmap(), x, y, null);
h.postDelayed(r, FRAME_RATE);
}
public float getX() {
return x;
}
public float getY() {
return y;
}
}
My question is: How can I retrieve the filled pixels from both of these views, and pass them through a function that detects a collision.
Thanks in advance for the help!:)
You could encapsulate your images with Array of points marking your border coordinates(and update them on move/calculate them based on origin) then you'll be able to decide whether oposing object are in touch or not(if any of oposing arrays share the same point)
You really need to define "filled pixels". I assume you mean the non-transparent pixels. The easiest way to find those, is by converting your entire view into a bitmap and iterating through its pixels. You can convert a View into a Bitmap like this:
private Bitmap getBitmapFromView(View view) {
view.buildDrawingCache();
Bitmap returnedBitmap = Bitmap.createBitmap(view.measuredWidth,
view.measuredHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(returnedBitmap);
canvas.drawColor(Color.WHITE, PorterDuff.Mode.SRC_IN);
Drawable drawable = view.background;
drawable.draw(canvas);
view.draw(canvas);
return returnedBitmap;
}
You'd also need to get the absolute location of the views:
Point getViewLocationOnScreen(View v) {
int[] coor = new int[]{0, 0};
v.getLocationOnScreen(coor);
return new Point(coor[0], coor[1]);
}
Then you just have to iterate through the pixels of each Bitmap, check their colors to know whether they're "filled", and also check whether they're overlapping based on their coordination inside the bitmaps and the absolute location of the views on screen.
Iterating through a bitmap's is done like this:
for (int x = 0; x < myBitmap.getWidth(); x++) {
for (int y = 0; y < myBitmap.getHeight(); y++) {
int color = myBitmap.getPixel(x, y);
}
}
But I must say, I don't think performing this sort of heavy computations on UI thread is really a good idea. There are dozens of much better ways to detect collisions than pixel-perfect checking. This will probably come out extremely laggy.
For an android app im coding i am trying to make it so when the users taps the screen the "player" which is an image slides over until it reaches that x value
I tried calling this method when the player touches the screen in a MotionEvent
public void movementUpdate(int x){
if(goalX < x){
goalX -= width;
}
if(goalX > x){
goalX += width;
}
goalX = x;
}
then in an update method i call
public void update(){
if(goalX > x)
x += 4;
if(goalX < x)
x -= 4;
}
but the image always goes past it or not far enough
Also since the image is drawn in java from the left corner when you tap infront of the image do you have to minus 1/2 the width so it stops in the middle and not the edge, and if you click behind minus 1/2 too?
I made a basic structure for you to use also. If you paste the code you may have to add the package name, change the activity name, change the layout name, and add a png image named grey_star to the drawable folder.
import android.os.CountDownTimer;
import android.provider.ContactsContract;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
//the vars that contains the users touch coords, I set some place holders the disapear as soon
//as the user touches the screen
private float touchX = 100;
private float touchY = 100;
//number of pixels to be moved every tick you should
//calculate them based on the screen size in a setup method
private int xSpeed = 10;
private int ySpeed = 10;
//bounds of the screen
private int leftBound;
private int rightBound;
private int topBound;
private int bottomBound;
//screen dimensions
private float pxScreenWidth;
private float pxScreenHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//make the scree go fullscreen
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
//remove action bar, and set content view
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
actionBar.hide();
//obtain the width and height of the screen in pixels
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
pxScreenHeight = displayMetrics.heightPixels;
pxScreenWidth = displayMetrics.widthPixels;
//set bounds
leftBound = 0;
rightBound = (int) pxScreenWidth;
topBound = 0;
bottomBound = (int) pxScreenHeight;
mainGame();
}
private void mainGame() {
//some kind of timer I am lazy and used the countdown for this example
//it does something every millisecond
//first value is number of ticks second is the size 1 = 1ms 1000=1s
new CountDownTimer(1000000, 1) {
public void onTick(long millisUntilFinished) {
//get the image view
ImageView thing = (ImageView) findViewById(R.id.thing);
int x = (int) thing.getX();
int y = (int) thing.getY();
moveThing(x, y, (int) touchX, (int) touchY);
}
public void onFinish() {
}
}.start();
}
private void moveThing(int xCoord, int yCoord, int targetX, int targetY) {
//adjust these values to center
ImageView thing = (ImageView) findViewById(R.id.thing);
targetX = targetX - thing.getWidth() / 2;
targetY = targetY - thing.getHeight() / 2;
int width = thing.getWidth();
int height = thing.getHeight();
//for recording intended position
int tempX = 0;
int tempY = 0;
//check so don't run if don't have to.
if (xCoord != targetX) {
boolean lessX = false;
//set value to move forward
int x = xCoord + xSpeed;
//but if has to move backwards set the value to move backwards
if (targetX < xCoord) {
x = xCoord - xSpeed;
lessX = true;
}
//if the amount of pixes goes over the target set it to the target
if (lessX == false && x > targetX) {
x = targetX;
} else if (lessX == true && x < targetX) {
x = targetX;
}
//check x bounds
int temp = checkXBounds(x, width, leftBound, rightBound);
if(temp != -1){
x = temp;
}
//draw the thing in the new location
if (xCoord < targetX || (xCoord > targetX && lessX == true)) {
thing.setX(x);
}
tempX = (int) x;
}
//check so don't run if don't have to.
if (yCoord != targetY) {
//set value to move forward
int y = yCoord + ySpeed;
boolean lessY = false;
//but if has to move backwards set the value to move backwards
if (targetY < yCoord) {
y = yCoord - ySpeed;
lessY = true;
}
//if the amount of pixes goes over the target set it to the target
if (y > targetY && lessY == false) {
y = targetY;
} else if (y < targetY && lessY == true) {
y = targetY;
}
//check y bounds
int temp = checkYBounds(y, topBound, bottomBound, height);
if(temp != -1){
y = temp;
}
//draw the thing in the new location
if (yCoord < targetY || (yCoord > targetY && lessY == true)) {
thing.setY(y);
}
tempY = (int) y;
}
TextView textView = (TextView) findViewById(R.id.coords);
textView.setText("x: " + tempX + " " + "y: " + tempY);
}
private int checkXBounds(int xCoord, int width, int leftBound, int rightBound){
if(checkLeftBound(xCoord, leftBound) == false){
return leftBound;
}
if(checkRightBound(xCoord, rightBound, width) == false){
return rightBound - width;
}
return -1;
}
private int checkYBounds(int yCoord, int topBound, int bottomBound, int height){
if(checkTopBound(yCoord, topBound) == false){
return topBound;
}
if(checkBottomBound(yCoord, bottomBound, height) == false){
return bottomBound - height;
}
return -1;
}
private boolean checkLeftBound(int xCoord, int leftBound) {
if(xCoord < leftBound){
return false;
}
return true;
}
private boolean checkRightBound(int xCoord, int rightBound, int width){
if(xCoord + width > rightBound){
return false;
}
return true;
}
private boolean checkTopBound(int yCoord, int topBound){
if(yCoord < topBound){
return false;
}
return true;
}
private boolean checkBottomBound(int yCoord, int bottomBound, int height){
if(yCoord + height > bottomBound){
return false;
}
return true;
}
//gets touch coordinates and handles moving the ship
#Override
public boolean onTouchEvent(MotionEvent event) {
//get the touch coordinates
touchX = event.getX();
touchY = event.getY();
return super.onTouchEvent(event);
}
}
This is the XML file. In a real game I would not make the ship this way. You will need to change a bit of the XML to make it work. Also, you will want to set the size of the image view a different way, because hard coding a value like I did is bad. I did it for speed.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.honeymanappdev.bradleyhoneyman.moveaimageviewbasedonusertouch.MainActivity">
<ImageView
android:id="#+id/thing"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/grey_star"/>
<TextView
android:id="#+id/coords"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"/>
I'd recommend using something else to load your bitmaps.
I read this, and would recommend using Picasso to load your bitmaps. It is an amazing service. Also it is open source.
I am writing an Android game right now and I would need some help in the collision of the wall on screen. When I drag the ball in the top and right it able to collide in wall but when I drag it faster it was able to overlap in the wall
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
// if the player moves
case MotionEvent.ACTION_MOVE: {
if (playerTouchRect.contains(x, y)) {
boolean left = false;
boolean right = false;
boolean up = false;
boolean down = false;
boolean canMove = false;
boolean foundFinish = false;
if (x != pLastXPos) {
if (x < pLastXPos) {
left = true;
} else {
right = true;
}
pLastXPos = x;
}
if (y != pLastYPos) {
if (y < pLastYPos) {
up = true;
} else {
down = true;
}
pLastYPos = y;
}
plCellRect = getRectFromPos(x, y);
newplRect.set(playerRect);
newplRect.left = x - (int) (playerRect.width() / 2);
newplRect.right = x + (int) (playerRect.width() / 2);
newplRect.top = y - (int) (playerRect.height() / 2);
newplRect.bottom = y + (int) (playerRect.height() / 2);
int currentRow = 0;
int currentCol = 0;
currentRow = getRowFromYPos(newplRect.top);
currentCol = getColFromXPos(newplRect.right);
if(!canMove){
canMove = mapManager.getCurrentTile().pMaze[currentRow][currentCol] == Cell.wall;
canMove =true;
}
finishTest = mapManager.getCurrentTile().pMaze[currentRow][currentCol];
foundA = finishTest == Cell.valueOf(letterNotGet + "");
canMove = mapManager.getCurrentTile().pMaze[currentRow][currentCol] != Cell.wall;
canMove = (finishTest == Cell.floor || finishTest == Cell.pl) && canMove;
if (canMove) {
invalidate();
setTitle();
}
if (foundA) {
mapManager.getCurrentTile().pMaze[currentRow][currentCol] = Cell.floor;
// finishTest
letterGotten.add(letterNotGet);
playCurrentLetter();
/*sounds.play(sExplosion, 1.0f, 1.0f, 0, 0, 1.5f);*/
foundS = letterNotGet == 's';
letterNotGet++;
}if(foundS){
AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
builder.setTitle(mainActivity.getText(R.string.finished_title));
LayoutInflater inflater = mainActivity.getLayoutInflater();
View view = inflater.inflate(R.layout.finish, null);
builder.setView(view);
View closeButton =view.findViewById(R.id.closeGame);
closeButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View clicked) {
if(clicked.getId() == R.id.closeGame) {
mainActivity.finish();
}
}
});
AlertDialog finishDialog = builder.create();
finishDialog.show();
}
else {
Log.d(TAG, "INFO: updated player position");
playerRect.set(newplRect);
setTouchZone();
updatePlayerCell();
}
} // end of (CASE) if playerTouch
break;
} // end of (SWITCH) Case motion
}//end of Switch
return true;
}//end of TouchEvent
private void finish() {
// TODO Auto-generated method stub
}
public int getColFromXPos(int xPos) {
val = xPos / (pvWidth / mapManager.getCurrentTile().pCols);
if (val == mapManager.getCurrentTile().pCols) {
val = mapManager.getCurrentTile().pCols - 1;
}
return val;
}
/**
* Given a y pixel position, return the row of the cell it is in This is
* used when determining the type of adjacent Cells.
*
* #param yPos
* y position in pixels
* #return The cell this position is in
*/
public int getRowFromYPos(int yPos) {
val = yPos / (pvHeight / mapManager.getCurrentTile().pRows);
if (val == mapManager.getCurrentTile().pRows) {
val = mapManager.getCurrentTile().pRows - 1;
}
return val;
}
/**
* When preserving the position we need to know which cell the player is in,
* so calculate it from the centre on its Rect
*/
public void updatePlayerCell() {
plCell.x = (playerRect.left + (playerRect.width() / 2))
/ (pvWidth / mapManager.getCurrentTile().pCols);
plCell.y = (playerRect.top + (playerRect.height() / 2))
/ (pvHeight / mapManager.getCurrentTile().pRows);
if (mapManager.getCurrentTile().pMaze[plCell.y][plCell.x] == Cell.floor) {
for (int row = 0; row < mapManager.getCurrentTile().pRows; row++) {
for (int col = 0; col < mapManager.getCurrentTile().pCols; col++) {
if (mapManager.getCurrentTile().pMaze[row][col] == Cell.pl) {
mapManager.getCurrentTile().pMaze[row][col] = Cell.floor;
break;
}
}
}
mapManager.getCurrentTile().pMaze[plCell.y][plCell.x] = Cell.pl;
}
}
public Rect getRectFromPos(int x, int y) {
calcCell.left = ((x / cellWidth) + 0) * cellWidth;
calcCell.right = calcCell.left + cellWidth;
calcCell.top = ((y / cellHeight) + 0) * cellHeight;
calcCell.bottom = calcCell.top + cellHeight;
Log.d(TAG, "Rect: " + calcCell + " Player: " + playerRect);
return calcCell;
}
public void setPlayerRect(Rect newplRect) {
playerRect.set(newplRect);
}
private void setTouchZone() {
playerTouchRect.set(
playerRect.left - playerRect.width() / TOUCH_ZONE,
playerRect.top - playerRect.height() / TOUCH_ZONE,
playerRect.right + playerRect.width() / TOUCH_ZONE,
playerRect.bottom + playerRect.height() / TOUCH_ZONE);
}
public Rect getPlayerRect() {
return playerRect;
}
public Point getPlayerCell() {
return plCell;
}
public void setPlayerCell(Point cell) {
plCell = cell;
}
}*
This is an architectural problem. You can read more about it here.
In essence, your physics simulation (as simple as might be) is coupled to your framerate (which is capped at 60fps). Any events occurring faster than 60Hz cannot be processed accurately hence your bug.
The solution is to run the collision detection on an independent thread and draw the state it calculates at 60fps.
Also, take a look at these gamedev questions that refer to the same article.
Ok so I am working on a game on Android. I need to implement pixel perfect collision detection. I already have the bounding boxes set up around each of the images, each bounding box is transformed to match the current rotation of the image. That all works great. I also have the pixel data from each bitmap stored in an array. Can someone help me figure out the most efficient way to go about detecting if the pixels overlap? Thanks in advance for any help!
I have based my code on Mayra's example and made bitmap pixel collision handling. I hope this will help.
public class CollisionUtil {
public static boolean isCollisionDetected(Sprite sprite1, Sprite sprite2){
Rect bounds1 = sprite1.getBounds();
Rect bounds2 = sprite2.getBounds();
if( Rect.intersects(bounds1, bounds2) ){
Rect collisionBounds = getCollisionBounds(bounds1, bounds2);
for (int i = collisionBounds.left; i < collisionBounds.right; i++) {
for (int j = collisionBounds.top; j < collisionBounds.bottom; j++) {
int sprite1Pixel = getBitmapPixel(sprite1, i, j);
int sprite2Pixel = getBitmapPixel(sprite2, i, j);
if( isFilled(sprite1Pixel) && isFilled(sprite2Pixel)) {
return true;
}
}
}
}
return false;
}
private static int getBitmapPixel(Sprite sprite, int i, int j) {
return sprite.getBitmap().getPixel(i-(int)sprite.getX(), j-(int)sprite.getY());
}
private static Rect getCollisionBounds(Rect rect1, Rect rect2) {
int left = (int) Math.max(rect1.left, rect2.left);
int top = (int) Math.max(rect1.top, rect2.top);
int right = (int) Math.min(rect1.right, rect2.right);
int bottom = (int) Math.min(rect1.bottom, rect2.bottom);
return new Rect(left, top, right, bottom);
}
private static boolean isFilled(int pixel) {
return pixel != Color.TRANSPARENT;
}
}
The basic idea is to create a bitmask for each object where you indicate in each pixel if the object is actually there or not. Then you compare each pixel of the bitmasks for the two objects.
You could minimize the number of pixels you need to check by calculating the rectangular area in which the two bounding boxes overlap. The pixels within this area are what you need to check.
Iterate through all of those pixels, and check if the pixel is filled in both objects. If any of them are, then you have a collision.
If your rectangles are aligned with the x/y axis, to find the overlap, find the left, right, top and bottom of the overlap. It would look something like this (I could have screwed up the edge cases, haven't tried this):
int left = max(obj1.left, obj2.left)
int right = min(obj1.right, obj2.right)
int top = min(obj1.top, obj2.top)
int bottom = max(obj1.bottom, obj2.bottom)
for (int x = left; x < right; x++) {
for (int y = top; y < bottom; y++) {
if (obj1.isFilled(x,y) && obj2.isFilled(x,y)) {
return true;
}
}
}
I changed arcones' code, so the method works with Bitmaps instead of Sprites.
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
public class KollisionsErkennung {
/**
* #param bitmap1 First bitmap
* #param x1 x-position of bitmap1 on screen.
* #param y1 y-position of bitmap1 on screen.
* #param bitmap2 Second bitmap.
* #param x2 x-position of bitmap2 on screen.
* #param y2 y-position of bitmap2 on screen.
*/
public static boolean isCollisionDetected(Bitmap bitmap1, int x1, int y1,
Bitmap bitmap2, int x2, int y2) {
Rect bounds1 = new Rect(x1, y1, x1+bitmap1.getWidth(), y1+bitmap1.getHeight());
Rect bounds2 = new Rect(x2, y2, x2+bitmap2.getWidth(), y2+bitmap2.getHeight());
if (Rect.intersects(bounds1, bounds2)) {
Rect collisionBounds = getCollisionBounds(bounds1, bounds2);
for (int i = collisionBounds.left; i < collisionBounds.right; i++) {
for (int j = collisionBounds.top; j < collisionBounds.bottom; j++) {
int bitmap1Pixel = bitmap1.getPixel(i-x1, j-y1);
int bitmap2Pixel = bitmap2.getPixel(i-x2, j-y2);
if (isFilled(bitmap1Pixel) && isFilled(bitmap2Pixel)) {
return true;
}
}
}
}
return false;
}
private static Rect getCollisionBounds(Rect rect1, Rect rect2) {
int left = (int) Math.max(rect1.left, rect2.left);
int top = (int) Math.max(rect1.top, rect2.top);
int right = (int) Math.min(rect1.right, rect2.right);
int bottom = (int) Math.min(rect1.bottom, rect2.bottom);
return new Rect(left, top, right, bottom);
}
private static boolean isFilled(int pixel) {
return pixel != Color.TRANSPARENT;
}
}
For my needs it works fast enough.
If anyone of you is interested, I'd like to share the code I wrote:
Important for you to know is that Sprite.getWidth() and Sprite.getHeight() simply return the width/height of the Bitmap that the Sprite holds. You can easily adjust the code for your needs, it should be pretty easy to understand how the code works :)
public static boolean touchesSprite(Sprite s1, Sprite s2) {
Bitmap b1 = s1.getBmp();
Bitmap b2 = s2.getBmp();
int xshift = s2.getX()-s1.getX();
int yshift = s2.getY()-s1.getY();
//Test if the Sprites overlap at all
if((xshift > 0 && xshift > s1.getWidth()) || (xshift < 0 && -xshift > s2.getWidth())) {
return false;
}
if((yshift > 0 && yshift > s1.getHeight()) || (yshift < 0 && -yshift > s2.getHeight())) {
return false;
}
//if they overlap, find out in which regions they do
int leftx, rightx, topy, bottomy;
int leftx2, topy2;
if(xshift >= 0) {
leftx = xshift;
leftx2 = 0;
rightx = Math.min(s1.getWidth(), s2.getWidth()+xshift);
} else {
rightx = Math.min(s1.getWidth(), s2.getWidth()+xshift);
leftx = 0;
leftx2 = -xshift;
}
if(yshift >= 0) {
topy = yshift;
topy2 = 0;
bottomy = Math.min(s1.getHeight(), s2.getHeight()+yshift);
} else {
bottomy = Math.min(s1.getHeight(), s2.getHeight()+yshift);
topy = 0;
topy2 = -yshift;
}
//then compare the overlapping regions,
//if in any spot both pixels are not transparent, return true
int ys = bottomy-topy;
int xs = rightx-leftx;
for(int x=0; x<xs; x++) {
for(int y=0; y<ys; y++) {
int pxl = b1.getPixel(leftx+x, topy+y);
int pxl2 = b2.getPixel(leftx2+x, topy2+y);
if(!((pxl & 0xff000000) == 0x0) && !((pxl2 & 0xff000000) == 0x0)) {
return true;
}
}
}
return false;
}