diff options
author | Thomas <tschneider.ac@gmail.com> | 2022-07-05 19:03:05 +0200 |
---|---|---|
committer | Thomas <tschneider.ac@gmail.com> | 2022-07-05 19:03:05 +0200 |
commit | 965676484ab181330385784ab61b3388de8849bc (patch) | |
tree | 500ea0d301f88de605ed5644354a54b0b4c72b32 | |
parent | 6219c010d00f0c4b4ca9945adf5c64b43bca48c7 (diff) |
Fix issue #225 - Set Focus on images
9 files changed, 335 insertions, 9 deletions
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 0ddda7578..58c28c852 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 3359d1a20..3cd5e3c08 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 000000000..33672f60c --- /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<CircleArea> mCircles = new HashSet<>(CIRCLES_LIMIT); + private final SparseArray<CircleArea> 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 a9c68c7a5..81e650865 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 07c3ceb7e..5415c24cf 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<EditingToolsAdapte mToolList.add(new ToolModel("Eraser", R.drawable.ic_eraser, ToolType.ERASER)); mToolList.add(new ToolModel("Filter", R.drawable.ic_photo_filter, ToolType.FILTER)); mToolList.add(new ToolModel("Emoji", R.drawable.ic_insert_emoticon, ToolType.EMOJI)); + mToolList.add(new ToolModel("Focus", R.drawable.ic_baseline_filter_center_focus_24, ToolType.FOCUS)); } @NonNull diff --git a/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java b/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java index 5a48bb039..1f5a2b5cc 100644 --- a/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java +++ b/app/src/main/java/app/fedilab/android/imageeditor/tools/ToolType.java @@ -13,5 +13,6 @@ public enum ToolType { FILTER, EMOJI, STICKER, - CROP + CROP, + FOCUS } diff --git a/app/src/main/java/app/fedilab/android/services/PostMessageService.java b/app/src/main/java/app/fedilab/android/services/PostMessageService.java index 736e94dbf..a4a73ab20 100644 --- a/app/src/main/java/app/fedilab/android/services/PostMessageService.java +++ b/app/src/main/java/app/fedilab/android/services/PostMessageService.java @@ -294,7 +294,8 @@ public class PostMessageService extends IntentService { } private static String postAttachment(MastodonStatusesService mastodonStatusesService, DataPost dataPost, MultipartBody.Part fileMultipartBody, Attachment attachment) { - Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, attachment.description, null); + + Call<Attachment> 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 000000000..757e14331 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_filter_center_focus_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="#FFFFFF" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="@android:color/white" + android:pathData="M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z" /> +</vector> diff --git a/app/src/main/res/layout/activity_edit_image.xml b/app/src/main/res/layout/activity_edit_image.xml index b671da595..8e04a7667 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" /> - <ja.burhanrashid52.photoeditor.PhotoEditorView - android:id="@+id/photoEditorView" + <RelativeLayout android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/rvConstraintTools" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" - app:layout_constraintTop_toTopOf="parent" /> + android:id="@+id/container_image" + app:layout_constraintTop_toTopOf="parent"> + + <ja.burhanrashid52.photoeditor.PhotoEditorView + android:id="@+id/photoEditorView" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <app.fedilab.android.helper.CirclesDrawingView + android:id="@+id/focus_circle" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" /> + </RelativeLayout> + <ImageView android:id="@+id/imgUndo" @@ -34,7 +47,6 @@ android:src="@drawable/ic_undo" app:layout_constraintBottom_toTopOf="@+id/rvConstraintTools" app:layout_constraintEnd_toStartOf="@+id/imgRedo" /> - <ImageView android:id="@+id/imgRedo" android:layout_width="@dimen/top_tool_icon_width" |