Skip to content

Commit

Permalink
#15: Define crop box size or aspect ratio
Browse files Browse the repository at this point in the history
  • Loading branch information
k3b committed Sep 22, 2022
1 parent 2a6db3c commit d86f668
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 33 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ features (e.g. support for other file formats, add resize-support or adding text
* Workflow [#3](https://github.com/k3b/LosslessJpgCrop/issues/3)/[#8](https://github.com/k3b/LosslessJpgCrop/issues/8) : From any app that supports [intent-action-GET-CONTENT](https://developer.android.com/reference/android/content/Intent#ACTION_GET_CONTENT) or intent-action-PICK for MIME *image/jpeg*
* Open/Pick the cropping of an uncropped image
* Feature [#17](https://github.com/k3b/LosslessJpgCrop/issues/17) : added support for image rotation
* Feature [#35](https://github.com/k3b/LosslessJpgCrop/issues/35) : Display crop box coordinates and size
* Show XY offset of of top left corner of crop box displayed along with it's dimensions.
* Feature [#35](https://github.com/k3b/LosslessJpgCrop/issues/35) : Display current crop box coordinates and size
* Show XY offset of top left corner of crop box displayed along with it's dimensions.
* One can get more predictable results by sticking to 8 or 16 multiples for offset and box size allows to target aspect ratio.
* Feature [#15](https://github.com/k3b/LosslessJpgCrop/issues/15) : Define crop box size or aspect ratio
* if you set width and height to a value below 100 then you define the aspect ratio of the cropping result
* if you set width and height to a value above 100 then you define the absolute size in pixel of the cropping result.

---

Expand Down
7 changes: 7 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
* > Feature [#35](https://github.com/k3b/LosslessJpgCrop/issues/35) : Display current crop box coordinates and size
* v Show XY offset of top left corner of crop box displayed along with it's dimensions.
* > snap to multible of 8
* > Feature [#15](https://github.com/k3b/LosslessJpgCrop/issues/15) : Define crop box size or aspect ratio
* v if you set width and height to a value below 100 then you define the aspect ratio of the cropping result
* v if you set width and height to a value above 100 then you define the absolute size in pixel of the cropping result.
* > menu to define free/square/predefined/userdefined
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.TextView;
import android.widget.Toast;

Expand Down Expand Up @@ -45,14 +46,29 @@
*/
abstract class CropAreasChooseBaseActivity extends BaseActivity {
protected static final String TAG = "LLCrop";

private static final String STATE_CURRENT_CROP_AREA = "CURRENT_CROP_AREA";
private static final String STATE_CURRENT_ASPECT_RATIO = "KEY_ASPECT_RATIO";

protected static final boolean LOAD_ASYNC = false;
private static final boolean ENABLE_ASPECT_RATIO = false;

private static int lastInstanceNo4Debug = 0;
private int instanceNo4Debug = 0;

private final int idMenuMainMethod;
private final Map<Integer, Integer> menu2Rotation;

protected static final String IMAGE_JPEG_MIME = "image/jpeg";

protected CropImageView uCropView = null;
protected TextView txtStatus = null;
private ImageProcessor mSpectrum;
private String aspectRatio = null;

// #7: workaround rotation change while picker is open causes Activity re-create without
// uCropView recreation completed.
private Rect mLastCropRect = null;
protected CropAreasChooseBaseActivity(int idMenuMainMethod) {
this.idMenuMainMethod = idMenuMainMethod;
menu2Rotation = new HashMap<>();
Expand Down Expand Up @@ -199,16 +215,16 @@ protected void onSaveEditPictureAsOutputUriPickerResult(Uri _outUri) {
close(inStream, inStream);
}
} else {
// uri==null or error
// outUri==null or error
Log.i(TAG, getInstanceNo4Debug() + "onOpenPublicOutputUriPickerResult(null): No output url, not saved.");
}
}
}
protected Edit edit = new Edit();

protected class Send {
protected void onGetSendImage(Uri uri, Bundle savedInstanceState) {
SetImageUriAndLastCropArea(uri, savedInstanceState);
protected void onGetSendImage(Uri imageUri, Bundle savedInstanceState) {
SetImageUriAndLastCropArea(imageUri, savedInstanceState);
}

protected boolean sendPrivateCroppedImage() {
Expand Down Expand Up @@ -268,17 +284,6 @@ private void copyExtra(Intent outIntent, Bundle extras, String... extraIds) {
}
protected Send send = new Send();

private static final String CURRENT_CROP_AREA = "CURRENT_CROP_AREA";
protected static final String IMAGE_JPEG_MIME = "image/jpeg";

protected CropImageView uCropView = null;
protected TextView txtStatus = null;
private ImageProcessor mSpectrum;

// #7: workaround rotation change while picker is open causes Activity re-create without
// uCropView recreation completed.
private Rect mLastCropRect = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
instanceNo4Debug = ++lastInstanceNo4Debug;
Expand All @@ -287,14 +292,45 @@ protected void onCreate(Bundle savedInstanceState) {
uCropView = findViewById(R.id.ucrop);
txtStatus = findViewById(R.id.status);

if (savedInstanceState != null) {
aspectRatio = savedInstanceState.getString(STATE_CURRENT_ASPECT_RATIO, aspectRatio);
}
mSpectrum = new ImageProcessor();

uCropView.setOnSetCropOverlayMovedListener(new CropImageView.OnSetCropOverlayMovedListener() {
@Override
public void onCropOverlayMoved(Rect rect) {
onUpdateCropping();
}
});
mSpectrum = new ImageProcessor();

setAspectRatio(aspectRatio);
}

private void setAspectRatio(String aspectRatio) {
String[] xy = (aspectRatio == null) ? null : aspectRatio.split("x");

if (xy == null) {
uCropView.clearAspectRatio();
} else {
try {
int x = Integer.parseInt(xy[0]);
int y = Integer.parseInt(xy[1]);
uCropView.setAspectRatio(x, y);
if (x >= 100 && y >= 100) {
uCropView.setMinCropResultSize(x,y);
uCropView.setMaxCropResultSize(x,y);
} else {
uCropView.setMinCropResultSize(40,99999);
uCropView.setMaxCropResultSize(40,99999);
}
} catch (Exception ex) {
String message = "setAspectRatio('" + aspectRatio + "') . Valid example '7x13'";
Log.e(TAG, getInstanceNo4Debug() + message);

// throw new IllegalArgumentException(message, ex);
}
}
}

private void onUpdateCropping() {
Expand All @@ -319,22 +355,22 @@ protected void finishIfMainMethod(int idMenuMainMethod) {
}
}

protected void SetImageUriAndLastCropArea(Uri uri, Bundle savedInstanceState) {
protected void SetImageUriAndLastCropArea(Uri imageUri, Bundle savedInstanceState) {
final Rect crop = (Rect) ((savedInstanceState == null)
? null
: savedInstanceState.getParcelable(CURRENT_CROP_AREA));
: savedInstanceState.getParcelable(STATE_CURRENT_CROP_AREA));

SetImageUriAndLastCropArea(uri, crop);
SetImageUriAndLastCropArea(imageUri, crop);
}

protected void SetImageUriAndLastCropArea(Uri uri, Rect crop) {
protected void SetImageUriAndLastCropArea(Uri imageUri, Rect crop) {
try {
if (LOAD_ASYNC) {
uCropView.setImageUriAsync(uri);
uCropView.setImageUriAsync(imageUri);
} else {
InputStream stream = getContentResolver().openInputStream(uri);
InputStream stream = getContentResolver().openInputStream(imageUri);
Bitmap bitmap = BitmapFactory.decodeStream(stream);
ExifInterface exif = getExif(this, uri);
ExifInterface exif = getExif(this, imageUri);
uCropView.setImageBitmap(bitmap, exif);
if (exif != null) {
setBaseRotation(exif.getRotationDegrees());
Expand All @@ -344,16 +380,16 @@ protected void SetImageUriAndLastCropArea(Uri uri, Rect crop) {


} catch (Exception e) {
final String msg = getInstanceNo4Debug() + "SetImageUriAndLastCropArea '" + uri + "' ";
final String msg = getInstanceNo4Debug() + "SetImageUriAndLastCropArea '" + imageUri + "' ";
Log.e(TAG, msg, e);
Toast.makeText(this, msg + e.getMessage(), Toast.LENGTH_LONG).show();
}
}

private static ExifInterface getExif(Context context, Uri uri) {
private static ExifInterface getExif(Context context, Uri imageUri) {
InputStream is = null;
try {
is = context.getContentResolver().openInputStream(uri);
is = context.getContentResolver().openInputStream(imageUri);
if (is != null) {
ExifInterface ei = new ExifInterface(is);
is.close();
Expand Down Expand Up @@ -392,7 +428,7 @@ private void setCropRect(final Rect crop) {
if (LOAD_ASYNC) {
uCropView.setOnSetImageUriCompleteListener(new CropImageView.OnSetImageUriCompleteListener() {
@Override
public void onSetImageUriComplete(CropImageView view, Uri uri, Exception error) {
public void onSetImageUriComplete(CropImageView view, Uri imageUri, Exception error) {
// called when uCropView recreation is completed.
uCropView.setCropRect(crop);
Rect newCrop = getCropRect();
Expand Down Expand Up @@ -428,7 +464,9 @@ protected void onSaveInstanceState(Bundle outState) {

Rect crop = getCropRect();
Log.d(TAG, getInstanceNo4Debug() + "onSaveInstanceState : crop=" + crop);
outState.putParcelable(CURRENT_CROP_AREA, crop);
outState.putParcelable(STATE_CURRENT_CROP_AREA, crop);
outState.putString(STATE_CURRENT_ASPECT_RATIO, aspectRatio);

}

private void pickFromGallery(int requestId, int requestPermissionId) {
Expand Down Expand Up @@ -460,7 +498,7 @@ protected String asString(Uri outUri) {
return URLDecoder.decode(outUri.toString(), StandardCharsets.UTF_8.toString());
} catch (Exception e) {
//!!! UnsupportedEncodingException, IllegalCharsetNameException
Log.e(TAG, getInstanceNo4Debug() + "err cannot convert uri to string('" + outUri.toString() + "').", e);
Log.e(TAG, getInstanceNo4Debug() + "err cannot convert imageUri to string('" + outUri.toString() + "').", e);
return outUri.toString();
}
}
Expand Down Expand Up @@ -596,7 +634,7 @@ protected Uri cropToSharedUri() {
close(inStream, inStream);
}
} else {
Log.e(TAG, getInstanceNo4Debug() + "Error cropToSharedUri(): Missing input uri.");
Log.e(TAG, getInstanceNo4Debug() + "Error cropToSharedUri(): Missing input imageUri.");
}
return outUri;
}
Expand Down Expand Up @@ -626,7 +664,9 @@ public void setRotation(int rotation) {
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_rotate, menu);
// getMenuInflater().inflate(R.menu.menu_aspect_ratio, menu);
if (ENABLE_ASPECT_RATIO) {
getMenuInflater().inflate(R.menu.menu_aspect_ratio, menu);
}

/*
Format 3:4:
Expand All @@ -647,9 +687,22 @@ public boolean onPrepareOptionsMenu(Menu menu) {
menu.findItem(key).setChecked(getRotation() == menu2Rotation.get(key));
}
}
if (ENABLE_ASPECT_RATIO) {
SubMenu menuCrop = menu.findItem(R.menu.menu_aspect_ratio).getSubMenu();
for (int i = menuCrop.size() - 1; i >= 0; i--) {
MenuItem item = menuCrop.getItem(i);
item.setCheckable(true);
item.setChecked(isCheckedAspectRatio(item));
}
}
return super.onPrepareOptionsMenu(menu);
}

private boolean isCheckedAspectRatio(MenuItem item) {
// !!! ENABLE_ASPECT_RATIO
return item.getItemId() == Menu.NONE && item.getTitle() != null;
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
Expand Down
48 changes: 48 additions & 0 deletions app/src/main/res/menu/menu_aspect_ratio.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android" >

<item android:id="@+id/menu_aspect_ratio"

android:showAsAction="ifRoom"
android:icon="@android:drawable/ic_menu_crop"
android:title="Aspect Ratio"
android:orderInCategory="70"
tools:ignore="AppCompatResource" >
<menu>
<item android:id="@+id/menu_ratio_free"
android:showAsAction="never"
android:title="Free"
android:checkable="true"
android:checked="true"
tools:ignore="AppCompatResource" />
<item android:id="@+id/menu_ratio_square"
android:showAsAction="never"
android:title="Square"
android:checkable="true"
tools:ignore="AppCompatResource" />
<item android:id="@+id/menu_ratio_userdefined"
android:showAsAction="never"
android:title="userdefined"
android:checkable="true"
tools:ignore="AppCompatResource" />

<item android:title="9x11" />
<item android:title="9x13" />
<item android:title="9x15" />
<item android:title="10x13" />
<item android:title="10x15" />
<item android:title="13x18" />
<!--
/*
Format 3:4:
9x11,10x13,11x15,13x17,20x27,30x40
Format 2:3:
9x13,10x15,11x17,13x18,20x30,30x45
Format 9:16:
9x15,10x18
*/
-->
</menu>
</item>
</menu>
2 changes: 1 addition & 1 deletion fastlane/metadata/android/en-US/changelogs/10.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
New Feature [#35 Display crop box coordinates and size](https://github.com/k3b/LosslessJpgCrop/issues/35)
?? #15 predefined crop size
??? #15 predefined crop size

0 comments on commit d86f668

Please sign in to comment.