Skip to content
This repository has been archived by the owner on Jan 7, 2023. It is now read-only.

Enable image rotation and possibility to change output image quality #174

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent result)

private void beginCrop(Uri source) {
Uri destination = Uri.fromFile(new File(getCacheDir(), "cropped"));
Crop.of(source, destination).asSquare().start(this);
Crop.of(source, destination).asSquare().enableRotation().start(this);
}

private void handleCrop(int resultCode, Intent result) {
Expand Down
22 changes: 22 additions & 0 deletions lib/src/main/java/com/soundcloud/android/crop/Crop.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ interface Extra {
String ASPECT_Y = "aspect_y";
String MAX_X = "max_x";
String MAX_Y = "max_y";
String ROTATION = "rotation";
String JPG_QUALITY = "jpg_quality";
String ERROR = "error";
}

Expand Down Expand Up @@ -79,6 +81,26 @@ public Crop withMaxSize(int width, int height) {
return this;
}

/**
* Enable rotation*
*/
public Crop enableRotation() {
cropIntent.putExtra(Extra.ROTATION, true);
return this;
}

/**
* Set JPG quality
*
* @param quality JPG Quality (10< q <100)
*/
public Crop withJpgQuality(int quality) {
if ( (quality>=10) || (quality<=100) ) {
cropIntent.putExtra(Extra.JPG_QUALITY, quality);
}
return this;
}

/**
* Send the crop Intent from an Activity
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,21 @@ public class CropImageActivity extends MonitoredActivity {
private CropImageView imageView;
private HighlightView cropView;

private int rotationInternal = 0;


@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setupWindowFlags();
setupViews();

if ( icicle == null ) {
rotationInternal = 0;
} else {
rotationInternal = icicle.getInt( "rotationInternal", 0 );
}

loadInput();
if (rotateBitmap == null) {
finish();
Expand All @@ -81,6 +90,12 @@ public void onCreate(Bundle icicle) {
startCrop();
}

@Override
public void onSaveInstanceState( Bundle outState ) {
super.onSaveInstanceState(outState);
outState.putInt("rotationInternal", rotationInternal);
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private void setupWindowFlags() {
requestWindowFeature(Window.FEATURE_NO_TITLE);
Expand Down Expand Up @@ -114,6 +129,30 @@ public void onClick(View v) {
onSaveClicked();
}
});

findViewById(R.id.btn_rotate_left).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onRotate(270);
}
});

findViewById(R.id.btn_rotate_right).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
onRotate(90);
}
});

Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
if (extras.containsKey(Crop.Extra.ROTATION)) {
boolean enableRotation = extras.getBoolean(Crop.Extra.ROTATION);
if (enableRotation) {
findViewById(R.id.btn_rotate_left).setVisibility(View.VISIBLE);
findViewById(R.id.btn_rotate_right).setVisibility(View.VISIBLE);
}
}
}
}

private void loadInput() {
Expand All @@ -132,6 +171,9 @@ private void loadInput() {
if (sourceUri != null) {
exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));

exifRotation = (exifRotation + rotationInternal) % 360;
if (exifRotation<0) exifRotation+=360;

InputStream is = null;
try {
sampleSize = calculateBitmapSampleSize(sourceUri);
Expand Down Expand Up @@ -187,11 +229,15 @@ private int getMaxTextureSize() {
}

private void startCrop() {
startCrop(getResources().getString(R.string.crop__wait));
}

private void startCrop( String message ) {
if (isFinishing()) {
return;
}
imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
CropUtil.startBackgroundJob(this, null, message,
new Runnable() {
public void run() {
final CountDownLatch latch = new CountDownLatch(1);
Expand Down Expand Up @@ -262,6 +308,25 @@ public void run() {
}
}


private void onRotate( int angle ) {
if ( (angle==0) || (angle==90) || (angle==180) || (angle==270) ) {
rotationInternal = (rotationInternal + angle) % 360;
if (rotationInternal<0) rotationInternal += 360;
} else {
return;
}

imageView.reset();
loadInput();
if (rotateBitmap == null) {
finish();
return;
}
startCrop(null);
}


private void onSaveClicked() {
if (cropView == null || isSaving) {
return;
Expand Down Expand Up @@ -344,9 +409,17 @@ private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {

try {
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
if (rect.width() > outWidth || rect.height() > outHeight) {

int rectW = rect.width();
int rectH = rect.height();
if ( (Math.abs(exifRotation)!=0) && (Math.abs(exifRotation)!=180) ) {
rectW = rect.height();
rectH = rect.width();
}

if (rectW > outWidth || rectH > outHeight) {
Matrix matrix = new Matrix();
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
matrix.postScale((float) outWidth / rectW, (float) outHeight / rectH);
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
}
} catch (IllegalArgumentException e) {
Expand Down Expand Up @@ -376,12 +449,22 @@ private void clearImageView() {
}

private void saveOutput(Bitmap croppedImage) {

int jpgQuality = 90;
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
if (extras.containsKey(Crop.Extra.JPG_QUALITY)) {
jpgQuality = extras.getInt(Crop.Extra.JPG_QUALITY);
}
}

if (saveUri != null) {
OutputStream outputStream = null;
try {
outputStream = getContentResolver().openOutputStream(saveUri);
if (outputStream != null) {
croppedImage.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
croppedImage.compress(Bitmap.CompressFormat.JPEG, jpgQuality, outputStream);
}
} catch (IOException e) {
setResultException(e);
Expand All @@ -390,10 +473,15 @@ private void saveOutput(Bitmap croppedImage) {
CropUtil.closeSilently(outputStream);
}

/*
CropUtil.copyExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
);
*/
CropUtil.saveExifRotation(
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri),
exifRotation );

