From 2b13e2117497fa64e245773efc1991d20167210b Mon Sep 17 00:00:00 2001 From: Jiwan Date: Sun, 2 Apr 2023 23:06:41 -0700 Subject: [PATCH] add support for drawing arrows --- .../photoeditor/PhotoEditorActivity.java | 57 ++--- .../photoeditorsdk/CustomBrushDrawingView.kt | 205 ++++++++++++++++++ .../res/drawable-xxxhdpi/arrow_outward.xml | 10 + .../src/main/res/drawable/arrow_outward.xml | 9 + .../main/res/layout/activity_photo_editor.xml | 153 +++++++------ 5 files changed, 344 insertions(+), 90 deletions(-) create mode 100644 android/src/main/java/com/ahmedadeltito/photoeditorsdk/CustomBrushDrawingView.kt create mode 100644 android/src/main/res/drawable-xxxhdpi/arrow_outward.xml create mode 100644 android/src/main/res/drawable/arrow_outward.xml diff --git a/android/src/main/java/com/ahmedadeltito/photoeditor/PhotoEditorActivity.java b/android/src/main/java/com/ahmedadeltito/photoeditor/PhotoEditorActivity.java index 9bbb79aa..b4cad794 100755 --- a/android/src/main/java/com/ahmedadeltito/photoeditor/PhotoEditorActivity.java +++ b/android/src/main/java/com/ahmedadeltito/photoeditor/PhotoEditorActivity.java @@ -13,7 +13,6 @@ import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.Matrix; -import android.graphics.Paint; import android.graphics.Typeface; import android.net.Uri; import android.os.Build; @@ -22,6 +21,7 @@ import android.os.Environment; import android.provider.DocumentsContract; import android.provider.MediaStore; + import androidx.annotation.NonNull; import androidx.exifinterface.media.ExifInterface; import androidx.core.app.ActivityCompat; @@ -33,11 +33,11 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageView; @@ -50,7 +50,7 @@ import android.view.WindowManager; import com.ahmedadeltito.photoeditor.widget.SlidingUpPanelLayout; -import com.ahmedadeltito.photoeditorsdk.BrushDrawingView; +import com.ahmedadeltito.photoeditorsdk.CustomBrushDrawingView; import com.ahmedadeltito.photoeditorsdk.OnPhotoEditorSDKListener; import com.ahmedadeltito.photoeditorsdk.PhotoEditorSDK; import com.ahmedadeltito.photoeditorsdk.ViewType; @@ -102,7 +102,7 @@ public class PhotoEditorActivity extends AppCompatActivity implements View.OnCli private boolean hideBottomControls = true; private ImageView photoEditImageView; - private BrushDrawingView brushDrawingView; + private CustomBrushDrawingView brushDrawingView; @Override @@ -141,12 +141,13 @@ protected void onCreate(Bundle savedInstanceState) { emojiFont = getFontFromRes(R.raw.emojioneandroid); - brushDrawingView = (BrushDrawingView) findViewById(R.id.drawing_view); + brushDrawingView = (CustomBrushDrawingView) findViewById(R.id.drawing_view); drawingViewColorPickerRecyclerView = (RecyclerView) findViewById(R.id.drawing_view_color_picker_recycler_view); parentImageRelativeLayout = (RelativeLayout) findViewById(R.id.parent_image_rl); ImageView closeTextView = (ImageView) findViewById(R.id.close_tv); ImageView addTextView = (ImageView) findViewById(R.id.add_text_tv); ImageView addPencil = (ImageView) findViewById(R.id.add_pencil_tv); + ImageView arrowPencil = (ImageView) findViewById(R.id.add_arrow_tv); RelativeLayout deleteRelativeLayout = (RelativeLayout) findViewById(R.id.delete_rl); ImageView deleteTextView = (ImageView) findViewById(R.id.delete_tv); TextView addImageEmojiTextView = (TextView) findViewById(R.id.add_image_emoji_tv); @@ -165,8 +166,8 @@ protected void onCreate(Bundle savedInstanceState) { ViewPager pager = (ViewPager) findViewById(R.id.image_emoji_view_pager); PageIndicator indicator = (PageIndicator) findViewById(R.id.image_emoji_indicator); - - // Changing width of an imageview to maintain aspect ratio + + // Changing width of an imageview to maintain aspect ratio // and to fix image perfectly in parent relative layout int width = rotatedBitmap.getWidth(); int height = rotatedBitmap.getHeight(); @@ -236,6 +237,7 @@ public void onPageScrollStateChanged(int state) { addCropTextView.setOnClickListener(this); addTextView.setOnClickListener(this); addPencil.setOnClickListener(this); + arrowPencil.setOnClickListener(this); saveTextView.setOnClickListener(this); doneDrawingTextView.setOnClickListener(this); clearAllTextView.setOnClickListener(this); @@ -274,24 +276,24 @@ public void onFinish() { }.start(); ArrayList hiddenControls = (ArrayList) getIntent().getExtras().getSerializable("hiddenControls"); - for (int i = 0;i < hiddenControls.size();i++) { + for (int i = 0; i < hiddenControls.size(); i++) { if (hiddenControls.get(i).toString().equalsIgnoreCase("text")) { - addTextView.setVisibility(View.INVISIBLE); + addTextView.setVisibility(View.GONE); } if (hiddenControls.get(i).toString().equalsIgnoreCase("clear")) { - clearAllTextView.setVisibility(View.INVISIBLE); + clearAllTextView.setVisibility(View.GONE); } if (hiddenControls.get(i).toString().equalsIgnoreCase("draw")) { - addPencil.setVisibility(View.INVISIBLE); + addPencil.setVisibility(View.GONE); } if (hiddenControls.get(i).toString().equalsIgnoreCase("save")) { - saveTextView.setVisibility(View.INVISIBLE); + saveTextView.setVisibility(View.GONE); } if (hiddenControls.get(i).toString().equalsIgnoreCase("sticker")) { - addImageEmojiTextView.setVisibility(View.INVISIBLE); + addImageEmojiTextView.setVisibility(View.GONE); } if (hiddenControls.get(i).toString().equalsIgnoreCase("crop")) { - addCropTextView.setVisibility(View.INVISIBLE); + addCropTextView.setVisibility(View.GONE); } } } @@ -408,6 +410,11 @@ public void onColorPickerClickListener(int colorCode) { } } + private void updateArrowDrawingView() { + brushDrawingView.setDrawingMode(CustomBrushDrawingView.DrawingMode.Arrow); + updateBrushDrawingView(true); + } + private void returnBackWithSavedImage() { int permissionCheck = PermissionChecker.checkCallingOrSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); @@ -575,11 +582,13 @@ public void onClick(View v) { onBackPressed(); } else if (v.getId() == R.id.add_image_emoji_tv) { mLayout.setPanelState(SlidingUpPanelLayout.PanelState.EXPANDED); - } else if(v.getId() == R.id.add_crop_tv) { + } else if (v.getId() == R.id.add_crop_tv) { System.out.println("CROP IMAGE DUD"); startCropping(); } else if (v.getId() == R.id.add_text_tv) { openAddTextPopupWindow("", -1); + } else if (v.getId() == R.id.add_arrow_tv) { + updateArrowDrawingView(); } else if (v.getId() == R.id.add_pencil_tv) { updateBrushDrawingView(true); } else if (v.getId() == R.id.done_drawing_tv) { @@ -679,26 +688,23 @@ public int getCount() { } } - private Typeface getFontFromRes(int resource) - { + private Typeface getFontFromRes(int resource) { Typeface tf = null; InputStream is = null; try { is = getResources().openRawResource(resource); - } - catch(Resources.NotFoundException e) { + } catch (Resources.NotFoundException e) { Log.e(TAG, "Could not find font in resources!"); } String outPath = getCacheDir() + "/tmp" + System.currentTimeMillis() + ".raw"; - try - { + try { byte[] buffer = new byte[is.available()]; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outPath)); int l = 0; - while((l = is.read(buffer)) > 0) + while ((l = is.read(buffer)) > 0) bos.write(buffer, 0, l); bos.close(); @@ -707,9 +713,7 @@ private Typeface getFontFromRes(int resource) // clean up new File(outPath).delete(); - } - catch (IOException e) - { + } catch (IOException e) { Log.e(TAG, "Error reading in font!"); return null; } @@ -759,7 +763,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final if (resultUri != null) { try { selectedImagePath = resultUri.toString(); - Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver() , resultUri); + Bitmap bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), resultUri); photoEditImageView.setImageBitmap(bitmap); } catch (Exception ex) { System.out.println("NO IMAGE DATA FOUND"); @@ -772,6 +776,7 @@ public void onActivityResult(final int requestCode, final int resultCode, final } } } + @TargetApi(Build.VERSION_CODES.KITKAT) protected String getPath(final Uri uri) { // DocumentProvider diff --git a/android/src/main/java/com/ahmedadeltito/photoeditorsdk/CustomBrushDrawingView.kt b/android/src/main/java/com/ahmedadeltito/photoeditorsdk/CustomBrushDrawingView.kt new file mode 100644 index 00000000..52b44a7d --- /dev/null +++ b/android/src/main/java/com/ahmedadeltito/photoeditorsdk/CustomBrushDrawingView.kt @@ -0,0 +1,205 @@ +package com.ahmedadeltito.photoeditorsdk + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.util.AttributeSet +import android.view.MotionEvent +import kotlin.math.* + +open class CustomBrushDrawingView(context: Context, attributeSet: AttributeSet?, defStyle: Int) : + BrushDrawingView(context, attributeSet, defStyle) { + constructor(context: Context, attributeSet: AttributeSet) : this(context, attributeSet, 0) + constructor(context: Context) : this(context, null, 0) + + + private var drawPath = Path() + private var drawPaint = Paint() + private var canvasPaint = Paint() + + private var drawCanvas: Canvas? = null + private var canvasBitMap: Bitmap? = null + + enum class DrawingMode { + Brush, + Arrow + } + + private var drawingMode: DrawingMode = DrawingMode.Brush + + private var initialPoint: PointF? = null + private var isDrawing = false + private var photoEditorSDKListener: OnPhotoEditorSDKListener? = null + + override fun setupBrushDrawing() { + drawPaint = Paint() + drawPath = Path() + refreshArrowDrawing() + canvasPaint = Paint(Paint.DITHER_FLAG) + super.setupBrushDrawing() + } + + private fun refreshArrowDrawing() { + val paintObject = drawPaint + paintObject.isAntiAlias = true + paintObject.isDither = true + paintObject.style = Paint.Style.STROKE + paintObject.strokeJoin = Paint.Join.ROUND + paintObject.strokeCap = Paint.Cap.ROUND + paintObject.strokeWidth = brushSize + paintObject.xfermode = PorterDuffXfermode(PorterDuff.Mode.DARKEN) + initialPoint = null + } + + override fun setBrushColor(color: Int) { + super.setBrushColor(color) + refreshArrowDrawing() + drawPaint.color = brushColor + } + + override fun setBrushSize(size: Float) { + super.setBrushSize(size) + refreshArrowDrawing() + } + + fun setDrawingMode(drawingMode: DrawingMode) { + this.drawingMode = drawingMode + when (drawingMode) { + DrawingMode.Brush -> { + drawPaint.style = Paint.Style.STROKE + } + DrawingMode.Arrow -> { + drawPaint.style = Paint.Style.FILL_AND_STROKE + } + } + } + + override fun setBrushDrawingMode(isDrawing: Boolean) { + this.isDrawing = isDrawing + if (isDrawing) { + visibility = VISIBLE + refreshArrowDrawing() + } else { + setDrawingMode(DrawingMode.Brush) + } + } + + override fun clearAll() { + val c = drawCanvas ?: return + c.drawColor(0, PorterDuff.Mode.CLEAR) + invalidate() + } + + override fun onDraw(canvas: Canvas?) { + drawBitmapInCanvas(canvas, canvasBitMap, canvasPaint) + drawDrawPathInCanvas(canvas, drawPath, drawPaint) + } + + override fun setOnPhotoEditorSDKListener(onPhotoEditorSDKListener: OnPhotoEditorSDKListener?) { + this.photoEditorSDKListener = onPhotoEditorSDKListener + } + + + private fun drawBitmapInCanvas( + canvas: Canvas?, + bitmap: Bitmap?, + paint: Paint? + ) { + val c = canvas ?: return + val b = bitmap ?: return + val p = paint ?: return + c.drawBitmap(b, 0f, 0f, p) + } + + private fun drawDrawPathInCanvas( + canvas: Canvas?, + path: Path, + paint: Paint + ) { + canvas ?: return + canvas.drawPath(path, paint) + } + + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) + canvasBitMap = bitmap + drawCanvas = Canvas(bitmap) + } + + private fun drawArrowInPath( + path: Path, + startPoint: PointF?, + endPoint: PointF?, + headLength: Float = 20.0f + ) { + startPoint ?: return + endPoint ?: return + val startX = startPoint.x + val startY = startPoint.y + val endX = endPoint.x + val endY = endPoint.y + val angle = atan2(endY - startY, endX - startX) + path.reset() + path.moveTo(startX, startY) + path.lineTo(endX, endY) + val angleOffset = Math.PI / 6 + path.lineTo( + (endX - (headLength * cos(angle + angleOffset))).toFloat(), + (endY - (headLength * sin(angle + angleOffset))).toFloat() + ) + path.lineTo( + (endX - (headLength * cos(angle - angleOffset))).toFloat(), + (endY - (headLength * sin(angle - angleOffset))).toFloat() + ) + path.lineTo(endX, endY) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!isDrawing) { + return false + } + val x = event.x + val y = event.y + when (event.action) { + MotionEvent.ACTION_DOWN -> { + when (drawingMode) { + DrawingMode.Brush -> { + drawPath.moveTo(x, y) + } + DrawingMode.Arrow -> { + initialPoint = PointF(x, y) + } + } + photoEditorSDKListener?.onStartViewChangeListener(ViewType.BRUSH_DRAWING) + } + MotionEvent.ACTION_MOVE -> { + when (drawingMode) { + DrawingMode.Brush -> { + drawPath.lineTo(x, y) + } + DrawingMode.Arrow -> { + drawArrowInPath(drawPath, initialPoint, PointF(x, y)) + } + } + invalidate() + } + MotionEvent.ACTION_UP -> { + when (drawingMode) { + DrawingMode.Brush -> { + drawDrawPathInCanvas(drawCanvas, drawPath, drawPaint) + } + DrawingMode.Arrow -> { + drawDrawPathInCanvas(drawCanvas, drawPath, drawPaint) + } + } + drawPath.reset() + photoEditorSDKListener?.onStartViewChangeListener(ViewType.BRUSH_DRAWING) + invalidate() + } + } + return true + } +} + diff --git a/android/src/main/res/drawable-xxxhdpi/arrow_outward.xml b/android/src/main/res/drawable-xxxhdpi/arrow_outward.xml new file mode 100644 index 00000000..4d910b8f --- /dev/null +++ b/android/src/main/res/drawable-xxxhdpi/arrow_outward.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/src/main/res/drawable/arrow_outward.xml b/android/src/main/res/drawable/arrow_outward.xml new file mode 100644 index 00000000..fa0b1186 --- /dev/null +++ b/android/src/main/res/drawable/arrow_outward.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/src/main/res/layout/activity_photo_editor.xml b/android/src/main/res/layout/activity_photo_editor.xml index c08d847e..79315d3f 100644 --- a/android/src/main/res/layout/activity_photo_editor.xml +++ b/android/src/main/res/layout/activity_photo_editor.xml @@ -25,31 +25,33 @@ + + android:layout_centerInParent="true" + android:adjustViewBounds="true"> + - + + @@ -61,14 +63,19 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" - android:layout_marginEnd="20dp" android:layout_marginStart="20dp" - android:layout_marginTop="10dp" - android:padding="10dp" + android:layout_marginEnd="20dp" + android:paddingHorizontal="10dp" android:text="@string/done" android:textColor="#FFFFFF" android:textSize="20sp" - android:visibility="gone" /> + android:layout_alignBottom="@id/top_shadow" + android:layout_alignTop="@id/top_shadow" + android:visibility="gone" + tools:visibility="visible" + android:textAlignment="center" + android:gravity="center" + /> @@ -106,26 +114,38 @@ + + + app:tint="#FFFFFF" /> @@ -134,41 +154,45 @@ android:id="@+id/add_crop_tv" android:layout_width="40dp" android:layout_height="40dp" - android:scaleType="fitXY" - android:layout_marginTop="5dp" - android:layout_marginStart="3dp" - android:layout_marginEnd="3dp" + android:layout_centerVertical="true" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" android:layout_toStartOf="@+id/add_pencil_tv" + android:scaleType="fitXY" android:src="@drawable/crop" android:textColor="#FFFFFF" android:textSize="20sp" /> + + - - + + @@ -191,30 +215,31 @@ android:id="@+id/save_tv" android:layout_width="48dp" android:layout_height="48dp" - android:scaleType="fitXY" - android:paddingTop="5dp" android:layout_alignParentStart="true" android:layout_centerVertical="true" android:gravity="center" + android:paddingTop="5dp" + android:scaleType="fitXY" android:src="@drawable/download" android:textColor="#FFFFFF" android:textSize="25sp" /> +