Skip to content

Commit

Permalink
Implemented save as cropped image
Browse files Browse the repository at this point in the history
  • Loading branch information
k3b committed Apr 15, 2019
1 parent bb292e6 commit 8c20d99
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 15 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
llCrop allows you to either [remove unwanted outer areas from a jpg-photo](https://en.wikipedia.org/wiki/Cropping_(image))
or to create zoom-ins.

Just load a jpeg photo, select a rectangle and save the rectangle as a new photo-file.

While there are many apps that can crop images (and may have many more features) these apps cause quality-losses caused by
jpg-re-encoding.

llCrop ("ll" stands for loss-less) can do cropping without quality-losses because it crops in the raw jpg-photo-data without
the need for jpg-image-re-encoding.

4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ android {
compileSdkVersion 28
defaultConfig {
applicationId "de.k3b.android.lossless_jpg_crop"
minSdkVersion 15

// SAF ACTION_CREATE_DOCUMENT requires api-19 and later
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
android:supportsRtl="true"

android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<activity android:name=".CropAreasChooseActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,35 @@
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.provider.DocumentFile;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

import net.realify.lib.androidimagecropper.CropImageView;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class MainActivity extends BaseActivity {
public class CropAreasChooseActivity extends BaseActivity {
private static final String TAG = "ResultActivity";
private static final int REQUEST_GET_PICTURE = 1;
protected static final int REQUEST_GET_PICTURE_PERMISSION = 101;

private static final int REQUEST_SAVE_PICTURE = 2;
private static final int REQUEST_SAVE_PICTURE_PERMISSION = 102;
private static final String CURRENT_CROP_AREA = "CURRENT_CROP_AREA";

private CropImageView uCropView = null;
private ImageProcessor mSpectrum;

@Override
Expand All @@ -45,7 +48,7 @@ protected void onCreate(Bundle savedInstanceState) {
pickFromGallery();
} else {
try {
CropImageView uCropView = findViewById(R.id.ucrop);
uCropView = findViewById(R.id.ucrop);

/*
InputStream stream = getContentResolver().openInputStream(uri);
Expand All @@ -54,6 +57,12 @@ protected void onCreate(Bundle savedInstanceState) {
uCropView.setImageBitmap(bitmap);
*/
uCropView.setImageUriAsync(uri);

Rect crop = (Rect) ((savedInstanceState == null)
? null
: savedInstanceState.getParcelable(CURRENT_CROP_AREA));

uCropView.setCropRect(crop);
} catch (Exception e) {
Log.e(TAG, "setImageUri", e);
Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
Expand All @@ -62,6 +71,14 @@ protected void onCreate(Bundle savedInstanceState) {

}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);

Rect crop = getCropRect();
outState.putParcelable(CURRENT_CROP_AREA, crop);
}

@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
Expand Down Expand Up @@ -99,7 +116,7 @@ private void onGetPictureResult(int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
final Uri selectedUri = data.getData();
if (selectedUri != null) {
Intent intent = new Intent(Intent.ACTION_VIEW, selectedUri, this, MainActivity.class);
Intent intent = new Intent(Intent.ACTION_VIEW, selectedUri, this, CropAreasChooseActivity.class);
this.startActivity(intent);
finish();
return;
Expand All @@ -119,11 +136,10 @@ private void saveCroppedImage() {
} else {
Uri imageUri = getIntent().getData();

CropImageView uCropView = findViewById(R.id.ucrop);

Rect crop = uCropView.getCropRect();
Rect crop = getCropRect();

debug(imageUri, crop);
openOutputUriPicker(REQUEST_SAVE_PICTURE);
// debug(imageUri, crop);

/**
!!!! mImageView.getCurrentCropImageState() scale must be fixed according to image with/hight/orientation
Expand All @@ -146,6 +162,71 @@ private void saveCroppedImage() {
}
}

private boolean openOutputUriPicker(int folderpickerCode) {
Uri inUri = getIntent().getData();
String originalFileName = (inUri == null) ? "" : inUri.getLastPathSegment();
String proposedFileName = replaceExtension(originalFileName, "_llcrop.jpg");

Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT)
.setType("image/jpeg")
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_TITLE, proposedFileName)
.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
;
startActivityForResult(intent, folderpickerCode);
return true;
}

private void onOpenOutputUriPickerResult(int resultCode, Uri outUri) {

if (resultCode == RESULT_OK) {
final Uri inUri = getIntent().getData();
Rect rect = getCropRect();
InputStream inStream = null;
OutputStream outStream = null;

final String context_message = "Cropping '" + inUri + "'(" + rect + ") => '" + outUri + "'";
Log.d(TAG, context_message);

try {
inStream = getContentResolver().openInputStream(inUri);
outStream = getContentResolver().openOutputStream(outUri, "w");
this.mSpectrum.crop(inStream, outStream, rect, 0);
finish();
return;
} catch (Exception e) {
Log.e(TAG, "Error " + context_message + e.getMessage(), e);
} finally {
close(outStream, outStream);
close(inStream, inStream);
}
}
}

private static void close(Closeable stream, Object source) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.w(TAG, "Error closing " + source, e);
}
}
}

/** replaceExtension("/path/to/image.jpg", ".xmp") becomes "/path/to/image.xmp" */
private static String replaceExtension(String path, String extension) {
if (path == null) return null;
int ext = path.lastIndexOf(".");
return ((ext >= 0) ? path.substring(0, ext) : path) + extension;
}

private Rect getCropRect() {
if (uCropView == null) return null;
return uCropView.getCropRect();
}

private void debug(Uri imageUri, Object currentCropImageState) {
Log.d(TAG, imageUri.getLastPathSegment() + "(" + currentCropImageState + ")");
}
Expand Down Expand Up @@ -179,6 +260,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
onGetPictureResult(resultCode, data);
return;
}
if (requestCode == REQUEST_SAVE_PICTURE) {
onOpenOutputUriPickerResult(resultCode, data.getData());
return;
}

super.onActivityResult(requestCode, resultCode, data);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.k3b.android.lossless_jpg_crop;

import android.content.Context;
import android.graphics.Rect;
import android.util.Log;

import com.facebook.spectrum.Configuration;
Expand All @@ -25,14 +26,18 @@ public static void init(Context context) {
SpectrumSoLoader.init(context);
}

public void ImageProcessor() {
public ImageProcessor() {
mSpectrum = Spectrum.make(
new SpectrumLogcatLogger(Log.INFO),
Configuration.makeEmpty(),
SpectrumPluginJpeg.get()); // JPEG only
// DefaultPlugins.get()); // JPEG, PNG and WebP plugins
}

public void crop(InputStream inputStream, OutputStream outputStream, Rect rect, int degrees) {
crop(inputStream, outputStream, rect.left, rect.top, rect.right, rect.bottom, degrees);
}

public void crop(InputStream inputStream, OutputStream outputStream, int left, int top, int right, int bottom, int degrees) {
final EncodeRequirement encoding =
new EncodeRequirement(EncodedImageFormat.JPEG, 80, EncodeRequirement.Mode.LOSSLESS);
Expand All @@ -45,7 +50,7 @@ public void crop(InputStream inputStream, OutputStream outputStream, int left, i
.cropAbsoluteToOrigin(left, top, right, bottom, false)

// forceUpOrientation=true
.rotate(degrees, false, false, true)
// .rotate(degrees, false, false, true)
.build(),
"my_callsite_identifier");
} catch (Exception ex) {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"
tools:context=".CropAreasChooseActivity"
>

<!--
Expand Down

0 comments on commit 8c20d99

Please sign in to comment.