diff --git a/app/src/main/java/com/simplemobiletools/draw/MyCanvas.java b/app/src/main/java/com/simplemobiletools/draw/MyCanvas.java deleted file mode 100644 index fc967c7..0000000 --- a/app/src/main/java/com/simplemobiletools/draw/MyCanvas.java +++ /dev/null @@ -1,277 +0,0 @@ -package com.simplemobiletools.draw; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; - -import java.util.LinkedHashMap; -import java.util.Map; - -public class MyCanvas extends View { - private Paint mPaint; - private MyPath mPath; - private Map mPaths; - private PathsChangedListener mListener; - - private PaintOptions mPaintOptions; - private float mCurX; - private float mCurY; - private float mStartX; - private float mStartY; - private boolean mIsSaving = false; - private boolean mIsStrokeWidthBarEnabled = false; - - public MyCanvas(Context context, AttributeSet attrs) { - super(context, attrs); - - mPath = new MyPath(); - mPaint = new Paint(); - mPaintOptions = new PaintOptions(); - mPaint.setColor(mPaintOptions.getColor()); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setStrokeJoin(Paint.Join.ROUND); - mPaint.setStrokeCap(Paint.Cap.ROUND); - mPaint.setStrokeWidth(mPaintOptions.getStrokeWidth()); - mPaint.setAntiAlias(true); - - mPaths = new LinkedHashMap<>(); - mPaths.put(mPath, mPaintOptions); - pathsUpdated(); - } - - public void setListener(PathsChangedListener listener) { - this.mListener = listener; - } - - public void undo() { - if (mPaths.size() <= 0) - return; - - MyPath lastKey = null; - for (MyPath key : mPaths.keySet()) { - lastKey = key; - } - - mPaths.remove(lastKey); - pathsUpdated(); - invalidate(); - } - - public void setColor(int newColor) { - mPaintOptions.setColor(newColor); - if (mIsStrokeWidthBarEnabled) { - invalidate(); - } - } - - public void setStrokeWidth(float newStrokeWidth) { - mPaintOptions.setStrokeWidth(newStrokeWidth); - if (mIsStrokeWidthBarEnabled) { - invalidate(); - } - } - - public void setIsStrokeWidthBarEnabled(boolean isStrokeWidthBarEnabled) { - mIsStrokeWidthBarEnabled = isStrokeWidthBarEnabled; - invalidate(); - } - - public Bitmap getBitmap() { - final Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); - final Canvas canvas = new Canvas(bitmap); - canvas.drawColor(Color.WHITE); - mIsSaving = true; - draw(canvas); - mIsSaving = false; - return bitmap; - } - - public Map getPaths() { - return mPaths; - } - - public void addPath(MyPath path, PaintOptions options) { - mPaths.put(path, options); - pathsUpdated(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - for (Map.Entry entry : mPaths.entrySet()) { - changePaint(entry.getValue()); - canvas.drawPath(entry.getKey(), mPaint); - } - - changePaint(mPaintOptions); - canvas.drawPath(mPath, mPaint); - - if (mIsStrokeWidthBarEnabled && !mIsSaving) { - drawPreviewCircle(canvas); - } - } - - private void drawPreviewCircle(Canvas canvas) { - Resources res = getResources(); - mPaint.setStyle(Paint.Style.FILL); - - float y = getHeight() - res.getDimension(R.dimen.preview_dot_offset_y); - canvas.drawCircle(getWidth() / 2, y, mPaintOptions.getStrokeWidth() / 2, mPaint); - mPaint.setStyle(Paint.Style.STROKE); - mPaint.setColor(Utils.shouldUseWhite(mPaintOptions.getColor()) ? Color.WHITE : Color.BLACK); - mPaint.setStrokeWidth(res.getDimension(R.dimen.preview_dot_stroke_size)); - - y = getHeight() - res.getDimension(R.dimen.preview_dot_offset_y); - float radius = (mPaintOptions.getStrokeWidth() + res.getDimension(R.dimen.preview_dot_stroke_size)) / 2; - canvas.drawCircle(getWidth() / 2, y, radius, mPaint); - changePaint(mPaintOptions); - } - - private void changePaint(PaintOptions paintOptions) { - mPaint.setColor(paintOptions.getColor()); - mPaint.setStrokeWidth(paintOptions.getStrokeWidth()); - } - - public void clearCanvas() { - mPath.reset(); - mPaths.clear(); - pathsUpdated(); - invalidate(); - } - - private void actionDown(float x, float y) { - mPath.reset(); - mPath.moveTo(x, y); - mCurX = x; - mCurY = y; - } - - private void actionMove(float x, float y) { - mPath.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2); - mCurX = x; - mCurY = y; - } - - private void actionUp() { - mPath.lineTo(mCurX, mCurY); - - // draw a dot on click - if (mStartX == mCurX && mStartY == mCurY) { - mPath.lineTo(mCurX, mCurY + 2); - mPath.lineTo(mCurX + 1, mCurY + 2); - mPath.lineTo(mCurX + 1, mCurY); - } - - mPaths.put(mPath, mPaintOptions); - pathsUpdated(); - mPath = new MyPath(); - mPaintOptions = new PaintOptions(mPaintOptions.getColor(), mPaintOptions.getStrokeWidth()); - } - - private void pathsUpdated() { - if (mListener != null && mPaths != null) { - mListener.pathsChanged(mPaths.size()); - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - final float x = event.getX(); - final float y = event.getY(); - - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mStartX = x; - mStartY = y; - actionDown(x, y); - break; - case MotionEvent.ACTION_MOVE: - actionMove(x, y); - break; - case MotionEvent.ACTION_UP: - actionUp(); - break; - default: - break; - } - - invalidate(); - return true; - } - - public interface PathsChangedListener { - void pathsChanged(int cnt); - } - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState savedState = new SavedState(superState); - - savedState.mPaths = mPaths; - return savedState; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - SavedState savedState = (SavedState) state; - super.onRestoreInstanceState(savedState.getSuperState()); - - mPaths = savedState.mPaths; - pathsUpdated(); // This doesn't seem to be necessary - } - - static class SavedState extends BaseSavedState { - Map mPaths; - - SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - out.writeInt(mPaths.size()); - for (Map.Entry entry : mPaths.entrySet()) { - out.writeSerializable(entry.getKey()); - PaintOptions paintOptions = entry.getValue(); - out.writeInt(paintOptions.getColor()); - out.writeFloat(paintOptions.getStrokeWidth()); - } - } - - public static final Parcelable.Creator CREATOR = - new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - - private SavedState(Parcel in) { - super(in); - int size = in.readInt(); - for (int i = 0; i < size; i++) { - MyPath key = (MyPath) in.readSerializable(); - PaintOptions paintOptions = new PaintOptions(in.readInt(), in.readFloat()); - mPaths.put(key, paintOptions); - } - } - } -} diff --git a/app/src/main/java/com/simplemobiletools/draw/MyCanvas.kt b/app/src/main/java/com/simplemobiletools/draw/MyCanvas.kt new file mode 100644 index 0000000..aa6553d --- /dev/null +++ b/app/src/main/java/com/simplemobiletools/draw/MyCanvas.kt @@ -0,0 +1,257 @@ +package com.simplemobiletools.draw + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.os.Parcel +import android.os.Parcelable +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import java.util.* + +class MyCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) { + private val mPaint: Paint + private var mPath: MyPath? = null + private var mPaths: MutableMap? = null + private var mListener: PathsChangedListener? = null + + private var mPaintOptions: PaintOptions? = null + private var mCurX = 0f + private var mCurY = 0f + private var mStartX = 0f + private var mStartY = 0f + private var mIsSaving = false + private var mIsStrokeWidthBarEnabled = false + + init { + + mPath = MyPath() + mPaint = Paint() + mPaintOptions = PaintOptions() + mPaint.color = mPaintOptions!!.color + mPaint.style = Paint.Style.STROKE + mPaint.strokeJoin = Paint.Join.ROUND + mPaint.strokeCap = Paint.Cap.ROUND + mPaint.strokeWidth = mPaintOptions!!.strokeWidth + mPaint.isAntiAlias = true + + mPaths = LinkedHashMap() + mPaths!!.put(mPath!!, mPaintOptions!!) + pathsUpdated() + } + + fun setListener(listener: PathsChangedListener) { + this.mListener = listener + } + + fun undo() { + if (mPaths!!.isEmpty()) + return + + var lastKey: MyPath? = null + for (key in mPaths!!.keys) { + lastKey = key + } + + mPaths!!.remove(lastKey) + pathsUpdated() + invalidate() + } + + fun setColor(newColor: Int) { + mPaintOptions!!.color = newColor + if (mIsStrokeWidthBarEnabled) { + invalidate() + } + } + + fun setStrokeWidth(newStrokeWidth: Float) { + mPaintOptions!!.strokeWidth = newStrokeWidth + if (mIsStrokeWidthBarEnabled) { + invalidate() + } + } + + fun setIsStrokeWidthBarEnabled(isStrokeWidthBarEnabled: Boolean) { + mIsStrokeWidthBarEnabled = isStrokeWidthBarEnabled + invalidate() + } + + val bitmap: Bitmap + get() { + val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + canvas.drawColor(Color.WHITE) + mIsSaving = true + draw(canvas) + mIsSaving = false + return bitmap + } + + val paths: Map + get() = mPaths!! + + fun addPath(path: MyPath, options: PaintOptions) { + mPaths!!.put(path, options) + pathsUpdated() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + for ((key, value) in mPaths!!) { + changePaint(value) + canvas.drawPath(key, mPaint) + } + + changePaint(mPaintOptions!!) + canvas.drawPath(mPath!!, mPaint) + + if (mIsStrokeWidthBarEnabled && !mIsSaving) { + drawPreviewCircle(canvas) + } + } + + private fun drawPreviewCircle(canvas: Canvas) { + val res = resources + mPaint.style = Paint.Style.FILL + + var y = height - res.getDimension(R.dimen.preview_dot_offset_y) + canvas.drawCircle((width / 2).toFloat(), y, mPaintOptions!!.strokeWidth / 2, mPaint) + mPaint.style = Paint.Style.STROKE + mPaint.color = if (Utils.shouldUseWhite(mPaintOptions!!.color)) Color.WHITE else Color.BLACK + mPaint.strokeWidth = res.getDimension(R.dimen.preview_dot_stroke_size) + + y = height - res.getDimension(R.dimen.preview_dot_offset_y) + val radius = (mPaintOptions!!.strokeWidth + res.getDimension(R.dimen.preview_dot_stroke_size)) / 2 + canvas.drawCircle((width / 2).toFloat(), y, radius, mPaint) + changePaint(mPaintOptions!!) + } + + private fun changePaint(paintOptions: PaintOptions) { + mPaint.color = paintOptions.color + mPaint.strokeWidth = paintOptions.strokeWidth + } + + fun clearCanvas() { + mPath!!.reset() + mPaths!!.clear() + pathsUpdated() + invalidate() + } + + private fun actionDown(x: Float, y: Float) { + mPath!!.reset() + mPath!!.moveTo(x, y) + mCurX = x + mCurY = y + } + + private fun actionMove(x: Float, y: Float) { + mPath!!.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2) + mCurX = x + mCurY = y + } + + private fun actionUp() { + mPath!!.lineTo(mCurX, mCurY) + + // draw a dot on click + if (mStartX == mCurX && mStartY == mCurY) { + mPath!!.lineTo(mCurX, mCurY + 2) + mPath!!.lineTo(mCurX + 1, mCurY + 2) + mPath!!.lineTo(mCurX + 1, mCurY) + } + + mPaths!!.put(mPath!!, mPaintOptions!!) + pathsUpdated() + mPath = MyPath() + mPaintOptions = PaintOptions(mPaintOptions!!.color, mPaintOptions!!.strokeWidth) + } + + private fun pathsUpdated() { + if (mListener != null && mPaths != null) { + mListener!!.pathsChanged(mPaths!!.size) + } + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + val x = event.x + val y = event.y + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + mStartX = x + mStartY = y + actionDown(x, y) + } + MotionEvent.ACTION_MOVE -> actionMove(x, y) + MotionEvent.ACTION_UP -> actionUp() + else -> { + } + } + + invalidate() + return true + } + + interface PathsChangedListener { + fun pathsChanged(cnt: Int) + } + + public override fun onSaveInstanceState(): Parcelable { + val superState = super.onSaveInstanceState() + val savedState = SavedState(superState) + + savedState.mPaths = mPaths + return savedState + } + + public override fun onRestoreInstanceState(state: Parcelable) { + if (state !is SavedState) { + super.onRestoreInstanceState(state) + return + } + val savedState = state + super.onRestoreInstanceState(savedState.superState) + + mPaths = savedState.mPaths + pathsUpdated() // This doesn't seem to be necessary + } + + internal class SavedState : View.BaseSavedState { + var mPaths: MutableMap? = null + + constructor(superState: Parcelable) : super(superState) + + override fun writeToParcel(out: Parcel, flags: Int) { + super.writeToParcel(out, flags) + out.writeInt(mPaths!!.size) + for ((key, paintOptions) in mPaths!!) { + out.writeSerializable(key) + out.writeInt(paintOptions.color) + out.writeFloat(paintOptions.strokeWidth) + } + } + + private constructor(parcel: Parcel) : super(parcel) { + val size = parcel.readInt() + for (i in 0..size - 1) { + val key = parcel.readSerializable() as MyPath + val paintOptions = PaintOptions(parcel.readInt(), parcel.readFloat()) + mPaths!!.put(key, paintOptions) + } + } + + companion object { + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun newArray(size: Int): Array = arrayOf() + + override fun createFromParcel(source: Parcel) = SavedState(source) + } + } + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/draw/MyPath.kt b/app/src/main/kotlin/com/simplemobiletools/draw/MyPath.kt index 007f626..9e96920 100644 --- a/app/src/main/kotlin/com/simplemobiletools/draw/MyPath.kt +++ b/app/src/main/kotlin/com/simplemobiletools/draw/MyPath.kt @@ -12,7 +12,7 @@ import java.security.InvalidParameterException import java.util.* // https://stackoverflow.com/a/8127953 -internal class MyPath : Path(), Serializable { +class MyPath : Path(), Serializable { private val actions = LinkedList() diff --git a/app/src/main/kotlin/com/simplemobiletools/draw/PaintOptions.kt b/app/src/main/kotlin/com/simplemobiletools/draw/PaintOptions.kt index d8bc5a1..935b20b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/draw/PaintOptions.kt +++ b/app/src/main/kotlin/com/simplemobiletools/draw/PaintOptions.kt @@ -2,7 +2,7 @@ package com.simplemobiletools.draw import android.graphics.Color -internal class PaintOptions { +class PaintOptions { var color = Color.BLACK var strokeWidth = 5f