On my app I have an ImageView which I turned into a Bitmap for editing. I need to detect which pixels on the ImageView were touched by the user. In addition, if the user draws a line with his finger, I need to know all the pixels that were touched in order to change them. How do I detect which pixels were touched?
-
Could you elaborate a little bit more about what you mean by "need to detect which pixels on the ImageView were touched by the user"? Sounds like you want to create some sort of image editing tool in which the user is allowed to "paint" a path with his finger over a bitmap. If that is the case have you tried to just register touch and drag listeners for your view and deal with the event coordinates? – ulix Dec 24 '14 at 07:35
-
I'm a beginner programmer so I really didn't know what to start with. In my app I display one pic and when the user touches the pic, those pixels turn into the matching pixels on another pic. That creates a blending effect. I know how to blend the pics but don't know how to make listeners for touch. – Jonah G Dec 24 '14 at 07:39
2 Answers
Ok Jonah, here are some directions for you.
I guess you want that blending effect to react quickly to user input so first thing you'd better go for a custom SurfaceView instead of a ImageView because it is more suitable for drawing high frame rate animations required in 2D action games and animations. I strongly recommend you to read this guide; giving special attention to the part about the use of SurfaceView
, before going any further. You will basically need to create a class that extends SurfaceView
and implements SurfaceHolder.Callback
. This view will then be responsible to listen for user touch events and to render the frames to animate the blending effect.
Take a look at following code as a reference:
public class MainView extends SurfaceView implements SurfaceHolder.Callback {
public MainView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this); // Register this view as a surface change listener
setFocusable(true); // make sure we get key events
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen...
case MotionEvent.ACTION_CANCEL:
// User dragged his finger out of the view bounds...
case MotionEvent.ACTION_UP:
// User raised his finger...
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
// Update the blending effect bitmap here and trigger a frame redraw,
// if you don't already have an animation thread to do it for you.
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
// You need to wait for this call back before attempting to draw
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
}
}
Then use it on the layout of your "drawing" activity like this:
<com.xxx.yyy.MainView
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
To draw to this surface you need the following code:
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
if (c != null)
c.drawBitmap(blendingImage, 0, 0, null); // Render blending effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
A fully functional example would be impractical to put in an answer so I recommend you to download the Lunar Lander sample game from Google for a full working example. Note however, that you won't need a game animation thread (although it won't hurt having one), like the one coded in the Lunar Lander sample, if all you need is the blending effect. The purpose of that thread is to create a game loop in which game frames are constantly generated to animate objects that may or may not depend on user input. In your case, all you need is to trigger a frame redraw after processing each touch event.
EDIT: The following code are fixes to get the code you've provided in the comments, working.
Here are the changes to MainActivity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// put pics from drawables to Bitmaps
Resources res = getResources();
BitmapDrawable bd1 = (BitmapDrawable) res.getDrawable(R.drawable.pic1);
// FIX: This block makes `operation` a mutable bitmap version of the loaded resource
// This is required because immutable bitmaps can't be changed
Bitmap tmp = bd1.getBitmap();
operation = Bitmap.createBitmap(tmp.getWidth(), tmp.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(operation);
Paint paint = new Paint();
c.drawBitmap(tmp, 0f, 0f, paint);
BitmapDrawable bd2 = (BitmapDrawable) res.getDrawable(R.drawable.pic2);
bmp = bd2.getBitmap();
myView = new MainView(this, operation, bmp);
FrameLayout preview = (FrameLayout) findViewById(R.id.preview);
preview.addView(myView);
}
...
Here are the changes to the MainView class:
public class MainView extends SurfaceView implements Callback {
private SurfaceHolder holder;
private Bitmap operation;
private Bitmap bmp2;
private boolean surfaceReady;
// took out AttributeSet attrs
public MainView(Context context, Bitmap operation, Bitmap bmp2) {
super(context);
this.operation = operation;
this.bmp2 = bmp2;
holder = getHolder(); // Fix: proper reference the instance variable
holder.addCallback(this); // Register this view as a surface change
// listener
setFocusable(true); // make sure we get key events
}
// Added so the blending operation is made in one place so it can be more easily upgraded
private void blend(int x, int y) {
if (x >= 0 && y >= 0 && x < bmp2.getWidth() && x < operation.getWidth() && y < bmp2.getHeight() && y < operation.getHeight())
operation.setPixel(x, y, bmp2.getPixel(x, y));
}
// Added so the drawing is now made in one place
private void drawOverlays() {
Canvas c = null;
try {
c = holder.lockCanvas(null);
synchronized (holder) {
if (c != null)
c.drawBitmap(operation, 0, 0, null); // Render blending
// effect
}
} catch (Exception e) {
Log.e("SurfaceView", "Error drawing frame", e);
} finally {
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an
// inconsistent state
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
if (!surfaceReady) // No attempt to blend or draw while surface isn't ready
return false;
// Check if the touch pointer is the one you want
if (event.getPointerId(event.getActionIndex()) == 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// User touched screen. Falls through ACTION_MOVE once there is no break
case MotionEvent.ACTION_MOVE:
// User dragged his finger...
blend((int) event.getX(), (int) event.getY());
}
// Update the blending effect bitmap here and trigger a frame
// redraw,
// if you don't already have an animation thread to do it for you.
drawOverlays();
return true;
}
return false;
}
/*
* Callback invoked when the Surface has been created and is ready to be
* used.
*/
public void surfaceCreated(SurfaceHolder holder) {
surfaceReady = true;
drawOverlays();
}
/*
* Callback invoked when the Surface has been destroyed and must no longer
* be touched. WARNING: after this method returns, the Surface/Canvas must
* never be touched again!
*/
public void surfaceDestroyed(SurfaceHolder holder) {
// You shouldn't draw to this surface after this method has been called
surfaceReady = false;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
}
This code works for me. I just hope I didn't forget anything =:)
Let me know if you still have trouble, ok?

- 999
- 9
- 13
-
Thanks a lot! But how do I set the updated bitmap on the Surface View? And what do I put in OnActionUp? – Jonah G Dec 28 '14 at 16:08
-
You don't "set" a bitmap to it as if it were an ImageView. You grab it's Canvas object and draw the bitmap on it (like I show in the last block of code). Now, about the touch events, `MotionEvent.ACTION_UP` is usually important to tell you that the "gesture" started by `MotionEvent.ACTION_DOWN` has ended. It may or may not be important for you. Let's say, for example, you have a game in which the user has to drag pieces in a board. `MotionEvent.ACTION_UP` is the event that tells you the user just finished the dragging so it's time to place the piece in the nearest valid board position. – ulix Dec 28 '14 at 21:18
-
I when through all the steps, but the app still has errors. Here are links to my MainActivity: https://docs.google.com/document/d/16KfcnpjVPUbHeM3VlfBX55FFbPFHJAPDeRqJl3r8Tyo/pub and MainView https://docs.google.com/document/d/1x15Mn-dl2BNx__EORl94e4XEwwLfRsciG7GUQP331YU/pub What am I doing wrong? – Jonah G Jan 01 '15 at 14:06
-
It's hard to tell without looking at the full stack trace of the exception thrown. Anyway, I took a look at the links above an my guess is that you are lacking the proper constructor in your MainView class. If you have a layout file that references your custom SurfaceView (MainView in this case) the layout manager will look for the following constructor `public MainView(Context context, AttributeSet attrs) {...}`. If it is not present, you will get a NoSuchMethodException. If my guess wasn't right, please add links to the full stack trace of the exception and to the layout files. – ulix Jan 02 '15 at 00:20
-
I updated MainView and Main activity, and added a FrameLayout to MainActivity to hold the MainView, but there are still errors. MainView: https://docs.google.com/document/d/1JvyrVBuF6xwuWpj2kJG4M7vCnGJm98nvSA_BPy-AwcQ/pub MainActivity: https://docs.google.com/document/d/1s4sVXIfv47U28I_C5bZpQeAzJwN8axce62wwhBJn21o/pub Layout: https://docs.google.com/document/d/1rKq8nh7jERAy2ZuGak26uJ0rZ12RI7vMNUOpxKmazeU/pub – Jonah G Jan 03 '15 at 17:58
-
Could you elaborate more on these errors? Are they exceptions or just the program not behaving as expected? In case you are getting exceptions, please make the full stack trace available to me. If you don't know what a java stack trace is, please take a look at this: http://stackoverflow.com/questions/3988788/what-is-a-stack-trace-and-how-can-i-use-it-to-debug-my-application-errors – ulix Jan 03 '15 at 20:21
-
Is this what you mean? https://docs.google.com/document/d/1Jy6wnfuibAK0YFM0JARmoG7VGvC8jCemikK4MALQdPw/pub – Jonah G Jan 03 '15 at 20:50
-
Hey Jonah, I've just edited my answer to include a few fixes on the code you provided above. The changes come after the "EDIT:..." in bold. Let me know it it works for you – ulix Jan 04 '15 at 06:46
-
The code still doesn't run. Can you please send a link to your full code so I can compare it to mine. Thanks a lot! – Jonah G Jan 04 '15 at 13:46
-
Jonah, here is the full Eclipse project I've created for this case: https://drive.google.com/file/d/0B-xnSRS5FRKZT3FvblFqcVctU1k/view?usp=sharing. As you said you couldn't run I've made a few more changes to reduce memory usage and increase compatibility with older devices. Comments are inside the code. I've tested the code on both Android versions 2.3 and 4.2. Let me know. – ulix Jan 05 '15 at 06:20
-
thanks a lot but for some reason I can't open each file, and when I try to download the whole project, it comes out in a weird format. – Jonah G Jan 05 '15 at 13:22
-
It seems you don't have 7zip installed. Here is the standard zip version: https://drive.google.com/file/d/0B-xnSRS5FRKZRk4xWnlWazdWRU0/view?usp=sharing. Try downloading it to your hard drive prior to opening, ok? – ulix Jan 05 '15 at 17:59
-
Thanks a lot really appreciate it! I was able to download and open the app and the code. In the app, when I draw, I can't draw a straight line, it only colors pixels with a certain distance from each other. – Jonah G Jan 05 '15 at 21:15
-
That happens because the program is creating the effect only at the coordinates where the touch events were generated. Unless the user is moving his finger very slowly, you can't expect a new ACTION_MOVE event to be generated for every new adjacent pixel touched by the user because there is a limit on the number of events that can be generated per second. Connecting those coordinates with the blending effect, using any "painting brush" that is bigger than a pixel, will require a lot more effort on the code side; so I recommend you to open a new question for this problem alone. – ulix Jan 06 '15 at 00:32
-
The app crashes when I touch the part of the screen that is not part of the pictures, why is that? And could it be fixed? – Jonah G Jan 06 '15 at 12:48
-
I've added a basic check to the MainView class to prevent getPixel/setPixel methods from being called outside the bitmap bounds. Here is the link to get it: https://drive.google.com/file/d/0B-xnSRS5FRKZTy0zbTBLa0lobWc/view?usp=sharing. Please understand that, for didactic and practical reasons, I did not include all the details that should be addressed in a full featured application. My intention instead is to present you with the conceptual solution so you can evolve it into your final polished version... – ulix Jan 06 '15 at 19:22
-
...In fact, based on your past requests (specially the one connecting the coordinates), I expect you to completely replace my very conceptual implementation of the MainView.blend method with one that won't even be based on getPixel/setPixel methods and therefore wont even use the checks I've just added. :) – ulix Jan 06 '15 at 19:23
-
-
I'll be glad to help you with that issue too (about connecting coordinates with the blending effect), in my spare time. Let me know if you happen to create another question for that. Good luck! – ulix Jan 06 '15 at 19:38
-
I know it has been a while but I also need to be able to restore parts of the old bitmap in the same way I erased it. How would that be done? – Jonah G Mar 15 '15 at 12:58
-
Hi Jonah! Sorry I took so long to reply. Its been a while since I last checked my account. Anyway, all you need to do in your MainView is to create a `backup` bitmap from `operation` bitmap and two distinct modes of operation: "drawing" and "erasing" meant to tell the app to either use `bmp2` or `backup`, respectively, as the source of pixels to draw. The new version of MainView (https://drive.google.com/file/d/0B-xnSRS5FRKZdlp6NVFyWWtBZWs/view?usp=sharing) will provide you with a simple implementation. Click the top left corner of the image to switch between modes. – ulix Apr 09 '15 at 22:00
-
Thnx a lot! By the way I already found a solution myself:) Although I'm still having difficulties with turning the drawing into circles instead of squares like the pixels are in order to improve how precise the user can be with the drawing – Jonah G Apr 12 '15 at 21:40
-
Ok. Anyway, to use more generic shapes as painting brushes I suggest you to replace the use of Bitmap.getPixel(s)/setPixel(s) methods, which I only used above for the sake of simplicity, by means of creating a BitmapShader with a custom bitmap (as a brush shape) or even by creating a "lower level" solution using alpha compositing operators supported by the Paint class (through Paint.setXfermode with a proper PorterDuffXfermode). I believe the last being the best option in case you need to dynamically create brush shapes to interpolate between points and avoid the gaps you mentioned earlier. – ulix Apr 13 '15 at 18:12
-
I really appreciate all your help but I'm completely stuck now. I was able to use PorterDuff mode and it works great. The only problem I'm having is with saving the final picture which is a blend of the 2 bitmaps. I draw one bitmap on top of the other and make the top transparent in the touched areas. How do I put the matching pixels of the buttom pic in the transparent areas? – Jonah G Jun 04 '15 at 14:57
So the answer to this is that you're going to have to be a little clever, but it really shouldn't be so bad. Instead of posting all the code to do what you want to do, I'll give you a link here and an explanation.
So by managing the touch events of an application, you can figure out the average coordinates of a touch event. Using that information you can determine the center of all the pixels touched and continue to track that as a line is drawn with the finger. To track a straight line use the
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_UP:
clauses to determine the start and end of the line. If you want to track a line that is not straight and drawn, you're going to need a little more tracking using
case MotionEvent.ACTION_MOVE:
and that should get you fairly started. You may need a little bit of logic to deal with the fact that you will be drawing a very thin line and I suspect that's not quite what you're going for. Maybe it is though. Either way this should be a good place to get started.
EDIT
In regards to your first comment, here is a link you can use for an example. I have to make a small disclaimer though. To get all of the pieces to work together correctly, it may not seem that simple at first. I assure you that this is one of the simplest examples and breaks the tutorial into sections.
For what you would like to do, I think you'll want to pay particular attention to section 2 (no to be confused with step 2):
2. Facilitate Drawing
I suggest this because it shows different ways to use information form the TouchEvent. The things included in section 1 will explain a little bit about the environment to setup displaying a TouchEvent's captured data whereas section 3 is mostly about aesthetics. This may not directly answer your question, but I suspect it will get you where you need to be.
Happy coding! Leave a comment if you have any questions.

- 5,404
- 3
- 27
- 39
-
Can you please send a link of a simple working code so I'll have someplace to start from, since I'm pretty lost. Thanks a lot! – Jonah G Dec 24 '14 at 13:17