From 965676484ab181330385784ab61b3388de8849bc Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 5 Jul 2022 19:03:05 +0200 Subject: [PATCH] Fix issue #225 - Set Focus on images --- .../android/activities/ComposeActivity.java | 5 + .../client/entities/api/Attachment.java | 1 + .../android/helper/CirclesDrawingView.java | 249 ++++++++++++++++++ .../imageeditor/EditImageActivity.java | 52 +++- .../tools/EditingToolsAdapter.java | 1 + .../android/imageeditor/tools/ToolType.java | 3 +- .../android/services/PostMessageService.java | 3 +- .../ic_baseline_filter_center_focus_24.xml | 10 + .../main/res/layout/activity_edit_image.xml | 20 +- 9 files changed, 335 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/helper/CirclesDrawingView.java create mode 100644 app/src/main/res/drawable/ic_baseline_filter_center_focus_24.xml diff --git a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java index 0ddda757..58c28c85 100644 --- a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java @@ -109,12 +109,17 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana @Override public void onReceive(android.content.Context context, Intent intent) { String imgpath = intent.getStringExtra("imgpath"); + float focusX = intent.getFloatExtra("focusX", -2); + float focusY = intent.getFloatExtra("focusY", -2); if (imgpath != null) { int position = 0; for (Status status : statusList) { if (status.media_attachments != null && status.media_attachments.size() > 0) { for (Attachment attachment : status.media_attachments) { if (attachment.local_path.equalsIgnoreCase(imgpath)) { + if (focusX != -2) { + attachment.focus = focusX + "," + focusY; + } composeAdapter.notifyItemChanged(position); break; } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java index 3359d1a2..3cd5e3c0 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java @@ -48,6 +48,7 @@ public class Attachment implements Serializable { public String peertubeHost = null; public String peertubeId = null; + public String focus = null; public static class Meta implements Serializable { @SerializedName("focus") diff --git a/app/src/main/java/app/fedilab/android/helper/CirclesDrawingView.java b/app/src/main/java/app/fedilab/android/helper/CirclesDrawingView.java new file mode 100644 index 00000000..33672f60 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/CirclesDrawingView.java @@ -0,0 +1,249 @@ +package app.fedilab.android.helper; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.MotionEvent; +import android.view.View; + +import androidx.core.content.res.ResourcesCompat; + +import java.util.HashSet; +import java.util.Random; + +import app.fedilab.android.R; + +//Original work at https://stackoverflow.com/a/17830245 +public class CirclesDrawingView extends View { + + + // Radius limit in pixels + private final static int RADIUS_LIMIT = 100; + private static final int CIRCLES_LIMIT = 1; + private final Random mRadiusGenerator = new Random(); + /** + * All available circles + */ + private final HashSet mCircles = new HashSet<>(CIRCLES_LIMIT); + private final SparseArray mCirclePointer = new SparseArray<>(CIRCLES_LIMIT); + /** + * Paint to draw circles + */ + private Paint mCirclePaint; + private CircleArea touchedCircle; + + /** + * Default constructor + * + * @param ct {@link android.content.Context} + */ + public CirclesDrawingView(final Context ct) { + super(ct); + init(ct); + } + + public CirclesDrawingView(final Context ct, final AttributeSet attrs) { + super(ct, attrs); + init(ct); + } + + public CirclesDrawingView(final Context ct, final AttributeSet attrs, final int defStyle) { + super(ct, attrs, defStyle); + init(ct); + } + + public CircleArea getTouchedCircle() { + return this.touchedCircle; + } + + private void init(final Context ct) { + // Generate bitmap used for background + mCirclePaint = new Paint(); + + mCirclePaint.setColor(ResourcesCompat.getColor(getContext().getResources(), R.color.cyanea_accent, getContext().getTheme())); + mCirclePaint.setStrokeWidth(10); + mCirclePaint.setStyle(Paint.Style.STROKE); + } + + @Override + public void onDraw(final Canvas canv) { + // background bitmap to cover all area + for (CircleArea circle : mCircles) { + canv.drawCircle(circle.centerX, circle.centerY, circle.radius, mCirclePaint); + } + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + boolean handled = false; + + + int xTouch; + int yTouch; + int pointerId; + int actionIndex = event.getActionIndex(); + + // get touch event coordinates and make transparent circle from it + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + // it's the first pointer, so clear all existing pointers data + clearCirclePointer(); + + xTouch = (int) event.getX(0); + yTouch = (int) event.getY(0); + + // check if we've touched inside some circle + touchedCircle = obtainTouchedCircle(xTouch, yTouch); + touchedCircle.centerX = xTouch; + touchedCircle.centerY = yTouch; + mCirclePointer.put(event.getPointerId(0), touchedCircle); + invalidate(); + handled = true; + break; + + case MotionEvent.ACTION_POINTER_DOWN: + // It secondary pointers, so obtain their ids and check circles + pointerId = event.getPointerId(actionIndex); + + xTouch = (int) event.getX(actionIndex); + yTouch = (int) event.getY(actionIndex); + + // check if we've touched inside some circle + touchedCircle = obtainTouchedCircle(xTouch, yTouch); + + mCirclePointer.put(pointerId, touchedCircle); + touchedCircle.centerX = xTouch; + touchedCircle.centerY = yTouch; + invalidate(); + handled = true; + break; + + case MotionEvent.ACTION_MOVE: + final int pointerCount = event.getPointerCount(); + + for (actionIndex = 0; actionIndex < pointerCount; actionIndex++) { + // Some pointer has moved, search it by pointer id + pointerId = event.getPointerId(actionIndex); + + xTouch = (int) event.getX(actionIndex); + yTouch = (int) event.getY(actionIndex); + + touchedCircle = mCirclePointer.get(pointerId); + + if (null != touchedCircle) { + touchedCircle.centerX = xTouch; + touchedCircle.centerY = yTouch; + } + } + invalidate(); + handled = true; + break; + + case MotionEvent.ACTION_UP: + clearCirclePointer(); + invalidate(); + handled = true; + break; + + case MotionEvent.ACTION_POINTER_UP: + // not general pointer was up + pointerId = event.getPointerId(actionIndex); + + mCirclePointer.remove(pointerId); + invalidate(); + handled = true; + break; + + case MotionEvent.ACTION_CANCEL: + handled = true; + break; + + default: + // do nothing + break; + } + + return super.onTouchEvent(event) || handled; + } + + /** + * Clears all CircleArea - pointer id relations + */ + private void clearCirclePointer() { + + mCirclePointer.clear(); + } + + /** + * Search and creates new (if needed) circle based on touch area + * + * @param xTouch int x of touch + * @param yTouch int y of touch + * @return obtained {@link CircleArea} + */ + private CircleArea obtainTouchedCircle(final int xTouch, final int yTouch) { + CircleArea touchedCircle = getTouchedCircle(xTouch, yTouch); + + if (null == touchedCircle) { + touchedCircle = new CircleArea(xTouch, yTouch, mRadiusGenerator.nextInt(RADIUS_LIMIT) + RADIUS_LIMIT); + + if (mCircles.size() == CIRCLES_LIMIT) { + // remove first circle + mCircles.clear(); + } + + mCircles.add(touchedCircle); + } + + return touchedCircle; + } + + /** + * Determines touched circle + * + * @param xTouch int x touch coordinate + * @param yTouch int y touch coordinate + * @return {@link CircleArea} touched circle or null if no circle has been touched + */ + private CircleArea getTouchedCircle(final int xTouch, final int yTouch) { + CircleArea touched = null; + + for (CircleArea circle : mCircles) { + if ((circle.centerX - xTouch) * (circle.centerX - xTouch) + (circle.centerY - yTouch) * (circle.centerY - yTouch) <= circle.radius * circle.radius) { + touched = circle; + break; + } + } + + return touched; + } + + @Override + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight()); + } + + /** + * Stores data about single circle + */ + public static class CircleArea { + public int centerX; + public int centerY; + int radius; + + CircleArea(int centerX, int centerY, int radius) { + this.radius = radius; + this.centerX = centerX; + this.centerY = centerY; + } + + @Override + public String toString() { + return "Circle[" + centerX + ", " + centerY + ", " + radius + "]"; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java index a9c68c7a..81e65086 100644 --- a/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java +++ b/app/src/main/java/app/fedilab/android/imageeditor/EditImageActivity.java @@ -5,6 +5,7 @@ import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; @@ -33,6 +34,7 @@ import java.io.InputStream; import app.fedilab.android.R; import app.fedilab.android.databinding.ActivityEditImageBinding; +import app.fedilab.android.helper.CirclesDrawingView; import app.fedilab.android.helper.Helper; import app.fedilab.android.imageeditor.base.BaseActivity; import app.fedilab.android.imageeditor.filters.FilterListener; @@ -58,7 +60,6 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList private static final int CAMERA_REQUEST = 52; private static final int PICK_REQUEST = 53; private final int STORE_REQUEST = 54; - private final EditingToolsAdapter mEditingToolsAdapter = new EditingToolsAdapter(this); private final FilterViewAdapter mFilterViewAdapter = new FilterViewAdapter(this); private final ConstraintSet mConstraintSet = new ConstraintSet(); @@ -117,8 +118,6 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList binding.rvFilterView.setLayoutManager(llmFilters); binding.rvFilterView.setAdapter(mFilterViewAdapter); - //Typeface mTextRobotoTf = ResourcesCompat.getFont(this, R.font.roboto_medium); - //Typeface mEmojiTypeFace = Typeface.createFromAsset(getAssets(), "emojione-android.ttf"); Typeface mEmojiTypeFace = Typeface.createFromAsset(getAssets(), "emojione-android.ttf"); mPhotoEditor = new PhotoEditor.Builder(this, binding.photoEditorView) @@ -246,6 +245,49 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList if (exit) { Intent intentImage = new Intent(Helper.INTENT_SEND_MODIFIED_IMAGE); intentImage.putExtra("imgpath", imagePath); + CirclesDrawingView.CircleArea circleArea = binding.focusCircle.getTouchedCircle(); + if (circleArea != null) { + //Dimension of the editor containing the image + int pHeight = binding.photoEditorView.getHeight(); + int pWidth = binding.photoEditorView.getWidth(); + //Load the original image in a bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(new File(imagePath).getAbsolutePath(), options); + //Get height and width of the original image + int imageHeight = options.outHeight; + int imageWidth = options.outWidth; + + //Evaluate the dimension of the image in the editor + int imgHeightInEditor; + int imgWidthInEditor; + //If the original image has its height greater than width => heights are equals + float focusX = -2, focusY = -2; + if (imageHeight > imageWidth) { + imgHeightInEditor = pHeight; + float ratio = (float) pHeight / (float) imageHeight; + imgWidthInEditor = (int) (pWidth * ratio); + } else { //Otherwise widths are equals + imgWidthInEditor = pWidth; + float ratio = (float) pWidth / (float) imageWidth; + imgHeightInEditor = (int) (pHeight * ratio); + } + focusY = (float) (circleArea.centerY * 2 - imgHeightInEditor / 2) / (float) imgHeightInEditor - 0.5f; + focusX = (float) (circleArea.centerX * 2 - imgWidthInEditor / 2) / (float) imgWidthInEditor - 0.5f; + if (focusX > 1) { + focusX = 1; + } else if (focusX < -1) { + focusX = -1; + } + if (focusY > 1) { + focusY = 1; + } else if (focusY < -1) { + focusY = -1; + } + intentImage.putExtra("focusX", focusX); + intentImage.putExtra("focusY", focusY); + } + LocalBroadcastManager.getInstance(EditImageActivity.this).sendBroadcast(intentImage); finish(); } @@ -376,6 +418,7 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList @Override public void onToolSelected(ToolType toolType) { + binding.focusCircle.setVisibility(View.GONE); switch (toolType) { case SHAPE: mPhotoEditor.setBrushDrawingMode(true); @@ -414,6 +457,9 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList CropImage.activity(uri) .start(this); break; + case FOCUS: + binding.focusCircle.setVisibility(View.VISIBLE); + break; } } diff --git a/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java b/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java index 07c3ceb7..5415c24c 100644 --- a/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java +++ b/app/src/main/java/app/fedilab/android/imageeditor/tools/EditingToolsAdapter.java @@ -32,6 +32,7 @@ public class EditingToolsAdapter extends RecyclerView.Adapter attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, attachment.description, null); + + Call attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, attachment.description, attachment.focus); if (attachmentCall != null) { try { diff --git a/app/src/main/res/drawable/ic_baseline_filter_center_focus_24.xml b/app/src/main/res/drawable/ic_baseline_filter_center_focus_24.xml new file mode 100644 index 00000000..757e1433 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_filter_center_focus_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_edit_image.xml b/app/src/main/res/layout/activity_edit_image.xml index b671da59..8e04a766 100644 --- a/app/src/main/res/layout/activity_edit_image.xml +++ b/app/src/main/res/layout/activity_edit_image.xml @@ -16,14 +16,27 @@ android:orientation="horizontal" app:layout_constraintGuide_end="?attr/actionBarSize" /> - + android:id="@+id/container_image" + app:layout_constraintTop_toTopOf="parent"> + + + + + + -