I am working on an application where the user can draw on the screen.
I have a custom View
called: DrawingView
that uses a Canvas
object to draw.
I am able to successfully draw, and save the drawing as a PNG
.
Problem
The problem that I am running into is that when I save the drawing it saves the entire screen, with all the transparent space around the drawing.
for example:
Desired Outcome
What I am hoping to achieve is a way to crop the transparent space from the drawing.
for example:
DrawingView
public class DrawingView extends View {
private final static String TAG = DrawingView.class.getSimpleName();
private static final float TOLERANCE = 5f;
private static final float STROKE_WIDTH = 15f;
private final ArrayList<Pair<Path, Paint>> mPaths = new ArrayList<>();
private Bitmap mBitmap;
private Canvas mCanvas;
private Path mPath;
private Paint mPaint;
private float mX;
private float mY;
public DrawingView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPath = new Path();
mPaint = new Paint(Paint.DITHER_FLAG);
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeWidth(STROKE_WIDTH);
mPaths.add(new Pair<>(mPath, new Paint(mPaint)));
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(mBitmap);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
if (!mPaths.isEmpty()) {
canvas.drawPath(mPaths.get(mPaths.size() - 1).first, mPaths.get(mPaths.size() - 1).second);
}
}
public void save(String path) {
try {
FileOutputStream writer = new FileOutputStream(path);
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, writer);
writer.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void startTouch(float x, float y) {
mPath.reset();
mPath.moveTo(x, y);
mX = x;
mY = y;
mPaths.add(new Pair<>(mPath, new Paint(mPaint)));
invalidate();
}
private void moveTouch(float x, float y) {
float dx = Math.abs(x - mX);
float dy = Math.abs(x - mY);
if (dx >= TOLERANCE || dy >= TOLERANCE) {
mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
mX = x;
mY = y;
}
invalidate();
}
private void upTouch() {
mPath.lineTo(mX, mY);
mCanvas.drawPath(mPath, mPaint);
mPaths.add(new Pair<>(mPath, new Paint(mPaint)));
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(x, y);
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x, y);
break;
case MotionEvent.ACTION_UP:
upTouch();
break;
}
return true;
}
public void clear() {
ArrayList<Pair<Path, Paint>> paths = new ArrayList<>();
for (Pair pair : mPaths) {
paths.add(pair);
}
mPaths.removeAll(paths);
mBitmap = Bitmap.createBitmap(mCanvas.getWidth(), mCanvas.getHeight(), Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(mBitmap);
invalidate();
}
}
Solution
So I used the class RectF
to keep track of my min and max X,Y coordinates when drawing the image.
I created a RectF
object in my constructor called: mDrawnAreaRect
then I added a method to find the min and max X,Y values called findMinMax
:
private void findMinMax(int x, int y) {
if (x < mDrawnAreaRect.left) { //min x
mDrawnAreaRect.left = x;
}
if (y > mDrawnAreaRect.top) { //max y
mDrawnAreaRect.top = y;
}
if (x > mDrawnAreaRect.right) { //max x
mDrawnAreaRect.right = x;
}
if (y < mDrawnAreaRect.bottom) { //min y
mDrawnAreaRect.bottom = y;
}
}
I then implemented this method at the bottom of Override onTouchEvent
method like so:
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startTouch(x, y);
break;
case MotionEvent.ACTION_MOVE:
moveTouch(x, y);
break;
case MotionEvent.ACTION_UP:
upTouch();
break;
}
findMinMax((int) x, (int) y); //IMPLEMENTED HERE
return true;
}
finally when I save the Bitmap I use the left, top, right, and bottom values from mDrawnAreaRect
to crop out the transparent space in a method called: cropBitmap
:
private Bitmap cropBitmap() {
int minX = mDrawnAreaRect.left <= 0 ? 0 : (int) mDrawnAreaRect.left;
int maxY = mDrawnAreaRect.top > mBitmap.getHeight() ? mBitmap.getHeight() : (int) mDrawnAreaRect.top;
int maxX = mDrawnAreaRect.right > mBitmap.getWidth() ? mBitmap.getWidth() : (int) mDrawnAreaRect.right;
int minY = mDrawnAreaRect.bottom <= 0 ? 0 : (int) mDrawnAreaRect.bottom;
int width = maxX - minX;
int height = maxY - minY;
width = width > mBitmap.getWidth() ? mBitmap.getWidth() : width;
height = height > mBitmap.getHeight() ? mBitmap.getHeight() : height;
return Bitmap.createBitmap(mBitmap, minX, minY, width, height);
}