/* * Copyright (C) 2015 Federico Iosue (federico.iosue@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package it.feio.android.omninotes.models.views; import android.app.Activity; import android.content.Context; import android.graphics.*; import android.util.AttributeSet; import android.util.Pair; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import it.feio.android.omninotes.models.listeners.OnDrawChangedListener; import java.util.ArrayList; public class SketchView extends View implements OnTouchListener { private static final float TOUCH_TOLERANCE = 4; public static final int STROKE = 0; public static final int ERASER = 1; public static final int DEFAULT_STROKE_SIZE = 7; public static final int DEFAULT_ERASER_SIZE = 50; private float strokeSize = DEFAULT_STROKE_SIZE; private int strokeColor = Color.BLACK; private float eraserSize = DEFAULT_ERASER_SIZE; private int background = Color.WHITE; // private Canvas mCanvas; private Path m_Path; private Paint m_Paint; private float mX, mY; private int width, height; private ArrayList<Pair<Path, Paint>> paths = new ArrayList<>(); private ArrayList<Pair<Path, Paint>> undonePaths = new ArrayList<>(); private Context mContext; private Bitmap bitmap; private int mode = STROKE; private OnDrawChangedListener onDrawChangedListener; public SketchView(Context context, AttributeSet attr) { super(context, attr); this.mContext = context; setFocusable(true); setFocusableInTouchMode(true); setBackgroundColor(Color.WHITE); this.setOnTouchListener(this); m_Paint = new Paint(); m_Paint.setAntiAlias(true); m_Paint.setDither(true); m_Paint.setColor(strokeColor); m_Paint.setStyle(Paint.Style.STROKE); m_Paint.setStrokeJoin(Paint.Join.ROUND); m_Paint.setStrokeCap(Paint.Cap.ROUND); m_Paint.setStrokeWidth(strokeSize); m_Path = new Path(); Paint newPaint = new Paint(m_Paint); invalidate(); } public void setMode(int mode) { if (mode == STROKE || mode == ERASER) this.mode = mode; } public int getMode() { return this.mode; } /** * Change canvass background and force redraw * * @param bitmap */ public void setBackgroundBitmap(Activity mActivity, Bitmap bitmap) { if (!bitmap.isMutable()) { android.graphics.Bitmap.Config bitmapConfig = bitmap.getConfig(); // set default bitmap config if none if (bitmapConfig == null) { bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888; } bitmap = bitmap.copy(bitmapConfig, true); } this.bitmap = bitmap; // this.bitmap = getScaledBitmap(mActivity, bitmap); // mCanvas = new Canvas(bitmap); } // private Bitmap getScaledBitmap(Activity mActivity, Bitmap bitmap) { // DisplayMetrics display = new DisplayMetrics(); // mActivity.getWindowManager().getDefaultDisplay().getMetrics(display); // int screenWidth = display.widthPixels; // int screenHeight = display.heightPixels; // float scale = bitmap.getWidth() / screenWidth > bitmap.getHeight() / screenHeight ? bitmap.getWidth() / // screenWidth : bitmap.getHeight() / screenHeight; // int scaledWidth = (int) (bitmap.getWidth() / scale); // int scaledHeight = (int) (bitmap.getHeight() / scale); // return Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledHeight, true); // } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { width = View.MeasureSpec.getSize(widthMeasureSpec); height = View.MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(width, height); } public boolean onTouch(View arg0, 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; } @Override protected void onDraw(Canvas canvas) { if (bitmap != null) { canvas.drawBitmap(bitmap, 0, 0, null); } for (Pair<Path, Paint> p : paths) { canvas.drawPath(p.first, p.second); } onDrawChangedListener.onDrawChanged(); } private void touch_start(float x, float y) { // Clearing undone list undonePaths.clear(); if (mode == ERASER) { m_Paint.setColor(Color.WHITE); m_Paint.setStrokeWidth(eraserSize); } else { m_Paint.setColor(strokeColor); m_Paint.setStrokeWidth(strokeSize); } Paint newPaint = new Paint(m_Paint); // Clones the mPaint object // Avoids that a sketch with just erasures is saved if (!(paths.size() == 0 && mode == ERASER && bitmap == null)) { paths.add(new Pair<>(m_Path, newPaint)); } m_Path.reset(); m_Path.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); m_Path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mX = x; mY = y; } private void touch_up() { m_Path.lineTo(mX, mY); Paint newPaint = new Paint(m_Paint); // Clones the mPaint object // Avoids that a sketch with just erasures is saved if (!(paths.size() == 0 && mode == ERASER && bitmap == null)) { paths.add(new Pair<>(m_Path, newPaint)); } // kill this so we don't double draw m_Path = new Path(); } /** * Returns a new bitmap associated with drawed canvas * * @return */ public Bitmap getBitmap() { if (paths.size() == 0) return null; if (bitmap == null) { bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); bitmap.eraseColor(background); } Canvas canvas = new Canvas(bitmap); for (Pair<Path, Paint> p : paths) { canvas.drawPath(p.first, p.second); } return bitmap; } public void undo() { if (paths.size() >= 2) { undonePaths.add(paths.remove(paths.size() - 1)); // If there is not only one path remained both touch and move paths are removed undonePaths.add(paths.remove(paths.size() - 1)); invalidate(); } } public void redo() { if (undonePaths.size() > 0) { paths.add(undonePaths.remove(undonePaths.size() - 1)); paths.add(undonePaths.remove(undonePaths.size() - 1)); invalidate(); } } public int getUndoneCount() { return undonePaths.size(); } public ArrayList<Pair<Path, Paint>> getPaths() { return paths; } public void setPaths(ArrayList<Pair<Path, Paint>> paths) { this.paths = paths; } public ArrayList<Pair<Path, Paint>> getUndonePaths() { return undonePaths; } public void setUndonePaths(ArrayList<Pair<Path, Paint>> undonePaths) { this.undonePaths = undonePaths; } public int getStrokeSize() { return Math.round(this.strokeSize); } public void setSize(int size, int eraserOrStroke) { switch (eraserOrStroke) { case STROKE: strokeSize = size; break; case ERASER: eraserSize = size; break; } } public int getStrokeColor() { return this.strokeColor; } public void setStrokeColor(int color) { strokeColor = color; } public void erase() { paths.clear(); undonePaths.clear(); invalidate(); } public void setOnDrawChangedListener(OnDrawChangedListener listener) { this.onDrawChangedListener = listener; } }