I'm making an app where user will be able to click on part of the image and get a magnified version in the corner of WebView. I managed to make a Paint that would make a zoom version, but it displays wrong location, like there's some offset.
I know this question has been asked a lot of times and was already answered, but it appears non of those solutions helped.
Here's code I've used:
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
zoomPos = new PointF();
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix = new Matrix();
mShader = new BitmapShader(MainActivity.mutableBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
outlinePaint = new Paint(Color.BLACK);
outlinePaint.setStyle(Paint.Style.STROKE);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
zooming = true;
this.invalidate();
break;
case MotionEvent.ACTION_UP:
Point1 = true;
zooming = false;
this.invalidate();
break;
case MotionEvent.ACTION_CANCEL:
zooming = false;
this.invalidate();
break;
default:
break;
}
return true;
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
super.onDraw(canvas);
if (zooming) {
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
mPaint.getShader().setLocalMatrix(matrix);
canvas.drawCircle(100, 100, 100, mPaint);
}
}
Technically it should draw a circle at upper-left corner and display zoomed image of area where my finger is, it draws a circle, but again, zoom is shifted.
Final result should look something like this:
MainActivity.java
public class MainActivity extends Activity {
static ImageView takenPhoto;
static PointF zoomPos;
Paint shaderPaint;
static BitmapShader mShader;
BitmapShader shader;
Bitmap bmp;
static Bitmap mutableBitmap;
static Matrix matrix;
Canvas canvas;
static Paint mPaint;
static Paint Paint;
static boolean zooming;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
File file = new File(Environment.getExternalStorageDirectory() + "/Pictures/boxes.jpg");
String fileString = file.getPath();
takenPhoto = (ZoomView) findViewById(R.id.imageView1);
bmp = BitmapFactory.decodeFile(fileString);
mutableBitmap = bmp.copy(Bitmap.Config.ARGB_8888, true);
takenPhoto.setImageBitmap(mutableBitmap);
matrix = new Matrix();
mShader = new BitmapShader(mutableBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
zoomPos = new PointF();
Paint = new Paint(Color.RED);
}
}
ZoomView.java
public class ZoomView extends ImageView {
private PointF zoomPos;
PointF fingerPos;
private Paint paint = new Paint(Color.BLACK);
boolean zooming;
Matrix matrix;
BitmapShader mShader;
Paint mPaint;
Paint outlinePaint;
boolean Point1;
public ZoomView(Context context) {
super(context);
}
public ZoomView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ZoomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean onTouchEvent(#NonNull MotionEvent event) {
zoomPos = new PointF();
zoomPos.x = event.getX();
zoomPos.y = event.getY();
matrix = new Matrix();
mShader = new BitmapShader(MainActivity.mutableBitmap, TileMode.CLAMP, TileMode.CLAMP);
mPaint = new Paint();
mPaint.setShader(mShader);
outlinePaint = new Paint(Color.BLACK);
outlinePaint.setStyle(Paint.Style.STROKE);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
zooming = true;
this.invalidate();
break;
case MotionEvent.ACTION_UP:
Point1 = true;
zooming = false;
this.invalidate();
break;
case MotionEvent.ACTION_CANCEL:
zooming = false;
this.invalidate();
break;
default:
break;
}
return true;
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
super.onDraw(canvas);
if (zooming) {
matrix.reset();
matrix.postScale(2f, 2f, zoomPos.x, zoomPos.y);
mPaint.getShader().setLocalMatrix(matrix);
RectF src = new RectF(zoomPos.x-50, zoomPos.y-50, zoomPos.x+50, zoomPos.y+50);
RectF dst = new RectF(0, 0, 100, 100);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
matrix.postScale(2f, 2f);
mPaint.getShader().setLocalMatrix(matrix);
canvas.drawCircle(100, 100, 100, mPaint);
canvas.drawCircle(zoomPos.x, zoomPos.y, 100, mPaint);
canvas.drawCircle(zoomPos.x-110, zoomPos.y-110, 10, outlinePaint);
}
if(Point1){
canvas.drawCircle(zoomPos.x, zoomPos.y, 10, paint);
}
}
}
EDIT:
As you can see new code is way better, still there is some offset - black dot - position of the finger.
Seems that the issue is with how you are using the matrix.
Now you are using the original image (1) as a shader which is then being post scaled up around a pivot point (2), which is like doing a zoom around a point (3) - but not centering the point (4) ! (For example, open google maps and zoom in on the map with your mouse - the point is zoomed around the pivot but the pivot is not centered)
What will be an easier way to achieve what you want is by using the Rect to Rect method. I.E. you want to take a small area from the original image (5) and draw it to a larger area (6) .
And here is a code sample:
RectF src = new RectF(zoomPos.x-50, zoomPos.y-50, zoomPos.x+50, zoomPos.y+50);
RectF dst = new RectF(0, 0, 200, 200);
matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
Some more points:
Try not to create new object in the onTouch - it is being called many times and it is not good on performance . Instead, create once and reuse.
getAction() will have issues when there are more than one finger on screen since it is the action ID and the pointer ID. Instead use getActionMasked() and getActionIndex().
Do not use hardcoded values (50/100 in the sample code) - these are pixels and are not aware of screen density. Use scaled size like dp.
Related
I have created a drawing application in android and was wondering, what is the correct way to draw on canvas while been concise of RAM allocation.
I have noticed that when the number of paths or points within the paths become too large, delays in onDraw(Canvas canvas) become apparent. It is obvious there is a solution to this, but I am looking for the standard way to solve this.
I have not found much information online, but I am currently trying to save the current canvas to a Bitmap and then setting my View for the canvas (LinearLayout).
I am currently trying the following, but it is not setting the LinearLayout to the Background:
public Bitmap saveTempCanvas() {
Bitmap.Config conf = Bitmap.Config.ARGB_8888;
this.setDrawingCacheEnabled(true);
this.buildDrawingCache();
this.setBackgroundColor(Color.WHITE);
Bitmap bmp = Bitmap.createBitmap(this.getDrawingCache());
this.setDrawingCacheEnabled(false);
return bmp;
}
//. . .
//. . .
Drawable ob = new BitmapDrawable(getResources(), pv.saveTempCanvas());
LinearLayoutCanvas.setBackgroundDrawable(ob);
Is this the standard way to create an "Paint" activity?
Why is the following code not working to set the BitmapDrawable?
If you are calling this function in onDraw() each time, I am not surprised why it is more and more laggy. Here is my approach and it works well:
public class TouchEventView extends View {
private Paint paint = new Paint();
private Path path = new Path();
Bitmap bitMap;
Canvas myCanvas;
Canvas defaultCanvas;
public TouchEventView(Context context) {
this(context, null);
}
public TouchEventView(Context context, AttributeSet attrs) {
super(context, attrs);
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
bitMap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
paint.setAntiAlias(true);
paint.setStrokeWidth(6f);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.BEVEL);
}
#Override
protected void onDraw(Canvas canvas) {
myCanvas = new Canvas(bitMap);
defaultCanvas = canvas;
canvas.drawPath(path, paint);
myCanvas.drawColor(Color.WHITE);
myCanvas.setBitmap(bitMap);
myCanvas.drawPath(path, paint);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(eventX, eventY);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(eventX, eventY);
break;
case MotionEvent.ACTION_UP:
performClick();
break;
default:
return false;
}
invalidate();
return true;
}
#Override
public boolean performClick() {
super.performClick();
return true;
}
}
I am working on a painting app, with undo/redo function and would like to add eraser function.
Code for MainActivity
case R.id.undoBtn:
doodleView.onClickUndo();
break;
case R.id.redoBtn:
doodleView.onClickRedo();
break;
case R.id.eraserBtn:
Constants.mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); break;
DrawView
// Drawing Part
private Bitmap mBitmap;
private Paint mBitmapPaint;
private Canvas mCanvas;
private Path mPath;
private int selectedColor = Color.BLACK;
private int selectedWidth = 5;
private ArrayList<Path> paths = new ArrayList<Path>();
private ArrayList<Path> undonePaths = new ArrayList<Path>();
private Map<Path, Integer> colorsMap = new HashMap<Path, Integer>();
private Map<Path, Integer> widthMap = new HashMap<Path, Integer>();
private float mX, mY;
private static final float TOUCH_TOLERANCE = 4;
Context context_new;
public DoodleView(Context c, AttributeSet attrs)
{
super(c, attrs);
context_new = c;
setFocusable(true);
setFocusableInTouchMode(true);
setLayerType(View.LAYER_TYPE_SOFTWARE, null); // for solely removing the black eraser
mPath = new Path();
mCanvas = new Canvas();
mBitmapPaint = new Paint(Paint.DITHER_FLAG);
Constants.mPaint = new Paint();
Constants.mPaint.setAntiAlias(true); // smooth edges of drawn line
Constants.mPaint.setDither(true);
Constants.mPaint.setColor(Color.BLACK); // default color is black
Constants.mPaint.setStyle(Paint.Style.STROKE); // solid line
Constants.mPaint.setStrokeJoin(Paint.Join.ROUND);
Constants.mPaint.setStrokeWidth(20); // set the default line width
Constants.mPaint.setStrokeCap(Paint.Cap.ROUND); // rounded line ends
Constants.mPaint.setXfermode(null);
Constants.mPaint.setAlpha(0xFF);
}
#Override
public void onSizeChanged(int w, int h, int oldW, int oldH)
{
super.onSizeChanged(w, h, oldW, oldH);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Constants.DRAW_W = w;
Constants.DRAW_H = h;
Log.d("TAG", "onSizeChanged!!!" + Constants.DRAW_W + Constants.DRAW_H + Constants.SCREEN_W + Constants.SCREEN_H);
// bitmap.eraseColor(Color.WHITE); // erase the BitMap with white
}
#Override
protected void onDraw(Canvas canvas)
{
canvas.drawBitmap(mBitmap, 0, 0, null); // draw the background screen
for (Path p : paths)
{
Constants.mPaint.setColor(colorsMap.get(p));
Constants.mPaint.setStrokeWidth(widthMap.get(p));
canvas.drawPath(p, Constants.mPaint);
}
Constants.mPaint.setColor(selectedColor);
Constants.mPaint.setStrokeWidth(selectedWidth);
canvas.drawPath(mPath, Constants.mPaint);
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
float x = event.getX();
float y = event.getY();
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;
}
private void touch_start(float x, float y)
{
undonePaths.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;
startdrawing = true;
}
}
private void touch_up()
{
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, Constants.mPaint);
paths.add(mPath);
colorsMap.put(mPath,selectedColor);
widthMap.put(mPath,selectedWidth);
mPath = new Path();
}
public void onClickUndo()
{
if (paths.size()>0)
{
undonePaths.add(paths.remove(paths.size()-1));
invalidate();
}
else Toast.makeText(getContext(), R.string.toast_nothing_to_undo, Toast.LENGTH_SHORT).show();
}
public void onClickRedo()
{
if (undonePaths.size()>0)
{
paths.add(undonePaths.remove(undonePaths.size()-1));
invalidate();
}
else
Toast.makeText(getContext(), R.string.toast_nothing_to_redo, Toast.LENGTH_SHORT).show();
}
public void setDrawingColor(int color)
{
selectedColor = color;
Constants.mPaint.setColor(color);
}
public int getDrawingColor()
{
return Constants.mPaint.getColor();
}
Question:
Normal painting and undo / redo can be performed perfectly. However, the eraser does not work.
After pressing (touch_start) the eraser button and touch the screen, all the previous drawn line immediately turn black.
When using the eraser upon touch_move , the eraser itself is drawing black lines, even though it was on CLEAR mode.
Upon touch_up, All other drawn lines maintained at black color. The "erased" area was also in black color. However, when drawing a new path subsequently, the original line turned to their original color, the area "erased" by the eraser turn its color to the last color chosen and the paths retained in the View.
How could the code on eraser be written in a proper way? (keeping undo / redo)
Thanks a lot in advance!
It's hard to answer your question, but. I'd implement the erase functionality as follows:
The erase function will by simple white path. So the erase mode means that you are drawing white path which will be wider than the drawing path. This way you will be able to select even the width of the eraser and the undo/redo functionality will stay the same.
Try this type of application with samsung spen sdk, all this funtions are simplified there.
SPen Sdk Tutorial
or
Make your erase paint color to your background color.
// Please use this is erasure concept . This is working and tested.
public void addErasure(){
drawView.setErase(true);
drawView.setBrushSize(20);
}
public void addPencil(){
drawView.setErase(false);
drawView.setBrushSize(20);
drawView.setLastBrushSize(20);
}
/// this my drawing view. you can add this view into your main layout.
public class DrawingView extends View {
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor = 0xFF660000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//brush sizes
private float brushSize, lastBrushSize;
//erase flag
private boolean erase=false;
private boolean isFirstTime = false;
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
//setBackgroundColor(Color.CYAN);
setupDrawing();
}
//setup drawing
private void setupDrawing(){
//prepare for drawing and setup paint stroke properties
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(brushSize);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
//canvasPaint.setColor(Color.GREEN);
}
//size assigned to view
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Bitmap canvasBackGroundBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
canvasBackGroundBitmap = getResizedBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.pop_up_big_bg),h,w);
// drawCanvas = new Canvas(canvasBackGroundBitmap);
// drawCanvas.drawColor(Color.GREEN);
setBackgroundDrawable(new BitmapDrawable(canvasBackGroundBitmap));
drawCanvas = new Canvas(canvasBitmap);
}
public static Bitmap getResizedBitmap(Bitmap bm, int newHeight, int newWidth) {
int width = bm.getWidth();
int height = bm.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// "RECREATE" THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
//draw the view - will be called after touch event
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
//register user touches as drawing action
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
//drawPath.lineTo(touchX, touchY);
//drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
//update color
public void setColor(String newColor){
invalidate();
paintColor = Color.parseColor(newColor);
drawPaint.setColor(paintColor);
}
//set brush size
public void setBrushSize(float newSize){
float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
newSize, getResources().getDisplayMetrics());
brushSize=pixelAmount;
drawPaint.setStrokeWidth(brushSize);
}
//get and set last brush size
public void setLastBrushSize(float lastSize){
lastBrushSize=lastSize;
}
public float getLastBrushSize(){
return lastBrushSize;
}
//set erase true or false
public void setErase(boolean isErase){
erase=isErase;
if(erase) {
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}else {
drawPaint.setXfermode(null);
}
}
//start new drawing
public void startNew(){
drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
invalidate();
}
}
Try this solution - works great with undo/redo as well:
private float blur = 0F;
case R.id.eraserBtn:
Constants.mPaint.setColor(Color.WHITE); // or whatever color to match canvas color
Constants.mPaint.setShadowLayer(this.blur, 0F, 0F, Color.WHITE);
With this solution, no need to set:
Constants.mPaint.setXfermode(null);
I am trying to make undo,redo options in custom drawing view app. But it is not working as i want.
I am not good in english. Through picture, let me try to show the working of undo , redo functions. Hope so, this picture will explain my problem.
i want to remove first line on first undo click and second line on second undo click and so on... similarly on redo click draw last line on first click, second last on second click and so on..
Here is my custom view class.
public class DrawingView extends View{
ArrayList<Path> undoPath = new ArrayList<Path>();
ArrayList<Path> paths = new ArrayList<Path>();
private static int pathSize =0;
//draw path
private Path drawPath;
//drawing and canvas point
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor=0xFF660000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
private float brushSize, lastBrushSize;
public DrawingView(Context context, AttributeSet attrs) {
super(context, attrs);
setupDrawing();
}
public void setPattern(String newPattern){
invalidate();
int patternID = getResources().getIdentifier(newPattern, "drawable", "com.faisalahsan.paintingapp");
Bitmap patternBMP = BitmapFactory.decodeResource(getResources(), patternID);
BitmapShader patternBMPShader = new BitmapShader(patternBMP, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
drawPaint.setColor(0xFFFFFFFF);
drawPaint.setShader(patternBMPShader);
}
private void setupDrawing(){
//get drawing area setup for interaction
drawPath = new Path();
drawPaint= new Paint();
paths.add(drawPath);
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);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
#Override
protected void onDraw(Canvas canvas) {
for(Path p: paths){
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(p, drawPaint);
}
}
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
public void onClickUndo () {
if (paths.size()>0) {
undoPath.add(paths.remove(paths.size()-1));
invalidate();
}
}
public void onClickRedo (){
if (undoPath.size()>0) {
paths.add(undoPath.remove(undoPath.size()-1));
invalidate();
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
drawPath = new Path();
paths.add(drawPath);
break;
default:
return false;
}
invalidate();
return true;
}
}
You are not "redrawing" on the canvas when "undo" removes the action from the ArrayList "paths" - in other words, taking it out of the list does not change the image. You have to redraw what was on the canvas before it was drawn in order to "undo" the action.
Save the state of the canvas when "onDraw" starts and then redraw that canvas when undo is called. You need to keep a synchronized stack of canvas states in order to perform a series of "undo" and "redo" calls.
I am trying to implement a simple drawable view.
Right now I am using Path's quadTo method to draw a smooth line.
And the result like this :
I don't know how can draw a line small gradually when user move his finger fast. The same with this example :
Do you know how can I get this result ? (any way, engine or open source). Right now, I am thinking about implement my own "quadTo" method. But I think it is gonna be slow (or it over my ability). Because it is a native method on Android SDK.
Thank you for any help.
this is my implement for my simple drawable view for anyone who need it:
public class TestView extends LinearLayout{
private static final String TAG = "TestView";
private PointF previousPoint;
private PointF startPoint;
private PointF currentPoint;
private static final float STROKE_WIDTH = 5f;
private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint paintBm = new Paint(Paint.ANTI_ALIAS_FLAG);
private Bitmap bmp;
private Canvas canvasBmp;
private Path path;
private int paintSize = 25;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
this.setWillNotDraw(false);
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(STROKE_WIDTH);
paint.setColor(Color.BLACK);
//paint.setAlpha(100);
paintBm.setAntiAlias(true);
paintBm.setStyle(Paint.Style.STROKE);
paintBm.setStrokeJoin(Paint.Join.ROUND);
paintBm.setStrokeCap(Paint.Cap.ROUND);
paintBm.setStrokeWidth(STROKE_WIDTH);
paintBm.setColor(Color.BLACK);
paintBm.setAlpha(100);
path = new Path();
//paint.setPathEffect(new CornerPathEffect(2));
}
public TestView(Context context) {
super(context);
// TODO Auto-generated constructor stub
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setStrokeWidth(STROKE_WIDTH);
paint.setColor(Color.BLACK);
path = new Path();
//paint.setPathEffect(new CornerPathEffect(2));
}
#Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
if(bmp == null){
bmp = Bitmap.createBitmap(right-left,bottom-top,Bitmap.Config.ARGB_8888);
canvasBmp = new Canvas(bmp);
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
//printSamples(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
currentPoint = new PointF(event.getX(), event.getY());
previousPoint = currentPoint;
startPoint = previousPoint;
path.reset();
break;
case MotionEvent.ACTION_MOVE:
startPoint = previousPoint;
previousPoint = currentPoint;
currentPoint = new PointF(event.getX(), event.getY());
int historySize = event.getHistorySize();
for(int i = 0; i < historySize; i++){
}
drawLine(canvasBmp, path, paint, previousPoint, currentPoint);
//path.moveTo(currentPoint.x, currentPoint.y);
break;
case MotionEvent.ACTION_UP:
startPoint = previousPoint;
previousPoint = currentPoint;
currentPoint = new PointF(event.getX(), event.getY());
drawLine(canvasBmp, path, paint, previousPoint, currentPoint);
paintSize = 25;
break;
default:
break;
}
invalidate();
return true;// super.onTouchEvent(event);
}
#Override
protected void onDraw(Canvas canvas) {
Log.v("pichan", "dasd");
//canvas.drawBitmap(bmp, 0,0, null);
//canvas.drawColor(Color.BLUE);
//canvas.drawPath(path, paint);
canvas.drawBitmap(bmp, 0, 0, paintBm);
}
private void drawLine(Canvas canvas, Path path, Paint paint, PointF start, PointF end)
{
PointF mid1 = midPoint(previousPoint, startPoint);
PointF mid2 = midPoint(end, start);
path.reset();
paint.setStrokeWidth(paintSize);
path.moveTo(mid1.x, mid1.y);
path.quadTo(previousPoint.x, previousPoint.y, mid2.x, mid2.y);
canvas.drawPath(path, paint);
//canvas.
//paintSize -= 1;
}
private PointF midPoint(PointF p1, PointF p2)
{
return new PointF((p1.x + p2.x) / 2.0f , (p1.y + p2.y) * 0.5f);
}
}
After time researching, I build a SignView which let user sign on or draw and the result will be save to an image file.
Anyone interested in can take a look here:
SignView
Hoping this custom view can save someone time.
My basic goal is to subtract a Path from a predetermined region (also created with a Path) in Android 1.6.
Is there anyway to get Region.setPath to treat the path passed to it the same way the canvas treats a stroked path (which is what my users are seeing)?
I understand that the Style is used for Painting to the canvas, but I need the behavior to reflect what is drawn on canvas and the path being drawn is Stroked.
The behavior works find when adding shapes to the path (path.addRect, path.addCircle), but not when using lineTo, quadTo, cubeTo. My only other option is to compose a path of nothing but rects or circles.
public class ComplexRegions extends Activity {
static Display display;
/** Code Sample for StackOverflow */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
display = getWindowManager().getDefaultDisplay();
setContentView(new SampleView(this));
}
private static class SampleView extends View {
Path orig_path, finger_path;
Paint dirty_paint, fingerpaint, temp_paint;
Region orig_region, clip, fingerregion, tempregion;
Canvas c;
int h, w;
public float mX, mY, dx, dy;
boolean b;
public SampleView(Context context) {
super(context);
fingerregion = new Region();
tempregion = new Region();
orig_region = new Region();
clip = new Region();
orig_path = new Path();
finger_path = new Path();
dirty_paint = new Paint();
dirty_paint.setStyle(Paint.Style.STROKE);
dirty_paint.setColor(Color.BLUE);
dirty_paint.setStrokeWidth(5);
dirty_paint.setStrokeCap(Paint.Cap.ROUND);
dirty_paint.setStrokeJoin(Paint.Join.ROUND);
dirty_paint.setAntiAlias(false);
fingerpaint = new Paint();
fingerpaint.setColor(Color.GREEN);
fingerpaint.setStrokeWidth(10);
fingerpaint.setStyle(Paint.Style.STROKE);
fingerpaint.setStrokeCap(Paint.Cap.ROUND);
fingerpaint.setStrokeJoin(Paint.Join.ROUND);
fingerpaint.setAntiAlias(false);
temp_paint = new Paint();
temp_paint.setColor(Color.RED);
temp_paint.setStrokeWidth(1);
temp_paint.setStyle(Paint.Style.STROKE);
temp_paint.setStrokeCap(Paint.Cap.ROUND);
temp_paint.setStrokeJoin(Paint.Join.ROUND);
temp_paint.setAntiAlias(false);
w = display.getWidth();
h = display.getHeight();
clip.set(0, 0, w, h);
orig_path.addRect(w*0.25f, h*0.55f, w*0.65f, h*0.65f, Path.Direction.CW);
orig_region.setPath(orig_path, clip);
}
#Override
protected void onDraw(Canvas c) {
c.drawPath(orig_path, dirty_paint);
c.drawPath(finger_path, fingerpaint);
//following line shows that finger_path is
//behaving as though fill and stroke is on
//after being passed to the region class
c.drawPath(tempregion.getBoundaryPath(), temp_paint);
invalidate();
}
//L T R B
#Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int)event.getX();
int y = (int)event.getY();
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;
}
private void touch_start(float x, float y) {
finger_path.moveTo(x, y);
//finger_path.addCircle(x, y, 25, Path.Direction.CCW);
//when addCircle is only Path method used on path the result is fine
}
private void touch_move(float x, float y){
finger_path.lineTo(x, y);
}
private void touch_up() {
//*PROBLEM* Seems like setPath forces finger_path to default to fill and stroke
fingerregion.setPath(finger_path, clip);
//set tempregion to the region resulting from this Op
tempregion.op(orig_region, fingerregion, Region.Op.DIFFERENCE);
//check if the resulting region is empty
if(tempregion.isEmpty()){
for(int i = 0;i<100;i++)
Log.e("CR", "Region Completely Covered.");
}
}
}
}