Compare commits
3 Commits
master
...
feature/cr
| Author | SHA1 | Date |
|---|---|---|
|
|
dc7fbe916c | |
|
|
7020bcfe74 | |
|
|
f7ad4594fd |
|
|
@ -1,30 +1,17 @@
|
|||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'signing'
|
||||
//apply from: '../.publishing/sonatype.gradle'
|
||||
|
||||
archivesBaseName = 'android-crop'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion '23.0.1'
|
||||
buildToolsVersion '23.0.2'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 10
|
||||
targetSdkVersion 22
|
||||
|
||||
testApplicationId 'com.soundcloud.android.crop.test'
|
||||
testInstrumentationRunner 'android.test.InstrumentationTestRunner'
|
||||
targetSdkVersion 23
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.android.support:support-annotations:23.0.1'
|
||||
compile 'com.android.support:support-v4:23.0.1'
|
||||
androidTestCompile 'com.squareup:fest-android:1.0.7'
|
||||
androidTestCompile 'com.android.support:support-v4:23.0.1'
|
||||
androidTestCompile 'org.mockito:mockito-core:1.9.5'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
|
||||
compile 'com.android.support:support-annotations:23.1.1'
|
||||
compile 'com.android.support:support-v4:23.1.1'
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package com.soundcloud.android.crop;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
public class BaseTestCase extends InstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
// Work around dexmaker issue when running tests on Android 4.3
|
||||
System.setProperty("dexmaker.dexcache",
|
||||
getInstrumentation().getTargetContext().getCacheDir().getPath());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package com.soundcloud.android.crop;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.fest.assertions.api.ANDROID;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
public class CropBuilderTest extends BaseTestCase {
|
||||
|
||||
private Activity activity;
|
||||
private Crop builder;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
activity = mock(Activity.class);
|
||||
when(activity.getPackageName()).thenReturn("com.example");
|
||||
|
||||
builder = Crop.of(Uri.parse("image:input"), Uri.parse("image:output"));
|
||||
}
|
||||
|
||||
public void testInputUriSetAsData() {
|
||||
ANDROID.assertThat(builder.getIntent(activity)).hasData("image:input");
|
||||
}
|
||||
|
||||
public void testOutputUriSetAsExtra() {
|
||||
Intent intent = builder.getIntent(activity);
|
||||
Uri output = intent.getParcelableExtra(MediaStore.EXTRA_OUTPUT);
|
||||
|
||||
assertThat(output.toString()).isEqualTo("image:output");
|
||||
}
|
||||
|
||||
public void testAspectRatioSetAsExtras() {
|
||||
builder.withAspect(16, 10);
|
||||
|
||||
Intent intent = builder.getIntent(activity);
|
||||
|
||||
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(16);
|
||||
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(10);
|
||||
}
|
||||
|
||||
public void testFixedAspectRatioSetAsExtras() {
|
||||
builder.asSquare();
|
||||
|
||||
Intent intent = builder.getIntent(activity);
|
||||
|
||||
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
|
||||
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
|
||||
}
|
||||
|
||||
public void testMaxSizeSetAsExtras() {
|
||||
builder.withMaxSize(400, 300);
|
||||
|
||||
Intent intent = builder.getIntent(activity);
|
||||
|
||||
assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(400);
|
||||
assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(300);
|
||||
}
|
||||
|
||||
public void testBuildsIntentWithMultipleOptions() {
|
||||
builder.asSquare().withMaxSize(200, 200);
|
||||
|
||||
Intent intent = builder.getIntent(activity);
|
||||
|
||||
assertThat(intent.getIntExtra("aspect_x", 0)).isEqualTo(1);
|
||||
assertThat(intent.getIntExtra("aspect_y", 0)).isEqualTo(1);
|
||||
assertThat(intent.getIntExtra("max_x", 0)).isEqualTo(200);
|
||||
assertThat(intent.getIntExtra("max_y", 0)).isEqualTo(200);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
|
@ -56,6 +57,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
private int maxX;
|
||||
private int maxY;
|
||||
private int exifRotation;
|
||||
private int exifScale;
|
||||
|
||||
private Uri sourceUri;
|
||||
private Uri saveUri;
|
||||
|
|
@ -63,7 +65,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
private boolean isSaving;
|
||||
|
||||
private int sampleSize;
|
||||
private RotateBitmap rotateBitmap;
|
||||
private Bitmap srcBitmap;
|
||||
private CropImageView imageView;
|
||||
private HighlightView cropView;
|
||||
|
||||
|
|
@ -74,7 +76,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
setupViews();
|
||||
|
||||
loadInput();
|
||||
if (rotateBitmap == null) {
|
||||
if (srcBitmap == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
|
@ -119,6 +121,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
private void loadInput() {
|
||||
Intent intent = getIntent();
|
||||
Bundle extras = intent.getExtras();
|
||||
Bitmap initialBitmap ;
|
||||
|
||||
if (extras != null) {
|
||||
aspectX = extras.getInt(Crop.Extra.ASPECT_X);
|
||||
|
|
@ -130,15 +133,32 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
|
||||
sourceUri = intent.getData();
|
||||
if (sourceUri != null) {
|
||||
exifRotation = CropUtil.getExifRotation(CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));
|
||||
|
||||
Pair<Integer,Integer> exifRotationTrans =
|
||||
CropUtil.getExifRotationTranslation(
|
||||
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri));
|
||||
exifRotation = exifRotationTrans.first;
|
||||
exifScale = exifRotationTrans.second;
|
||||
InputStream is = null;
|
||||
try {
|
||||
sampleSize = calculateBitmapSampleSize(sourceUri);
|
||||
is = getContentResolver().openInputStream(sourceUri);
|
||||
BitmapFactory.Options option = new BitmapFactory.Options();
|
||||
option.inSampleSize = sampleSize;
|
||||
rotateBitmap = new RotateBitmap(BitmapFactory.decodeStream(is, null, option), exifRotation);
|
||||
initialBitmap = BitmapFactory.decodeStream(is, null, option);
|
||||
int drawHeight = initialBitmap.getHeight();
|
||||
int drawWidth = initialBitmap.getWidth();
|
||||
if ((exifRotation != 0) || (exifScale != 1)) {
|
||||
Matrix matrix = new Matrix() ;
|
||||
if (exifRotation != 0) {
|
||||
matrix.preRotate(exifRotation);
|
||||
}
|
||||
if (exifScale != 1) {
|
||||
matrix.postScale(exifScale, 1);
|
||||
}
|
||||
srcBitmap = Bitmap.createBitmap(initialBitmap, 0, 0, drawWidth, drawHeight, matrix, true);
|
||||
} else {
|
||||
srcBitmap = initialBitmap ;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("Error reading image: " + e.getMessage(), e);
|
||||
setResultException(e);
|
||||
|
|
@ -149,6 +169,9 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
CropUtil.closeSilently(is);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Log.e("Source URI is null");
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateBitmapSampleSize(Uri bitmapUri) throws IOException {
|
||||
|
|
@ -190,7 +213,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
if (isFinishing()) {
|
||||
return;
|
||||
}
|
||||
imageView.setImageRotateBitmapResetBase(rotateBitmap, true);
|
||||
imageView.setImageBitmapResetBase(srcBitmap, true);
|
||||
CropUtil.startBackgroundJob(this, null, getResources().getString(R.string.crop__wait),
|
||||
new Runnable() {
|
||||
public void run() {
|
||||
|
|
@ -217,13 +240,13 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
private class Cropper {
|
||||
|
||||
private void makeDefault() {
|
||||
if (rotateBitmap == null) {
|
||||
if (srcBitmap == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HighlightView hv = new HighlightView(imageView);
|
||||
final int width = rotateBitmap.getWidth();
|
||||
final int height = rotateBitmap.getHeight();
|
||||
final int width = srcBitmap.getWidth();
|
||||
final int height = srcBitmap.getHeight();
|
||||
|
||||
Rect imageRect = new Rect(0, 0, width, height);
|
||||
|
||||
|
|
@ -275,6 +298,8 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
|
||||
int outWidth = width;
|
||||
int outHeight = height;
|
||||
Log.e("Crop Width = " + width);
|
||||
Log.e("Crop Height = " + height);
|
||||
if (maxX > 0 && maxY > 0 && (width > maxX || height > maxY)) {
|
||||
float ratio = (float) width / (float) height;
|
||||
if ((float) maxX / (float) maxY > ratio) {
|
||||
|
|
@ -295,7 +320,7 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
}
|
||||
|
||||
if (croppedImage != null) {
|
||||
imageView.setImageRotateBitmapResetBase(new RotateBitmap(croppedImage, exifRotation), true);
|
||||
imageView.setImageBitmapResetBase(croppedImage, true);
|
||||
imageView.center();
|
||||
imageView.highlightViews.clear();
|
||||
}
|
||||
|
|
@ -318,22 +343,20 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
}
|
||||
|
||||
private Bitmap decodeRegionCrop(Rect rect, int outWidth, int outHeight) {
|
||||
// Release memory now
|
||||
clearImageView();
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
InputStream is = null;
|
||||
Bitmap croppedImage = null;
|
||||
boolean transformed = false ;
|
||||
try {
|
||||
is = getContentResolver().openInputStream(sourceUri);
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false);
|
||||
final int width = decoder.getWidth();
|
||||
final int height = decoder.getHeight();
|
||||
|
||||
if (exifRotation != 0) {
|
||||
// Adjust crop area to account for image rotation
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setRotate(-exifRotation);
|
||||
|
||||
if ((exifRotation != 0) || (exifScale != 1)) {
|
||||
// Adjust crop area to account for EXIF transformation so what you crop is what you get
|
||||
matrix.preRotate(-exifRotation);
|
||||
matrix.postScale(exifScale, 1);
|
||||
RectF adjusted = new RectF();
|
||||
matrix.mapRect(adjusted, new RectF(rect));
|
||||
|
||||
|
|
@ -343,10 +366,19 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
}
|
||||
|
||||
try {
|
||||
matrix.reset();
|
||||
croppedImage = decoder.decodeRegion(rect, new BitmapFactory.Options());
|
||||
if (croppedImage != null && (rect.width() > outWidth || rect.height() > outHeight)) {
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.postScale((float) outWidth / rect.width(), (float) outHeight / rect.height());
|
||||
if ((exifRotation != 0) || (exifScale != 1)) {
|
||||
transformed = true;
|
||||
// Remove the EXIF transformation from the cropped image so we can dispense with it altogether
|
||||
matrix.preRotate(exifRotation);
|
||||
matrix.postScale(exifScale, 1);
|
||||
}
|
||||
if (rect.width() > outWidth || rect.height() > outHeight) {
|
||||
transformed = true ;
|
||||
matrix.postScale(((float)outWidth)/((float)rect.width()), ((float)outHeight)/((float)rect.height()));
|
||||
}
|
||||
if (transformed) {
|
||||
croppedImage = Bitmap.createBitmap(croppedImage, 0, 0, croppedImage.getWidth(), croppedImage.getHeight(), matrix, true);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
|
@ -363,14 +395,16 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
setResultException(e);
|
||||
} finally {
|
||||
CropUtil.closeSilently(is);
|
||||
// Release memory now
|
||||
clearImageView();
|
||||
}
|
||||
return croppedImage;
|
||||
}
|
||||
|
||||
private void clearImageView() {
|
||||
imageView.clear();
|
||||
if (rotateBitmap != null) {
|
||||
rotateBitmap.recycle();
|
||||
if (srcBitmap != null) {
|
||||
srcBitmap.recycle();
|
||||
}
|
||||
System.gc();
|
||||
}
|
||||
|
|
@ -389,12 +423,6 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
} finally {
|
||||
CropUtil.closeSilently(outputStream);
|
||||
}
|
||||
|
||||
CropUtil.copyExifRotation(
|
||||
CropUtil.getFromMediaUri(this, getContentResolver(), sourceUri),
|
||||
CropUtil.getFromMediaUri(this, getContentResolver(), saveUri)
|
||||
);
|
||||
|
||||
setResultUri(saveUri);
|
||||
}
|
||||
|
||||
|
|
@ -412,8 +440,8 @@ public class CropImageActivity extends MonitoredActivity {
|
|||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (rotateBitmap != null) {
|
||||
rotateBitmap.recycle();
|
||||
if (srcBitmap != null) {
|
||||
srcBitmap.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor;
|
|||
import android.provider.MediaStore;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
|
|
@ -52,25 +53,43 @@ class CropUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static int getExifRotation(File imageFile) {
|
||||
if (imageFile == null) return 0;
|
||||
public static Pair<Integer,Integer> getExifRotationTranslation(File imageFile) {
|
||||
int exifRotation = 0, exifScale = 1;
|
||||
if (imageFile == null) return new Pair<Integer,Integer>(0,1);
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
|
||||
// We only recognize a subset of orientation tag values
|
||||
switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
default:
|
||||
return ExifInterface.ORIENTATION_UNDEFINED;
|
||||
case ExifInterface.ORIENTATION_UNDEFINED:
|
||||
case ExifInterface.ORIENTATION_NORMAL:
|
||||
break ;
|
||||
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
|
||||
exifScale=-1;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
exifRotation = 180;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
|
||||
exifRotation = 180;
|
||||
exifScale=-1;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_TRANSPOSE:
|
||||
exifRotation = 90;
|
||||
exifScale=-1;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
exifRotation = 90;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_TRANSVERSE:
|
||||
exifRotation = 270;
|
||||
exifScale=-1;
|
||||
break;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
exifRotation = 270;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("Error getting Exif data", e);
|
||||
return 0;
|
||||
}
|
||||
return new Pair<Integer,Integer>(exifRotation,exifScale);
|
||||
}
|
||||
|
||||
public static boolean copyExifRotation(File sourceFile, File destFile) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ import android.graphics.Color;
|
|||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Region;
|
||||
|
|
@ -41,19 +43,20 @@ import android.view.View;
|
|||
*/
|
||||
class HighlightView {
|
||||
|
||||
public static final int GROW_NONE = (1 << 0);
|
||||
public static final int GROW_LEFT_EDGE = (1 << 1);
|
||||
public static final int GROW_RIGHT_EDGE = (1 << 2);
|
||||
public static final int GROW_TOP_EDGE = (1 << 3);
|
||||
public static final int GROW_NONE = (1 << 0);
|
||||
public static final int GROW_LEFT_EDGE = (1 << 1);
|
||||
public static final int GROW_RIGHT_EDGE = (1 << 2);
|
||||
public static final int GROW_TOP_EDGE = (1 << 3);
|
||||
public static final int GROW_BOTTOM_EDGE = (1 << 4);
|
||||
public static final int MOVE = (1 << 5);
|
||||
public static final int MOVE = (1 << 5);
|
||||
|
||||
private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF33B5E5;
|
||||
private static final float HANDLE_RADIUS_DP = 12f;
|
||||
private static final float OUTLINE_DP = 2f;
|
||||
private static final int DEFAULT_HIGHLIGHT_COLOR = 0xFF00985F;
|
||||
private static final float HANDLE_RADIUS_DP = 8f;
|
||||
private static final float OUTLINE_DP = 1f;
|
||||
|
||||
enum ModifyMode { None, Move, Grow }
|
||||
enum HandleMode { Changing, Always, Never }
|
||||
enum ModifyMode {None, Move, Grow}
|
||||
|
||||
enum HandleMode {Changing, Always, Never}
|
||||
|
||||
RectF cropRect; // Image space
|
||||
Rect drawRect; // Screen space
|
||||
|
|
@ -69,6 +72,7 @@ class HighlightView {
|
|||
private boolean showCircle;
|
||||
private int highlightColor;
|
||||
|
||||
|
||||
private ModifyMode modifyMode = ModifyMode.None;
|
||||
private HandleMode handleMode = HandleMode.Changing;
|
||||
private boolean maintainAspectRatio;
|
||||
|
|
@ -76,6 +80,7 @@ class HighlightView {
|
|||
private float handleRadius;
|
||||
private float outlineWidth;
|
||||
private boolean isFocused;
|
||||
private float hysteresis;
|
||||
|
||||
public HighlightView(View context) {
|
||||
viewContext = context;
|
||||
|
|
@ -85,10 +90,11 @@ class HighlightView {
|
|||
private void initStyles(Context context) {
|
||||
TypedValue outValue = new TypedValue();
|
||||
context.getTheme().resolveAttribute(R.attr.cropImageStyle, outValue, true);
|
||||
hysteresis = dpToPx(16);
|
||||
TypedArray attributes = context.obtainStyledAttributes(outValue.resourceId, R.styleable.CropImageView);
|
||||
try {
|
||||
showThirds = attributes.getBoolean(R.styleable.CropImageView_showThirds, false);
|
||||
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, false);
|
||||
showCircle = attributes.getBoolean(R.styleable.CropImageView_showCircle, true);
|
||||
highlightColor = attributes.getColor(R.styleable.CropImageView_highlightColor,
|
||||
DEFAULT_HIGHLIGHT_COLOR);
|
||||
handleMode = HandleMode.values()[attributes.getInt(R.styleable.CropImageView_showHandles, 0)];
|
||||
|
|
@ -107,7 +113,7 @@ class HighlightView {
|
|||
initialAspectRatio = this.cropRect.width() / this.cropRect.height();
|
||||
drawRect = computeLayout();
|
||||
|
||||
outsidePaint.setARGB(125, 50, 50, 50);
|
||||
outsidePaint.setARGB(153, 0, 0, 0);
|
||||
outlinePaint.setStyle(Paint.Style.STROKE);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
outlineWidth = dpToPx(OUTLINE_DP);
|
||||
|
|
@ -135,7 +141,11 @@ class HighlightView {
|
|||
Rect viewDrawingRect = new Rect();
|
||||
viewContext.getDrawingRect(viewDrawingRect);
|
||||
|
||||
path.addRect(new RectF(drawRect), Path.Direction.CW);
|
||||
if (showCircle) {
|
||||
path.addOval(new RectF(drawRect), Path.Direction.CW);
|
||||
} else {
|
||||
path.addRect(new RectF(drawRect), Path.Direction.CW);
|
||||
}
|
||||
outlinePaint.setColor(highlightColor);
|
||||
|
||||
if (isClipPathSupported(canvas)) {
|
||||
|
|
@ -152,10 +162,6 @@ class HighlightView {
|
|||
drawThirds(canvas);
|
||||
}
|
||||
|
||||
if (showCircle) {
|
||||
drawCircle(canvas);
|
||||
}
|
||||
|
||||
if (handleMode == HandleMode.Always ||
|
||||
(handleMode == HandleMode.Changing && modifyMode == ModifyMode.Grow)) {
|
||||
drawHandles(canvas);
|
||||
|
|
@ -167,10 +173,20 @@ class HighlightView {
|
|||
* Fall back to naive method for darkening outside crop area
|
||||
*/
|
||||
private void drawOutsideFallback(Canvas canvas) {
|
||||
canvas.drawRect(0, 0, canvas.getWidth(), drawRect.top, outsidePaint);
|
||||
canvas.drawRect(0, drawRect.bottom, canvas.getWidth(), canvas.getHeight(), outsidePaint);
|
||||
canvas.drawRect(0, drawRect.top, drawRect.left, drawRect.bottom, outsidePaint);
|
||||
canvas.drawRect(drawRect.right, drawRect.top, canvas.getWidth(), drawRect.bottom, outsidePaint);
|
||||
int layer = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null,
|
||||
Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG);
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setXfermode(null);
|
||||
paint.setColor(outsidePaint.getColor());
|
||||
if (showCircle) {
|
||||
canvas.drawOval(new RectF(drawRect), paint);
|
||||
} else {
|
||||
canvas.drawRect(new RectF(drawRect), paint);
|
||||
}
|
||||
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OUT));
|
||||
paint.setColor(outsidePaint.getColor());
|
||||
canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);
|
||||
canvas.restoreToCount(layer);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -183,7 +199,7 @@ class HighlightView {
|
|||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
return false;
|
||||
} else if ((Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
|| Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
return true;
|
||||
} else {
|
||||
return !canvas.isHardwareAccelerated();
|
||||
|
|
@ -191,7 +207,7 @@ class HighlightView {
|
|||
}
|
||||
|
||||
private void drawHandles(Canvas canvas) {
|
||||
int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
|
||||
int xMiddle = drawRect.left + ((drawRect.right - drawRect.left) / 2);
|
||||
int yMiddle = drawRect.top + ((drawRect.bottom - drawRect.top) / 2);
|
||||
|
||||
canvas.drawCircle(drawRect.left, yMiddle, handleRadius, handlePaint);
|
||||
|
|
@ -204,7 +220,7 @@ class HighlightView {
|
|||
outlinePaint.setStrokeWidth(1);
|
||||
float xThird = (drawRect.right - drawRect.left) / 3;
|
||||
float yThird = (drawRect.bottom - drawRect.top) / 3;
|
||||
|
||||
|
||||
canvas.drawLine(drawRect.left + xThird, drawRect.top,
|
||||
drawRect.left + xThird, drawRect.bottom, outlinePaint);
|
||||
canvas.drawLine(drawRect.left + xThird * 2, drawRect.top,
|
||||
|
|
@ -215,11 +231,6 @@ class HighlightView {
|
|||
drawRect.right, drawRect.top + yThird * 2, outlinePaint);
|
||||
}
|
||||
|
||||
private void drawCircle(Canvas canvas) {
|
||||
outlinePaint.setStrokeWidth(1);
|
||||
canvas.drawOval(new RectF(drawRect), outlinePaint);
|
||||
}
|
||||
|
||||
public void setMode(ModifyMode mode) {
|
||||
if (mode != modifyMode) {
|
||||
modifyMode = mode;
|
||||
|
|
@ -229,8 +240,43 @@ class HighlightView {
|
|||
|
||||
// Determines which edges are hit by touching at (x, y)
|
||||
public int getHit(float x, float y) {
|
||||
if (showCircle) {
|
||||
return getCircleHit(x, y);
|
||||
} else {
|
||||
return getRectangleHit(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private int getCircleHit(float x, float y) {
|
||||
Rect r = computeLayout();
|
||||
int retval = GROW_NONE;
|
||||
|
||||
double radius = r.height() / (double) 2;
|
||||
double distanceToCenter = Math.sqrt(Math.pow(r.top + radius - y, 2)
|
||||
+ Math.pow(r.left + radius - x, 2));
|
||||
if (Math.abs(distanceToCenter - radius) < hysteresis) {
|
||||
if (y < r.top + radius / 2) {
|
||||
retval |= GROW_TOP_EDGE;
|
||||
}
|
||||
if (y > r.top + radius * 1.5) {
|
||||
retval |= GROW_BOTTOM_EDGE;
|
||||
}
|
||||
if (x < r.left + radius / 2) {
|
||||
retval |= GROW_LEFT_EDGE;
|
||||
}
|
||||
if (x > r.left + radius * 1.5) {
|
||||
retval |= GROW_RIGHT_EDGE;
|
||||
}
|
||||
}
|
||||
|
||||
if (retval == GROW_NONE && r.contains((int) x, (int) y)) {
|
||||
retval = MOVE;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
private int getRectangleHit(float x, float y) {
|
||||
Rect r = computeLayout();
|
||||
final float hysteresis = 20F;
|
||||
int retval = GROW_NONE;
|
||||
|
||||
// verticalCheck makes sure the position is between the top and
|
||||
|
|
@ -241,16 +287,16 @@ class HighlightView {
|
|||
&& (x < r.right + hysteresis);
|
||||
|
||||
// Check whether the position is near some edge(s)
|
||||
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
|
||||
if ((Math.abs(r.left - x) < hysteresis) && verticalCheck) {
|
||||
retval |= GROW_LEFT_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
|
||||
if ((Math.abs(r.right - x) < hysteresis) && verticalCheck) {
|
||||
retval |= GROW_RIGHT_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
|
||||
if ((Math.abs(r.top - y) < hysteresis) && horizCheck) {
|
||||
retval |= GROW_TOP_EDGE;
|
||||
}
|
||||
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
|
||||
if ((Math.abs(r.bottom - y) < hysteresis) && horizCheck) {
|
||||
retval |= GROW_BOTTOM_EDGE;
|
||||
}
|
||||
|
||||
|
|
@ -268,7 +314,7 @@ class HighlightView {
|
|||
if (edge == MOVE) {
|
||||
// Convert to image space before sending to moveBy()
|
||||
moveBy(dx * (cropRect.width() / r.width()),
|
||||
dy * (cropRect.height() / r.height()));
|
||||
dy * (cropRect.height() / r.height()));
|
||||
} else {
|
||||
if (((GROW_LEFT_EDGE | GROW_RIGHT_EDGE) & edge) == 0) {
|
||||
dx = 0;
|
||||
|
|
@ -295,10 +341,10 @@ class HighlightView {
|
|||
// Put the cropping rectangle inside image rectangle
|
||||
cropRect.offset(
|
||||
Math.max(0, imageRect.left - cropRect.left),
|
||||
Math.max(0, imageRect.top - cropRect.top));
|
||||
Math.max(0, imageRect.top - cropRect.top));
|
||||
|
||||
cropRect.offset(
|
||||
Math.min(0, imageRect.right - cropRect.right),
|
||||
Math.min(0, imageRect.right - cropRect.right),
|
||||
Math.min(0, imageRect.bottom - cropRect.bottom));
|
||||
|
||||
drawRect = computeLayout();
|
||||
|
|
@ -374,10 +420,10 @@ class HighlightView {
|
|||
// Maps the cropping rectangle from image space to screen space
|
||||
private Rect computeLayout() {
|
||||
RectF r = new RectF(cropRect.left, cropRect.top,
|
||||
cropRect.right, cropRect.bottom);
|
||||
cropRect.right, cropRect.bottom);
|
||||
matrix.mapRect(r);
|
||||
return new Rect(Math.round(r.left), Math.round(r.top),
|
||||
Math.round(r.right), Math.round(r.bottom));
|
||||
Math.round(r.right), Math.round(r.bottom));
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 76 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 76 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
|
@ -1,6 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/crop__selector_pressed">
|
||||
<item android:drawable="@color/crop__button_bar" />
|
||||
</ripple>
|
||||
android:color="@color/crop__selector_pressed"/>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 83 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 142 B |
|
After Width: | Height: | Size: 345 B |
|
After Width: | Height: | Size: 337 B |
|
|
@ -9,12 +9,4 @@
|
|||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:state_focused="true">
|
||||
<shape>
|
||||
<solid android:color="@color/crop__selector_focused" />
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
|
||||
</selector>
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/crop__tile"
|
||||
android:tileMode="repeat" />
|
||||
|
|
@ -2,8 +2,10 @@
|
|||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black">
|
||||
|
||||
<include
|
||||
android:id="@+id/done_cancel_bar"
|
||||
|
|
@ -13,7 +15,6 @@
|
|||
android:id="@+id/crop_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/crop__texture"
|
||||
android:layout_below="@id/done_cancel_bar" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="@style/Crop.DoneCancelBar">
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#00985f">
|
||||
|
||||
<FrameLayout
|
||||
<ImageView
|
||||
android:id="@+id/btn_cancel"
|
||||
style="@style/Crop.ActionButton">
|
||||
<TextView style="@style/Crop.ActionButtonText.Cancel" />
|
||||
</FrameLayout>
|
||||
style="@style/Crop.ActionButton"
|
||||
android:layout_gravity="left"
|
||||
android:src="@drawable/crop__ic_cancel"/>
|
||||
|
||||
<FrameLayout
|
||||
<ImageView
|
||||
android:id="@+id/btn_done"
|
||||
style="@style/Crop.ActionButton">
|
||||
<TextView style="@style/Crop.ActionButtonText.Done" />
|
||||
</FrameLayout>
|
||||
style="@style/Crop.ActionButton"
|
||||
android:layout_gravity="right"
|
||||
android:src="@drawable/crop__ic_done"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<resources>
|
||||
|
||||
<color name="crop__button_bar">#f3f3f3</color>
|
||||
<color name="crop__button_text">#666666</color>
|
||||
<color name="crop__selector_pressed">#1a000000</color>
|
||||
<color name="crop__selector_focused">#77000000</color>
|
||||
<color name="crop__selector_pressed">#40FFFFFF</color>
|
||||
|
||||
</resources>
|
||||
|
|
@ -1,44 +1,12 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<resources>
|
||||
|
||||
<style name="Crop"></style>
|
||||
|
||||
<style name="Crop.DoneCancelBar">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">@dimen/crop__bar_height</item>
|
||||
<item name="android:orientation">horizontal</item>
|
||||
<item name="android:divider">@drawable/crop__divider</item>
|
||||
<item name="android:showDividers" tools:ignore="NewApi">middle</item>
|
||||
<item name="android:dividerPadding" tools:ignore="NewApi">12dp</item>
|
||||
<item name="android:background">@color/crop__button_bar</item>
|
||||
</style>
|
||||
<style name="Crop"/>
|
||||
|
||||
<style name="Crop.ActionButton">
|
||||
<item name="android:layout_width">0dp</item>
|
||||
<item name="android:layout_height">match_parent</item>
|
||||
<item name="android:layout_weight">1</item>
|
||||
<item name="android:layout_width">@dimen/crop__bar_height</item>
|
||||
<item name="android:layout_height">@dimen/crop__bar_height</item>
|
||||
<item name="android:background">@drawable/crop__selectable_background</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:gravity">center_vertical</item>
|
||||
<item name="android:paddingRight">20dp</item> <!-- Offsets left drawable -->
|
||||
<item name="android:drawablePadding">8dp</item>
|
||||
<item name="android:textColor">@color/crop__button_text</item>
|
||||
<item name="android:textStyle">bold</item>
|
||||
<item name="android:textSize">13sp</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText.Done">
|
||||
<item name="android:drawableLeft">@drawable/crop__ic_done</item>
|
||||
<item name="android:text">@string/crop__done</item>
|
||||
</style>
|
||||
|
||||
<style name="Crop.ActionButtonText.Cancel">
|
||||
<item name="android:drawableLeft">@drawable/crop__ic_cancel</item>
|
||||
<item name="android:text">@string/crop__cancel</item>
|
||||
<item name="android:scaleType">center</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||