setResultUri(saveUri);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,10 @@ public void add(HighlightView hv) {
highlightViews.add(hv);
invalidate();
}

public void reset() {
highlightViews.clear();
invalidate();
}

}
45 changes: 39 additions & 6 deletions lib/src/main/java/com/soundcloud/android/crop/CropUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
/*
* Modified from original in AOSP.
*/
class CropUtil {
public class CropUtil {

private static final String SCHEME_FILE = "file";
private static final String SCHEME_CONTENT = "content";
Expand Down Expand Up @@ -87,6 +87,33 @@ public static boolean copyExifRotation(File sourceFile, File destFile) {
}
}

public static boolean saveExifRotation( File destFile, int exifRotation ) {
if (destFile == null) return false;
exifRotation = exifRotation % 360;
if (exifRotation<0) exifRotation+=360;
try {
ExifInterface exifDest = new ExifInterface(destFile.getAbsolutePath());
switch (exifRotation) {
case 90:
exifDest.setAttribute( ExifInterface.TAG_ORIENTATION, "" + ExifInterface.ORIENTATION_ROTATE_90 );
break;
case 180:
exifDest.setAttribute( ExifInterface.TAG_ORIENTATION, "" + ExifInterface.ORIENTATION_ROTATE_180 );
break;
case 270:
exifDest.setAttribute( ExifInterface.TAG_ORIENTATION, "" + ExifInterface.ORIENTATION_ROTATE_270 );
break;
default:
exifDest.setAttribute( ExifInterface.TAG_ORIENTATION, "" + ExifInterface.ORIENTATION_NORMAL );
}
exifDest.saveAttributes();
return true;
} catch (IOException e) {
Log.e("Error saving Exif data", e);
return false;
}
}

@Nullable
public static File getFromMediaUri(Context context, ContentResolver resolver, Uri uri) {
if (uri == null) return null;
Expand Down Expand Up @@ -161,8 +188,10 @@ public static void startBackgroundJob(MonitoredActivity activity,
String title, String message, Runnable job, Handler handler) {
// Make the progress dialog uncancelable, so that we can guarantee
// the thread will be done before the activity getting destroyed
ProgressDialog dialog = ProgressDialog.show(
activity, title, message, true, false);
ProgressDialog dialog = null;
if (null!=message) {
dialog = ProgressDialog.show(activity, title, message, true, false);
}
new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
}

Expand All @@ -175,7 +204,7 @@ private static class BackgroundJob extends MonitoredActivity.LifeCycleAdapter im
private final Runnable cleanupRunner = new Runnable() {
public void run() {
activity.removeLifeCycleListener(BackgroundJob.this);
if (dialog.getWindow() != null) dialog.dismiss();
if ( (null!=dialog) && (dialog.getWindow() != null)) dialog.dismiss();
}
};

Expand Down Expand Up @@ -206,12 +235,16 @@ public void onActivityDestroyed(MonitoredActivity activity) {

@Override
public void onActivityStopped(MonitoredActivity activity) {
dialog.hide();
if (null!=dialog) {
dialog.hide();
}
}

@Override
public void onActivityStarted(MonitoredActivity activity) {
dialog.show();
if (null!=dialog) {
dialog.show();
}
}
}

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion lib/src/main/res/layout/crop__activity_crop.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<include
android:id="@+id/done_cancel_bar"
layout="@layout/crop__layout_done_cancel" />
layout="@layout/crop__layout_done_cancel_rotate" />

<com.soundcloud.android.crop.CropImageView
android:id="@+id/crop_image"
Expand Down
30 changes: 30 additions & 0 deletions lib/src/main/res/layout/crop__layout_done_cancel_rotate.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Crop.DoneCancelBar">

<FrameLayout
android:id="@+id/btn_cancel"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Cancel" />
</FrameLayout>

<FrameLayout
android:id="@+id/btn_rotate_left"
android:visibility="gone"
style="@style/Crop.ActionButtonWrap">
<TextView style="@style/Crop.ActionButtonText.RotateLeft" />
</FrameLayout>

<FrameLayout
android:id="@+id/btn_rotate_right"
android:visibility="gone"
style="@style/Crop.ActionButtonWrap">
<TextView style="@style/Crop.ActionButtonText.RotateRight" />
</FrameLayout>

<FrameLayout
android:id="@+id/btn_done"
style="@style/Crop.ActionButton">
<TextView style="@style/Crop.ActionButtonText.Done" />
</FrameLayout>

</LinearLayout>
10 changes: 10 additions & 0 deletions lib/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">

<string name="crop__saving">Bild speichern…</string>
<string name="crop__wait">Bitte warten…</string>
<string name="crop__pick_error">Kein Bild vorhanden</string>

<string name="crop__done">FERTIG</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ABBRUCH</string>

</resources>
10 changes: 10 additions & 0 deletions lib/src/main/res/values-hu/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<resources xmlns:tools="http://schemas.android.com/tools">

<string name="crop__saving">Kép mentése…</string>
<string name="crop__wait">Kérem várjon…</string>
<string name="crop__pick_error">Nincs megfelelő kép</string>

<string name="crop__done">KÉSZ</string>
<string name="crop__cancel" tools:ignore="ButtonCase">ELVET</string>

</resources>
Loading