What I want to do is to draw an image when the touch is down and when it is moving the image should also move along with the finger and when the touch is up the image should remain in that position. Again if i put the finger down another copy of that same image should again appear in the touch down co-ordinates and the process continues.
I saw many tutorials and tried everything. I tried to save the image of the canvas when the finger is down and when it moves clear the screen with the image saved during the finger down but that did not work. Then, I tried to clear the canvas with black color and paste the image saved during finger down and then draw the new image that I want to appear but still it did not work properly. It causes flickering and sometimes when lift up my finger and then again put it down the last image gets erased.
This is my code:
public class renderView extends SurfaceView implements Runnable{
boolean isRunning=false;
SurfaceHolder holder;
Thread t=null;
Bitmap img;
Paint paint;
Canvas canvas2,canvas3;
Bitmap cache,backup,cache2;
float x,y;
boolean up,down,move;
boolean dummy;
public renderView(Context context) {
super(context);
isRunning=true;
holder = getHolder();
img = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
paint= new Paint();
x=y=0;
cache = Bitmap.createBitmap(600, 800, Config.ARGB_8888);
cache2 = Bitmap.createBitmap(600, 800, Config.ARGB_8888);
backup = Bitmap.createBitmap(600,800, Bitmap.Config.ARGB_8888);
canvas2 = new Canvas(cache);
canvas3 =new Canvas(cache2);
move=down=false;
up=true;
dummy=false;
}
#Override
public void run() {
Log.d("status","run");
while(isRunning){
if(!holder.getSurface().isValid()){
continue;
}
Canvas canvas1 = holder.lockCanvas();
canvas1.drawColor(Color.BLUE);
canvas1.drawBitmap(cache2,0,0,paint);
canvas1.drawBitmap(cache,0,0,paint);
holder.unlockCanvasAndPost(canvas1);
}
}
public void pause(){
isRunning = false;
while(true){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
t=null;
}
public void resume(){
isRunning=true;
t=new Thread(this);
t.start();
}
public void down(float posX, float posY) {
/*x=posX;
y=posY;*/
canvas2.drawBitmap(img,posX,posY,paint);
canvas3.drawBitmap(img,posX,posY,paint);
up=false;
}
public void move(float posX, float posY) {
move=true;
dummy=true;
canvas2.drawColor(Color.BLACK);
canvas2.drawBitmap(cache2, 0, 0,paint);
canvas2.drawBitmap(img,posX,posY,paint);
}
public void up(float posX, float posY) {
up=true;
move=false;
canvas2.drawColor(Color.BLACK);
canvas2.drawBitmap(cache2,0,0,paint);
canvas2.drawBitmap(img,posX,posY,paint);
}
}
Related
I have problems with my drawing application. I need to catch the correct touch screen and draw them to the canvas. Changing the size of the brush is working properly. But when I change the transparency setting, the program does not work correctly. It imposes a new path on top of the previous one, and the transparency is lost. Screenshot. Where could I go wrong? I need your help. Thank you.
This is my SurfaceView code:
public class PainterView extends SurfaceView implements SurfaceHolder.Callback {
private PainterThread painterThread;
private BrushParameters brushParameters;
private Bitmap bitmap;
public PainterView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
brushParameters = new BrushParameters();
}
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
setWillNotDraw(false);
getThread().setRunning(true);
getThread().start();
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) {
if (bitmap == null) {
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
getThread().setBitmap(bitmap, true);
} else {
getThread().setBitmap(bitmap, false);
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
getThread().setRunning(false);
boolean retry = true;
while (retry) {
try {
getThread().join();
retry = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
painterThread = null;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
painterThread.startDraw(x, y);
break;
case MotionEvent.ACTION_MOVE:
painterThread.continueDraw(x, y);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
painterThread.finishDraw(x, y);
break;
}
return true;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
}
public BrushParameters getBrushParameters() {
return brushParameters;
}
public void setBrushColor(int color) {
brushParameters.setColor(color);
getThread().setBrushParameters(brushParameters);
}
public void setBrushSize(int size) {
brushParameters.setSize(size);
getThread().setBrushParameters(brushParameters);
}
public void setBrushAlpha(int alpha) {
brushParameters.setAlpha(alpha);
getThread().setBrushParameters(brushParameters);
}
public PainterThread getThread() {
if (painterThread == null) {
painterThread = new PainterThread(getHolder(), this);
}
return painterThread;
}
}
And my Thread class:
public class PainterThread extends Thread {
private SurfaceHolder surfaceHolder;
private PainterView painterView;
private boolean running = false;
private Paint paint;
private Path path;
private Bitmap mBitmap;
private Canvas mCanvas;
private float lastX, lastY;
private static float TOUCH_TOLERANCE = 4;
public PainterThread(SurfaceHolder surfaceHolder, PainterView painterView) {
this.surfaceHolder = surfaceHolder;
this.painterView = painterView;
path = new Path();
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
paint.setColor(Color.BLACK);
paint.setAlpha(255);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
}
public void setRunning(boolean running) {
this.running = running;
}
#Override
public void run() {
Canvas mCanvas;
while (running) {
mCanvas = null;
try {
mCanvas = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
if (mCanvas != null) {
mCanvas.drawBitmap(mBitmap, 0, 0, paint);
painterView.postInvalidate();
}
}
} finally {
if (mCanvas != null) {
surfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
public void setBitmap(Bitmap bitmap, boolean clear) {
mBitmap = bitmap;
if (clear) {
mBitmap.eraseColor(Color.WHITE);
}
mCanvas = new Canvas(mBitmap);
}
public void setBrushParameters(BrushParameters brushParameters) {
paint.setColor(brushParameters.getColor());
paint.setAlpha(brushParameters.getAlpha());
paint.setStrokeWidth(brushParameters.getSize());
}
public void startDraw(float x, float y) {
path.reset();
path.moveTo(x, y);
lastX = x;
lastY = y;
}
public void continueDraw(float x, float y) {
float dx = Math.abs(x - lastX);
float dy = Math.abs(y - lastY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
path.quadTo(lastX, lastY, (x + lastX) / 2, (y + lastY) / 2);
mCanvas.drawPath(path, paint);
lastX = x;
lastY = y;
}
}
public void finishDraw(float x, float y) {
path.moveTo(x, y);
mCanvas.drawPath(path, paint);
}
}
Thanks for help. I lost fiew days for find a cause of the problem...
I recommend getting rid of SurfaceView. You've got some confused-looking code (e.g. setBitmap() sets mCanvas, but that's getting overwritten in a loop by run()), and I think you're just making life harder on yourself.
SurfaceViews have two parts, the Surface and the View. The Surface is a separate layer that (by default) sits behind the View layer. The View part of the SurfaceView is normally just a transparent hole that lets you "see through" to the Surface layer behind.
In your case, you've overridden onDraw() in the View object, so you're actually drawing in the View. Over in your other thread you're drawing that same Bitmap onto the Surface. Even if your Bitmap has transparent pixels, you'll be seeing two identical Bitmaps layered on top of each other.
It looks like you're sharing a Bitmap and possibly a Canvas between two simultaneously-executing threads, which is a recipe for unhappiness.
If you get rid of the SurfaceView, and just use a custom View, I think everything will make more sense. The other approach is to get rid of onDraw() and the call to postInvalidate() and do everything on the Surface, but to take advantage of hardware-accelerated rendering it's better to use the custom View.
I'm working on application which gets an image and displays it into an image view. when the user tuches the screen I want the image covered by that circle (the circle is positioned were the screen is touched) to remain colored in that area after the screen is touched again.
I was able to draw the circle over the image but when the screen is touched again the image doesn't stay colored.
class DrawingView extends SurfaceView {
private final SurfaceHolder surfaceHolder;
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap bitmap1 = null;
private Bitmap bitmap2 = null;
private final Point node = new Point();
public DrawingView(Context context) {
super(context);
surfaceHolder = getHolder();
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN) {
if (surfaceHolder.getSurface().isValid()) {
node.x= (int)event.getX();
node.y = (int)event.getY();
Canvas canvas = surfaceHolder.lockCanvas();
bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.nic_eyes);
canvas.drawBitmap(bitmap1, 0, 0, null);
canvas.drawCircle(event.getX(), event.getY(), 50, paint);
bitmap2 = bitmap1.copy(bitmap1.getConfig(),true);
surfaceHolder.unlockCanvasAndPost(canvas);
photo = bitmap2;//photo is used for saving the modified image into galery
}
}
return false;
}
}
Any suggestions?
I'm working on a paint-like app, but I've run it to some troubles.
I know I have re-draw all the objects each frame, but this in turn makes performance slow further on when many objects are visible.
I've also noticed that clearing the background only once, then adding objects to be drawn to the surface when painting is active makes the screen flash almost to the point of inducing epilepsy.
So what is the best way to make a paint app 100% smooth no matter how many objects that are drawn?
Epilepsy-inducing code:
public void run()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (drawObjectsToAdd.size() == 0)
continue;
drawing = true;
Canvas c = holder.lockCanvas();
if (redrawBackground)
{
c.drawARGB(255, 255, 255, 255);
redrawBackground = false;
}
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
for (DrawObject draw : drawObjects)
{
draw.Draw(c, paint);
}
holder.unlockCanvasAndPost(c);
drawing = false;
}
}
this adds a new object once it's been added outside of the thread, but it makes the screen flash so much it gives me headaches - and I only redraw the background once.
Smooth at first but becomes laggy after a while, probably due to having to add hundreds and hundreds of objects in the end:
public void run()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (drawObjectsToAdd.size() > 0)
{
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
redraw = true;
}
if (clear)
{
drawObjectsToAdd.clear();
drawObjects.clear();
redraw = true;
clear = false;
}
if (!redraw)
continue;
drawing = true;
Canvas c = holder.lockCanvas();
c.drawARGB(255, 255, 255, 255);
for (DrawObject draw : drawObjects)
{
try
{
draw.Draw(c, paint);
}
catch (Exception ex) { }
}
holder.unlockCanvasAndPost(c);
drawing = false;
redraw = false;
}
}
I, at least for this app wanna store all the objects that are added so it doesn't matter how it's painted as long as it's smooth all the way. Preferably, add a circle - it will render a new Bitmap on to the surface, instead of having to redraw lots of objects each frame - instead store them but do not add objects already drawn.
UPDATE
Pseudo-Code of how I want it to be:
If no background is drawn
Draw background color
If new items have been added
Draw only new items to the background
Store new items in objects list
This way, we'll only draw the background once. When a new item is added, only draw that item to the existing surface. When the objects increases, looping through every item will reduce performance greatly and it will not be pleasant to work with.
UPDATE 2:
private void Draw()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (picture == null)
{
picture = new Picture();
Canvas c = picture.beginRecording(getWidth(), getHeight());
c.drawARGB(255, 255, 255, 255);
picture.endRecording();
}
if (drawObjectsToAdd.size() > 0)
{
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
Canvas c = picture.beginRecording(getWidth(), getHeight());
for (DrawObject draw : drawObjects)
{
draw.Draw(c, paint);
}
picture.endRecording();
drawObjects.clear();
}
Canvas c2 = holder.lockCanvas();
c2.drawPicture(picture);
holder.unlockCanvasAndPost(c2);
}
}
This last method from Update 2 makes it render all the lines like the "Snake game" when adding circles. Looks like a moving snake on a background, where some of it's circles disappear one frame and others don't the next. If I skip to redraw each frame, it will instead vary which of these circles that are visible.
what about that Picture implementation? increase MAX_DRAWERS to some reasonable value and see how it works
class SV extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private static final int MAX_DRAWERS = 8;
private boolean mRunning = true;
private List<Picture> mPictures = new LinkedList<Picture>();
private List<Drawer> mDrawers = new LinkedList<Drawer>();
private Paint mPaint;
public SV(Context context) {
super(context);
mPaint = new Paint();
mPaint.setColor(0xffffff00);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(this).start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
mRunning = false;
notify();
}
#Override
public synchronized boolean onTouchEvent(MotionEvent event) {
Drawer drawer = new Drawer(event.getX(), event.getY());
mDrawers.add(drawer);
if (mDrawers.size() > MAX_DRAWERS) {
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(getWidth(), getHeight());
mPaint.setAlpha(0xbb);
for (Drawer dr : mDrawers) {
dr.draw(canvas, mPaint);
}
picture.endRecording();
mPaint.setAlpha(0xff);
mPictures.add(picture);
mDrawers.clear();
Log.d(TAG, "onTouchEvent new Picture");
}
notify();
return false;
}
#Override
public synchronized void run() {
SurfaceHolder holder = getHolder();
while (mRunning) {
// Log.d(TAG, "run wait...");
try {
wait();
} catch (InterruptedException e) {
}
if (mRunning) {
// Log.d(TAG, "run woke up");
Canvas canvas = holder.lockCanvas();
canvas.drawColor(0xff0000ff);
for (Picture picture : mPictures) {
picture.draw(canvas);
}
for (Drawer drawer : mDrawers) {
drawer.draw(canvas, mPaint);
}
holder.unlockCanvasAndPost(canvas);
}
}
Log.d(TAG, "run bye bye");
}
class Drawer {
private float x;
private float y;
public Drawer(float x, float y) {
this.x = x;
this.y = y;
}
public void draw(Canvas canvas, Paint paint) {
canvas.drawCircle(x, y, 8, paint);
}
}
}
You generate your list of objects each and every frame, just to discard it after drawing. Why? Generate your list of draw objects once, then draw them.
That's all you need for your drawing thread...
public void run() {
while (running)
{
Canvas c = holder.lockCanvas();
c.drawARGB(255, 255, 255, 255);
for (DrawObject draw : drawObjects)
{
try
{
draw.Draw(c, paint);
}
catch (Exception ex) { }
}
holder.unlockCanvasAndPost(c);
}
}
I need to write finger paint application. I need some computations about the drawn curve, so I think it's a good idea to use SurfaceView but not View.
I've written code for drawing. I collect the points into Path and draw it. On ACTION_UP this Path is reseting. And in the drawing loop I draw only the last Path.
But for some reason previous paths are redrawn too. This cause annoying blinking, which I don't want. I've changing line color on every draw cycle and I see, that previous paths change their colors, despite of there is no code to collect and redraw them.
Why so? Is there any way to make SurfaceView not to repeat all previous drawings?
Here is my code:
public class OtherDrawView extends SurfaceView implements SurfaceHolder.Callback {
private DrawingThread mThread;
private Path mPath;
private static final Object lock = new Object();
public OtherDrawView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder surfaceHolder = getHolder();
surfaceHolder.addCallback(this);
mThread = new DrawingThread(surfaceHolder);
mPath = new Path();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPath.moveTo(event.getX(), event.getY());
continueDrawingThread();
return true;
case MotionEvent.ACTION_MOVE:
mPath.lineTo(event.getX(), event.getY());
continueDrawingThread();
return true;
case MotionEvent.ACTION_UP:
mPath.reset();
return true;
}
return false;
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
continueDrawingThread();
while (retry) {
try {
mThread.join();
retry = false;
}
catch (InterruptedException ignored) { }
}
}
private void continueDrawingThread() {
synchronized (lock) {
try {
lock.notifyAll();
} catch (Exception ignored) {}
}
}
private class DrawingThread extends Thread {
private boolean mRunning;
private Paint mPaint;
private Random mRandom = new Random(System.currentTimeMillis());
private final SurfaceHolder mSurfaceHolder;
public DrawingThread(SurfaceHolder surfaceHolder) {
mSurfaceHolder = surfaceHolder;
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
mPaint.setStrokeWidth(5);
mPaint.setStyle(Paint.Style.STROKE);
}
public void setRunning(boolean running) {
mRunning = running;
}
private void drawPath() {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
assert canvas != null;
mPaint.setColor(Color.rgb(mRandom.nextInt(255), mRandom.nextInt(255), mRandom.nextInt(255)));
canvas.drawPath(mPath, mPaint);
}
}
catch (Exception ignored) { }
finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
#Override
public void run() {
while (mRunning) {
drawPath();
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException ignored) {
}
}
}
}
}
}
I still can't understand why SurfaceView behaves like this, but I've found a workaround: draw not directly on canvas but on some bitmap and then output this bitmap on main canvas. Fully described in this article: http://android-coding.blogspot.ru/2012/01/flickering-problems-due-to-double.html
From the documentation for unlockCanvasAndPost(Canvas):
"After this call, the surface's current pixels will be shown on the screen, but its content is lost, in particular there is no guarantee that the content of the Surface will remain unchanged when lockCanvas() is called again."
So, when drawing to a surface view, you always have to clear the background (normally by re-drawing a background image) before drawing anything else on top, eg. the path in your case.
I want to zoom in the canvas board partially as the letter tray button will be fixed and the board will be zoom,
The partially coding is as mention below and the screen is fully draw in canvas which can be viewed as mention at the bottom.. please help and thanks for you concern.
public class BoardView extends SurfaceView implements SurfaceHolder.Callback
{
class DrawingThread extends Thread implements OnTouchListener {
public DrawingThread(SurfaceHolder holder, Handler handler) {
mSurfaceHolder = holder;
public void setRunning(boolean b) {
mRun = b;
}
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
// System.gc();
// c.scale(canvasScaleX, canvasScaleY);
// c.save();
// c.translate(canvasTranslateX, canvasTranslateY);
doDraw(c);
}
updateGame();
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
private void doDraw(Canvas canvas) {
if (ge == null)
return;
Rect bRect = new Rect(0, 0, dims.getTotalWidth(),
dims.getScoreHeight() + dims.getBoardheight());
Drawable drawable = getResources().getDrawable(R.drawable.board);
drawable.setBounds(bRect);
drawable.draw(canvas);
Rect tRect = new Rect(0, dims.getScoreHeight()
+ dims.getBoardheight(), dims.getTotalWidth(),
dims.getTotalHeight());
canvas.drawRect(tRect, fillTrayPaint);
int topHeight = dims.getScoreHeight() + dims.getBoardheight();
int bottom = (dims.getTotalHeight() + 5)
- (dims.getTotalWidth() / Tray.TRAY_SIZE);
Rect rect = new Rect(0, topHeight, dims.getTotalWidth(), bottom - 7);
Drawable drawableTray = getResources()
.getDrawable(R.drawable.strip);
drawableTray.setBounds(rect);
drawableTray.draw(canvas);
drawTray(canvas);
drawBoard(canvas);
// drawScore(canvas);
drawMovingTile(canvas);
}
public BoardView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// endTurn=(ImageButton)findViewById(R.id.endturn_button_horizontal);
thread = new DrawingThread(holder, handler);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
final float scale1 = getResources().getDisplayMetrics().density;
defaultFontSize = (int) (MIN_FONT_DIPS * scale1 + 0.5f);
thread.setDefaultFontSize(defaultFontSize);
dimensions = calculateDimensions(getWidth(), getHeight());
thread.setDimensions(dimensions);
thread.setRunning(true);
// if (thread != null && !thread.isAlive())
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
We can simply do it by canvas.clipRect(rectangle), at this point what we are suppose to do is save the canvas canvas.save() and after canvas.clipRect(rectangle) call canvas.restore().
Consider trying something like this:
Create a bitmap from the canvas you wish to zoom into.
When rendering that bitmap to the screen, you can change the scr rect
(which will "zoom" into the bitmap).
I hope this helps.
I think you need to render your game board into offscreen bitmap and then draw this bitmap at canvas using Matrix, so you will be able to change its position and scale.
I believe the easiest way to do this would be to use Canvas#scale(float scale, int pivotx, int pivoty) method. It essentially grows the entire canvas uniformally which really is what zooming is from a 2D perspective.
canvas.scale(2.0f, getWidth()/2, getHeight()/2) will scale the image by 2 from the center. canvas.scale(1.0f, getWidth()/2, getHeight()/2) will shrink it back.
I have never tried this in this context, so if you decide to try it report back the results please! I'd like to know for future reference.