mirror of
https://codeberg.org/tom79/Fedilab.git
synced 2025-01-03 14:40:07 +02:00
Fix issue #225 - Set Focus on images
This commit is contained in:
parent
6219c010d0
commit
965676484a
9 changed files with 335 additions and 9 deletions
app/src/main
java/app/fedilab/android
activities
client/entities/api
helper
imageeditor
services
res
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,5 +13,6 @@ public enum ToolType {
|
|||
FILTER,
|
||||
EMOJI,
|
||||
STICKER,
|
||||
CROP
|
||||
CROP,
|
||||
FOCUS
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue