My situation is rather simple. I have a mobile and wear app. My mobile app is a "drawing" app. It connects to a piece of hardware that has a magnetic pencil. Drawing on the hardware sends three functions to my app - touchStart, touchMove, touchStop. I can draw the path on my mobile app at a reasonable rate. But, if I send these 3 touch detections to my watch via SendMessage, the image is drawn slowly. I'm trying to speed it up so the drawing is in a real time, like the mobile device.
Here's the code to the Paint class of my watch app:
class SimpleDrawingView extends View {
// setup initial color
private final int paintColor = Color.WHITE;
// defines paint and canvas
private Paint drawPaint;
// Store circles to draw each time the user touches down
public int xwidth, xheight;
private Path bPath = new Path();
private Path path = new Path();
public SimpleDrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusable(true);
setFocusableInTouchMode(true);
setupPaint();
}
public Path redraw() {
Matrix m = new Matrix();
RectF innerBounds = new RectF();
bPath = new Path(path);
bPath.computeBounds(innerBounds, true);
int width = getWidth();
int height = getHeight();
RectF intoBounds = new RectF(0, 0, width, height);
m.setRectToRect(innerBounds, intoBounds, Matrix.ScaleToFit.CENTER);
bPath.transform(m);
return bPath;
}
// Draw each circle onto the view
#Override
protected void onDraw(Canvas canvas) {
Log.d("MEx", " drawing ");
redraw();
canvas.drawPath(path, drawPaint);
}
public void actionDown(Point a)
{
path.moveTo(a.x, a.y);
invalidate();
}
public void clear()
{
path.reset();
invalidate();
}
public void actionEnd(Point a)
{
// path.lineTo(a.x, a.y);
}
public void actionMove(Point a)
{
path.lineTo(a.x, a.y);
invalidate();
}
// Append new circle each time user presses on screen
private void setupPaint() {
// Setup paint with color and stroke styles
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(23);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
}
}
Here's my message receiver:
public class Receiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
for(String s : intent.getExtras().keySet()) {
String first = intent.getExtras().get(s).toString();
String label, x, y;
if (first.contains("charge+")) {
textLabel.setText(first.split("7")[1]);
} else if (first.contains("battery+")) {
String color = first.substring(8);
Log.d("TESTTWO", color + " <- ");
switch(color)
{
case "RED":
dotView.setImageResource(R.drawable.red);
textLabel.setText("");
break;
case "GREEN":
dotView.setImageResource(R.drawable.green);
textLabel.setTextColor(Color.GREEN);
break;
case "YELLOW":
textLabel.setTextColor(Color.YELLOW);
dotView.setImageResource(R.drawable.yellow);
break;
}
} else {
label = first.substring(0, 1);
String[] toSplit;
toSplit = first.substring(2).split(",");
Point point = new Point(Integer.parseInt(toSplit[0]), Integer.parseInt(toSplit[1]));
switch (label) {
case "S":
imageView.actionDown(point);
break;
case "M":
imageView.actionMove(point);
break;
case "E":
imageView.actionEnd(point);
break;
}
}
}
}
}
Points are sent to my watch in the format of either
S-x,y
M-x,y
E-x,y
S = start, M = move, E = end.
If someone can help me optimize this functionality it would be amazing!
I tried send the data as an asset as well, that's even slower.
Related
I want to delete from my PinView all pins when changing background image.
I add the pins by inserting them into a coordinate ArrayList and pin image and everything works correctly.
How can I do?
You mentioned that you already have the Points in an arrayList. Use the setPin function from below( it also needs a "name" for the point, you can simply add the index of the point from arrayList as name using String.valueOf(//you index); as the name of pin). Now when you want to remove the points, just run a simple for loop for the arrayList, and remove all the elements. Give it a try.
P.S. The code also contains few other functions, feel free to delete them if you don't need it.
public class PinView extends SubsamplingScaleImageView {
private ArrayList<PointF> sPin = new ArrayList<>();
private ArrayList<String> pinNames = new ArrayList<>();
private Bitmap pin;
private final float scale=1.5f;
public PinView(Context context) {
this(context, null);
}
public PinView(Context context, AttributeSet attr) {
super(context, attr);
initialise();
}
public boolean setPin(PointF sPin, String name) {
if (pinNames.contains(name)){
return false;
} else {
this.sPin.add(sPin);
pinNames.add(name);
initialise();
invalidate();
return true;
}
}
public PointF getPin(String name) {
return sPin.get(pinNames.indexOf(name));
}
public boolean removePin(String name){
if (pinNames.contains(name)){
sPin.remove(pinNames.indexOf(name));
pinNames.remove(name);
System.out.println("Removed pin named : "+name);
initialise();
invalidate();
return true;
} else {
System.out.println("No such pins : "+name);
initialise();
invalidate();
return false;
}
}
public ArrayList<String> getPinNames(){
return pinNames;
}
private void initialise() {
float density = getResources().getDisplayMetrics().densityDpi;
pin = BitmapFactory.decodeResource(this.getResources(), R.drawable.squarered);
float w = (density/420f) * pin.getWidth();
float h = (density/420f) * pin.getHeight();
pin = Bitmap.createScaledBitmap(pin, (int)w, (int)h, true);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Don't draw pin before image is ready so it doesn't move around during setup.
if (!isReady()) {
return;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
for (PointF point : sPin){
if (point != null && pin != null) {
PointF vPin = sourceToViewCoord(point);
float vX = vPin.x - (pin.getWidth()/2);
float vY = vPin.y - pin.getHeight();
canvas.drawBitmap(pin, vX, vY, paint);
}
}
}
public void doAnimationWithTime(PointF target)
{
//System.out.println("Width : " + this.getSWidth() + " height : " + this.getSHeight());
SubsamplingScaleImageView.AnimationBuilder animationBuilder = this.animateScaleAndCenter(scale, target);
animationBuilder.withDuration(700).withEasing(SubsamplingScaleImageView.EASE_OUT_QUAD).withInterruptible(true).start();
}
public boolean isPinVisibleOnImage(String name)
{
boolean result = false;
for(int i=0;i<pinNames.size();i++)
{
if(pinNames.get(i).equals(name.trim()))
{
result = true;
break;
}
}
return result;
}
}
I am working on a drawing app for android users. I want to implement undo redo functionality on my app. I am referring questions posted on stackoverflow to solve my problem but I could not be able to find the right solution for my code. Below I am posting my code, please help me to get rid of this issue.Any help would be highly appreciated.
Thank you in advance.
public class DrawingView extends View {
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
this.context = context;
setupDrawing();
}
public void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
canvasPaint = new Paint(Paint.DITHER_FLAG);
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
paths.add(drawPath);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
//view given size
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
#Override
protected void onDraw(Canvas canvas) {
//draw view
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
for(Path p : paths){
canvas.drawPath(p, drawPaint);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// undonePaths.clear();
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
// drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath = new Path();
paths.add(drawPath);
break;
default:
return false;
}
invalidate();
return true;
}
public void onClickUndo(){
if(paths.size()>0){
undonePaths.add(paths.remove(paths.size() - 1));
Toast.makeText(getContext(), "Undo is working", Toast.LENGTH_LONG).show();
invalidate();
}
else{
Toast.makeText(getContext(), "Undo is not working", Toast.LENGTH_LONG).show();
}
}
public void onClickRedo(){
if(undonePaths.size()>0){
paths.add(undonePaths.remove(undonePaths.size() - 1));
Toast.makeText(getContext(), "Redo is working", Toast.LENGTH_LONG).show();
invalidate();
}
else{
Toast.makeText(getContext(), "Redo is not working", Toast.LENGTH_LONG).show();
}
}
}
Just a few minor changes.
There's an extra Path in your lists that will cause an Undo/Redo step to essentially do nothing when the rest is corrected. So in setupDrawing() remove:
paths.add(drawPath);
In the onDraw() method, remove:
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
This drawBitmap() call was redrawing the member Bitmap that is drawn to on every ACTION_UP, making it appear as though your Path lists weren't being updated.
Then, in the onTouchEvent() method, in MotionEvent.ACTION_DOWN un-comment:
undonePaths.clear();
and change the MotionEvent.ACTION_UP case to:
case MotionEvent.ACTION_UP:
drawCanvas.drawPath(drawPath, drawPaint);
paths.add(drawPath);
drawPath = new Path();
break;
The reset() call was clearing the Path, which you don't want. Instead, here we add the recently completed Path to the list, and then create a new one to continue.
NB: The way this is currently working, the Undo/Redo functionality will not affect the canvasBitmap. As it is unclear what this Bitmap is for, I left it as is. If you want this Bitmap to mirror the View's, you can simply move the drawCanvas.drawPath() calls to onDraw(), in parallel to the canvas.drawPath() calls there.
var undoRedo = new Array();
var unStep = -1;
function historySave() {
unStep++;
while (undoRedo.length > 20) {
undoRedo.shift();
unStep--;
}
if (unStep !== 0 && unStep < undoRedo.length) {
undoRedo.length = unStep;
unStep++;
} else {
undoRedo.length = unStep;
}
undoRedo.push(document.getElementById('drawingCanvas').toDataURL());
}
function Undo() {
if (unStep > -1) {
unStep--;
var canvasPic = new Image();
canvasPic.src = undoRedo[unStep];
drawingContext.clearRect(0,0,drawingCanvas.width,drawingCanvas.height);
canvasPic.onload = function () {
drawingContext.drawImage(canvasPic, 0, 0);
}
}
}
function Redo() {
if (unStep < undoRedo.length - 1) {
unStep++;
var canvasPic = new Image();
canvasPic.src = undoRedo[unStep];
canvasPic.onload = function () {
drawingContext.drawImage(canvasPic, 0, 0);
}
}
}
I have drawn a polygonal path based on user finger movement using the code given below.
paint = new Paint();
strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 255, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
wallpath = new Path();
and on onDraw i am using this code to draw the figure
wallpath.lineTo(endPoint.x, endPoint.y);
canvas.drawPath(wallpath, paint);
The above code is working fine. But on double tap i want to change the fill color. For that I am using this code
paint.setARGB(125, 225, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
invalidate();
But it doesnt seems to be working. How can I do this properly?
Complete code for reference
public class HotspotView extends View
{
private Paint paint,strokePaint;
private PointF startPoint, endPoint;
private boolean isDrawing,isFinished,isAnimating,isRecording,isRedrawing;
private Path wallpath;
private ArrayList<PointF> points;
private RectF rectF;
private CurlView curlView;
public int imageWidth;
private String fileName;
private AudioRecorder audioRecorder;
private GestureDetector gestureDetector;
public static int LONG_PRESS_TIME = 500; // Time in miliseconds
private AudioPlayer player;
final Handler _handler = new Handler();
Runnable _longPressed = new Runnable() {
public void run() {
Log.i("info","LongPress");
isRecording = true;
isDrawing = false;
isRedrawing = true;
///////////////////******************///////////////////////
//paint = new Paint();
//strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 225, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
invalidate();
//////////////////*****************////////////////////////
audioRecorder = new AudioRecorder(fileName);
setFileName();
audioRecorder.startRecording(fileName);
}
};
public HotspotView(Context context)
{
super(context);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
public HotspotView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
public HotspotView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
gestureDetector = new GestureDetector(context, new GestureListener());
}
private void init()
{
isRecording = false;
isRedrawing = false;
paint = new Paint();
strokePaint = new Paint();
//paint.setColor(Color.RED);
paint.setARGB(125, 255, 0, 0);
paint.setStyle(Style.FILL);
paint.setPathEffect(new DashPathEffect(new float[] {10,20}, 0));
paint.setStrokeWidth(5);
paint.setAntiAlias(true);
wallpath = new Path();
points = new ArrayList<PointF>();
rectF = new RectF();
rectF.set(-1.7883435f, 1.0f, 1.7883435f, -1.0f);
}
#Override
protected void onDraw(Canvas canvas)
{
if(isAnimating)
{
PointF point = this.translate(points.get(0));
if(wallpath == null)
{
wallpath = new Path();
}
wallpath.moveTo(point.x, point.y);
isDrawing = false;
isFinished = false;
for(int i=1;i<points.size();i++)
{
if(isRedrawing)
{
point = points.get(i);
}
else
{
point = this.translate(points.get(i));
}
wallpath.lineTo(point.x, point.y);
//Log.d("Points", "X = "+point.x);
//Log.d("Points", "Y = "+point.y);
canvas.drawPath(wallpath, paint);
}
if(isRedrawing)
{
point = points.get(0);
}
else
{
point = this.translate(points.get(0));
}
wallpath.lineTo(point.x, point.y);
canvas.drawPath(wallpath, paint);
isFinished = true;
}
else if(isDrawing)
{
//canvas.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, paint);
wallpath.lineTo(endPoint.x, endPoint.y);
canvas.drawPath(wallpath, paint);
}
if(isFinished)
{
//wallpath.lineTo(endPoint.x, endPoint.y);
//canvas.drawPath(wallpath, strokePaint);
wallpath.close();
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
boolean result = gestureDetector.onTouchEvent(event);//return the double tap events
if(!isAnimating)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
isDrawing = true;
//wallpath.reset();
_handler.postDelayed(_longPressed, LONG_PRESS_TIME);
startPoint = new PointF(event.getX(), event.getY());
endPoint = new PointF();
endPoint.x = event.getX();
endPoint.y = event.getY();
wallpath.moveTo(endPoint.x,endPoint.y);
points.add(startPoint);
//invalidate();
break;
case MotionEvent.ACTION_MOVE:
PointF point = new PointF(event.getX(),event.getY());
endPoint.x = event.getX();
endPoint.y = event.getY();
double distance = Math.sqrt(Math.pow((endPoint.x - startPoint.x), 2)+ Math.pow(endPoint.y - startPoint.y,2));
if(distance >2)
{
_handler.removeCallbacks(_longPressed);
if(!isRecording)
{
if(isDrawing)
{
Log.d("Point", "X = "+(event.getX() - this.getLeft()));
Log.d("Point", "Y = "+(event.getY() - this.getTop()));
points.add(point);
invalidate();
}
}
}
break;
case MotionEvent.ACTION_UP:
_handler.removeCallbacks(_longPressed);
if(isRecording)
{
audioRecorder.stopRecording();
isRecording = false;
}
if(isDrawing)
{
endPoint.x = startPoint.x;//event.getX();
endPoint.y = startPoint.y;//event.getY();
strokePaint.setARGB(255, 255, 0, 0);
strokePaint.setStyle(Style.STROKE);
strokePaint.setPathEffect(new DashPathEffect(new float[] {5,10}, 0));
strokePaint.setStrokeWidth(5);
strokePaint.setAntiAlias(true);
isFinished = true;
invalidate();
//isDrawing = false;
}
break;
default:
break;
}
}
return result;
}
It would be helpful if you could post some more code from your onDraw method, because it's hard to say what is called when.
My guess is that invalidate works fine, but each time when onDraw is called, you reset your paint settings (paint = new Paint()), so paint with different color is simply not used.
EDIT:
I can't tell you in which exact line you have a bug, however in my opinion it's not related to path or paint settings. There are too many state flags (isDrawing, isFinished, isAnimating, isRecording, isRedrawing) in your code and you can't control it anymore.
OnDraw method should simply draw points:
#Override
protected void onDraw(Canvas canvas) {
if (points.size() > 0) {
PointF point = points.get(0);
wallpath.rewind();
wallpath.moveTo(point.x, point.y);
for (int i = 1; i < points.size(); i++) {
point = points.get(i);
wallpath.lineTo(point.x, point.y);
}
canvas.drawPath(wallpath, paint);
}
}
Simplify your code. Just for a test: replace your onDraw method with my proposition and you'll see that path will change color on long press.
To fill a Path you need to close it first. Call Path.close() to close the current contour.
If you want to change color of the lines, you need to use paint.setStyle(Paint.Style.STROKE) and use paint.setColor() with it.
... and of course you need to invalidate your view, that onDraw() - where you draw your path - is called again.
I am creating a drawing app that utilizes the DrawingSurfaceView class below. In that class i have a Paint Called eraserPaint that the user can toggle on and off.. When on that paint is suppose to eraser what ever is in its path. but instead its just drawing a black line..
When i save out the canvas as a transparent png the eraser is correct but on the screen it shows black..
Screenshot from phone of EraserPaint used to write "Erik" on blob
Saved out PNG from canvas
eraserPaint looks like this:
eraserPaint = new Paint();
eraserPaint.setAlpha(0);
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setStrokeWidth(60);
eraserPaint.setStyle(Style.STROKE);
eraserPaint.setMaskFilter(null);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
the WHOLE class
public KNDrawingSurfaceView(Context c, float width, float height, KNSketchBookActivity parent) {
super(c);
myWidth = width;
myHeight = height;
mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
_parent = parent;
mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);
tile = new Paint();
tileImage = BitmapFactory.decodeResource(getResources(), R.drawable.checkerpattern);
shader = new BitmapShader(tileImage, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
tile.setShader(shader);
mPath = new Path();
eraserPaint = new Paint();
eraserPaint.setAlpha(0x00);
eraserPaint.setColor(Color.TRANSPARENT);
eraserPaint.setStrokeWidth(60);
eraserPaint.setStyle(Style.STROKE);
//eraserPaint.setMaskFilter(null);
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setAntiAlias(true);
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
mCanvas.drawRect(0, 0, myWidth, myHeight, tile);
mCanvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
}
#Override
protected void onDraw(Canvas canvas) {
if (!_parent.isDrawerOpen()&&mPaint!=null) {
Log.v("onDraw:", "curent paths size:" + paths.size());
//mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
//canvas.drawPath(mPath, mPaint);
for (int i=0;i< paths.size();i++) {
tempPaint = paints.get(i);
eraserPaint.setStrokeWidth(tempPaint.getStrokeWidth());
if(fills.get(i)){
tempPaint.setStyle(Style.FILL_AND_STROKE);
eraserPaint.setStyle(Style.FILL_AND_STROKE);
}else{
tempPaint.setStyle(Style.STROKE);
eraserPaint.setStyle(Style.STROKE);
}
if(erasers.get(i)){
//tempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPath(paths.get(i), eraserPaint);
}else{
//tempPaint.setXfermode(null);
canvas.drawPath(paths.get(i), tempPaint);
}
//canvas.drawPath(paths.get(i), tempPaint);
}
if(_parent.toggleFill.isChecked()){
mPaint.setStyle(Style.FILL_AND_STROKE);
eraserPaint.setStyle(Style.FILL_AND_STROKE);
}else{
mPaint.setStyle(Style.STROKE);
eraserPaint.setStyle(Style.STROKE);
}
if(_parent.toggleErase.isChecked()){
//mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPath(mPath, eraserPaint);
}else{
//mPaint.setXfermode(null);
canvas.drawPath(mPath, mPaint);
}
//canvas.drawPath(mPath, mPaint);
}
}
public void onClickUndo() {
if (paths.size() > 0) {
undonePaths.add(paths.remove(paths.size() - 1));
undonePaints.add(paints.remove(paints.size() - 1));
undoneFills.add(fills.remove(fills.size() - 1));
undoneErasers.add(erasers.remove(erasers.size() - 1));
clearCanvasCache();
invalidate();
} else {
}
_parent.checkButtonStates();
}
public void onClickRedo() {
if (undonePaths.size() > 0) {
paths.add(undonePaths.remove(undonePaths.size() - 1));
paints.add(undonePaints.remove(undonePaints.size() - 1));
fills.add(undoneFills.remove(undoneFills.size() - 1));
erasers.add(undoneErasers.remove(undoneErasers.size() - 1));
clearCanvasCache();
invalidate();
} else {
}
_parent.checkButtonStates();
}
public void onClickClear() {
paths.clear();
paints.clear();
fills.clear();
erasers.clear();
undoneFills.clear();
undonePaths.clear();
undonePaints.clear();
undoneErasers.clear();
clearCanvasCache();
invalidate();
_parent.checkButtonStates();
}
public void saveDrawing() {
FileOutputStream outStream = null;
String fileName = "tempTag";
try {
outStream = new FileOutputStream("/sdcard/" + fileName + ".png");
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, outStream);
outStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
private void touch_start(float x, float y) {
undonePaths.clear();
undonePaints.clear();
undoneFills.clear();
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void touch_move(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
}
private void touch_up() {
mPath.lineTo(mX, mY);
// commit the path to our offscreen
if(_parent.toggleErase.isChecked()){
mCanvas.drawPath(mPath, eraserPaint);
erasers.add(true);
paints.add(eraserPaint);
}else{
mCanvas.drawPath(mPath, mPaint);
erasers.add(false);
paints.add(mPaint);
}
// kill this so we don't double draw
paths.add(mPath);
if(_parent.toggleFill.isChecked()){
fills.add(true);
}else{
fills.add(false);
}
if(_parent.toggleErase.isChecked()){
erasers.add(true);
}else{
erasers.add(false);
}
_parent.checkButtonStates();
mPath = new Path();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(mPaint==null &&!_parent._showingAlert){
_parent.showNoPaintAlert();
}
if (!_parent.isDrawerOpen()&&mPaint!=null) {
float x = event.getX();
float y = event.getY();
if (x > myWidth) {
x = myWidth;
}
if (y > myHeight) {
y = myHeight;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
} else {
return true;
}
}
public void clearCanvasCache() {
mBitmap = Bitmap.createBitmap((int) myWidth, (int) myHeight, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
}
}
I should add that i am adding this Custom View to a relative layout that has that checkered pattern as the background image..
PLEASE PLEASE PLEASE help.. i need that preview image to NOT show black after an eraser paint was used.. i need it to show the checkered pattern behind.. I know the eraser is working as those black eraser marks save out as transparent.
NEW NOTE
I was playing around and discovered something else thats curious. Experimenting, i tried switching from drawing to the canvas as passed to the onDraw method and directly to the canvas i set up in the contructor called mCanvas and noticed it did not draw as far as i could see.. so I added a log to the onDraw like so:
protected void onDraw(Canvas canvas) {
Log.v("DRAWING SURFACE", "canvas:"+canvas+" mCanvas:"+mCanvas);
which spits out
06-21 11:10:43.994: V/DRAWING SURFACE(4532): canvas:android.view.Surface$CompatibleCanvas#42a8c030 mCanvas:android.graphics.Canvas#431df180
I had this same problem with my app. I even tried the "finger paint" example code and still had the same problem. I was never able to have the eraser work as a path, but I was able to find a workaround. Rather than drawing a path when I erase, I draw a circle (It could be any shape) when the user puts his finger down or there is a "move" event:
case MotionEvent.ACTION_DOWN:
mPaint.setStrokeWidth(25);
mPaint.setXfermode(new PorterDuffXfermode(
PorterDuff.Mode.CLEAR));
mCanvas.drawCircle(x, y, 10, mPaint);
isErase = true;
invalidate();
}
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if(isErase)
{
mCanvas.drawCircle(x, y, 20, mPaint);
}
else{
touch_move(x, y);
}invalidate();
break;
It will take some time to incorporate this into your code, but I guarantee you it will take less time than the amount of time you have already spent trying to fix this problem. I can send you more of my PaintView if you think it would be helpful.
Same problem encountered, tried all other solutions found, no luck.
But I got a workaround. You can add a bitmap to store the strokes.
public void init(int width, int height) {
Log.i(TAG,"init with "+width+"x"+height);
foreground = Bitmap.createBitmap(width, height, Config.ARGB_8888);
cacheCanvas = new Canvas();
cacheCanvas.setBitmap(foreground);
}
Whenever there is any touch, record the stroke with the current paint and current stroke width. (the paint could be any color, including the eraser paint)
And then override the onDraw(Canvas) method. As the bitmap supports eraser while the canvas doesn't, we can first draw the resultant image on the bitmap first, and then draw the bitmap to the canvas.
#Override
protected void onDraw(Canvas canvas) {
// Log.i(TAG,"onDraw called");
synchronized (strokes) {
if (strokes.size() > 0) {
for (Stroke s : strokes) {
cacheCanvas.drawPath(s.path, s.color);
}
canvas.drawBitmap(foreground, 0, 0, null);
strokes.clear();
}
}
}
FYI, if the foreground bitmap is very large, the performance will be low. To solve this, we should invalidate only the area which the latest finger touches altered.
This is just a guess: it could be related to hardware acceleration. Try to disable hardware acceleration. If that helps, you can create a bitmap the size of the view, draw all your stuff to that bitmap, and then draw the bitmap into the view's canvas.
For the canvas to erase and invalidate, you have to set the setXfermode of your canvas to null. check the last line of the code.
if(view.getId()==R.id.erase_btn) {
erase_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onDraw.setErase(true);
}
}
}
public void setErase(boolean isErase){
erase=isErase;
if(erase) drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
else drawPaint.setXfermode(null);
}
You can use a boolean variable while choosing eraser (ie. isEraser = true) and in onDraw(), you can draw path if it's not eraser.
#Override
protected void onDraw(Canvas canvas) {
if(!isEraser ){
canvas.drawPath(mPath, mPaint);
}
}
I'm making a custom slide control. Is like a Volume wheel, so some values augment or decrease depending on rotate direction.
I have two issues with this:
The performance is really low
The garbage collector is trigger, many many times.
Well, I'm sure that i'm doing something wrong, so please give me a Light.
Am working with Android graphics on 2.1 (eclaire) SDK.
This is the code of the view that am calling from my activity:
public class DrawingView extends View {
private Paint p;
Bitmap bitmap;
Context mContext;
Canvas canvas;
private float sweepAngle;
private int _height;
private int _width;
private float lastAngle;
private int percent;
public DrawingView(Context context) {
super(context);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
this.set_Width(display.getWidth());
this.set_Height(display.getHeight());
this.setSweepAngle(10);
mContext = context;
p = new Paint();
p.setAntiAlias(true);
}
protected int getAngleFromLocation(Point location){
int finalAngle = (int) (Math.atan2(location.y - 200, location.x - 200) * (180 / Math.PI));
return finalAngle;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(),Bitmap.Config.ARGB_8888);
this.canvas = new Canvas(bitmap);
RectF rectF = new RectF();
rectF.set(20, 20, this.get_Widtt() - this.get_Widtt()/10, this.get_Widtt() - this.get_Widtt()/10);
canvas.drawArc(rectF, 180, this.getSweepAngle(), true, p);
//invalidate();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
double increment = 3.6;
Point touchLocation = new Point();
touchLocation.x = (int)event.getX();
touchLocation.y = (int)event.getY();
canvas.drawBitmap(bitmap = Bitmap.createBitmap(canvas.getWidth(),canvas.getHeight(),Bitmap.Config.ARGB_8888),event.getX(), event.getY(),null );
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
lastAngle = this.getAngleFromLocation(touchLocation);
System.out.println("ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
System.out.println("ACTION_MOVE");
int currentAngle = this.getAngleFromLocation(touchLocation);
System.out.println("CURRENT ANGLE: " + currentAngle);
if (currentAngle > lastAngle || (currentAngle == 1 && lastAngle == 359)) {
percent += increment;
} else if (currentAngle < lastAngle) {
percent -= increment;
}
if (percent > 360) {
percent = 360;
} else if (percent < 0) {
percent = 0;
}
lastAngle = currentAngle;
this.setSweepAngle(percent);
//Write the label
//int realPercent = percent*100/360;
System.out.println("PERCENT: "+percent);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
No need to create a new bitmap on every frame. In fact you should try to avoid allocationg any objects at all in onDraw().
Try this:
private RectF mRectF = new RectF();
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
mRectF.set(20, 20, this.get_Widtt() - this.get_Widtt() / 10, this.get_Widtt() - this.get_Widtt() / 10);
canvas.drawArc(rectF, 180, this.getSweepAngle(), true, p);
}
also remove the call to canvas.drawBitmap() in onTouchEvent.
It's most likely the bitmap/canvas creation that's taking so long and triggering the GC so much. You want to do as little object creation as possible during onDraw() in particular, and onTouchEvent() as well. Is there any reason you can't move that to a separate method that gets called once and reuse the same bitmap each frame? It looks like the values you're passing to createBitmap() are static, so it shouldn't be an issue.