switch lib
| 
						 | 
				
			
			@ -8,11 +8,11 @@ plugins {
 | 
			
		|||
}
 | 
			
		||||
def flavor
 | 
			
		||||
android {
 | 
			
		||||
    compileSdk 32
 | 
			
		||||
    compileSdk 33
 | 
			
		||||
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        minSdk 21
 | 
			
		||||
        targetSdk 32
 | 
			
		||||
        targetSdk 33
 | 
			
		||||
        versionCode 451
 | 
			
		||||
        versionName "3.12.1"
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
| 
						 | 
				
			
			@ -106,7 +106,7 @@ dependencies {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    implementation 'com.burhanrashid52:photoeditor:1.5.1'
 | 
			
		||||
    implementation project(path: ':cropper')
 | 
			
		||||
    implementation("com.vanniktech:android-image-cropper:4.3.3")
 | 
			
		||||
    annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
 | 
			
		||||
    implementation 'jp.wasabeef:glide-transformations:4.3.0'
 | 
			
		||||
    implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.24.0'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -213,7 +213,7 @@
 | 
			
		|||
            android:configChanges="keyboardHidden|orientation|screenSize"
 | 
			
		||||
            android:label="@string/scheduled" />
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
 | 
			
		||||
            android:name="com.canhub.cropper.CropImageActivity"
 | 
			
		||||
            android:theme="@style/Base.Theme.AppCompat" />
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -126,6 +126,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana
 | 
			
		|||
                                if (focusX != -2) {
 | 
			
		||||
                                    attachment.focus = focusX + "," + focusY;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                composeAdapter.notifyItemChanged(position);
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -251,7 +251,7 @@ public interface MastodonStatusesService {
 | 
			
		|||
            @Part MultipartBody.Part file,
 | 
			
		||||
            @Part MultipartBody.Part thumbnail,
 | 
			
		||||
            @Part("description") RequestBody description,
 | 
			
		||||
            @Part("focus") String focus
 | 
			
		||||
            @Part("focus") RequestBody focus
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    //Edit a Media
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1243,32 +1243,48 @@ public class Helper {
 | 
			
		|||
                counter++;
 | 
			
		||||
                Date now = new Date();
 | 
			
		||||
                attachment.filename = formatter.format(now) + "." + extension;
 | 
			
		||||
                InputStream selectedFileInputStream;
 | 
			
		||||
                try {
 | 
			
		||||
                    selectedFileInputStream = context.getContentResolver().openInputStream(uri);
 | 
			
		||||
                    if (selectedFileInputStream != null) {
 | 
			
		||||
                if (attachment.mimeType.startsWith("image")) {
 | 
			
		||||
                    try {
 | 
			
		||||
                        final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY);
 | 
			
		||||
                        boolean isCertCacheDirExists = certCacheDir.exists();
 | 
			
		||||
                        if (!isCertCacheDirExists) {
 | 
			
		||||
                            isCertCacheDirExists = certCacheDir.mkdirs();
 | 
			
		||||
                            certCacheDir.mkdirs();
 | 
			
		||||
                        }
 | 
			
		||||
                        if (isCertCacheDirExists) {
 | 
			
		||||
                            String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename;
 | 
			
		||||
                            attachment.local_path = filePath;
 | 
			
		||||
                            OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
 | 
			
		||||
                            byte[] buffer = new byte[1024];
 | 
			
		||||
                            int length;
 | 
			
		||||
                            while ((length = selectedFileInputStream.read(buffer)) > 0) {
 | 
			
		||||
                                selectedFileOutPutStream.write(buffer, 0, length);
 | 
			
		||||
                            }
 | 
			
		||||
                            selectedFileOutPutStream.flush();
 | 
			
		||||
                            selectedFileOutPutStream.close();
 | 
			
		||||
                        }
 | 
			
		||||
                        selectedFileInputStream.close();
 | 
			
		||||
                        String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename;
 | 
			
		||||
                        MediaHelper.ResizedImageRequestBody(context, uri, filePath);
 | 
			
		||||
                        attachment.local_path = filePath;
 | 
			
		||||
                    } catch (IOException e) {
 | 
			
		||||
                        e.printStackTrace();
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    InputStream selectedFileInputStream;
 | 
			
		||||
                    try {
 | 
			
		||||
                        selectedFileInputStream = context.getContentResolver().openInputStream(uri);
 | 
			
		||||
                        if (selectedFileInputStream != null) {
 | 
			
		||||
                            final File certCacheDir = new File(context.getCacheDir(), TEMP_MEDIA_DIRECTORY);
 | 
			
		||||
                            boolean isCertCacheDirExists = certCacheDir.exists();
 | 
			
		||||
                            if (!isCertCacheDirExists) {
 | 
			
		||||
                                isCertCacheDirExists = certCacheDir.mkdirs();
 | 
			
		||||
                            }
 | 
			
		||||
                            if (isCertCacheDirExists) {
 | 
			
		||||
                                String filePath = certCacheDir.getAbsolutePath() + "/" + attachment.filename;
 | 
			
		||||
                                attachment.local_path = filePath;
 | 
			
		||||
                                OutputStream selectedFileOutPutStream = new FileOutputStream(filePath);
 | 
			
		||||
                                byte[] buffer = new byte[1024];
 | 
			
		||||
                                int length;
 | 
			
		||||
                                while ((length = selectedFileInputStream.read(buffer)) > 0) {
 | 
			
		||||
                                    selectedFileOutPutStream.write(buffer, 0, length);
 | 
			
		||||
                                }
 | 
			
		||||
                                selectedFileOutPutStream.flush();
 | 
			
		||||
                                selectedFileOutPutStream.close();
 | 
			
		||||
                            }
 | 
			
		||||
                            selectedFileInputStream.close();
 | 
			
		||||
                        }
 | 
			
		||||
                    } catch (Exception e) {
 | 
			
		||||
                        e.printStackTrace();
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (Exception e) {
 | 
			
		||||
                    e.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Handler mainHandler = new Handler(Looper.getMainLooper());
 | 
			
		||||
                Runnable myRunnable = () -> callBack.onAttachmentCopied(attachment);
 | 
			
		||||
                mainHandler.post(myRunnable);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,14 +24,23 @@ import android.app.DownloadManager;
 | 
			
		|||
import android.content.Context;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.SharedPreferences;
 | 
			
		||||
import android.database.Cursor;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.BitmapFactory;
 | 
			
		||||
import android.graphics.Canvas;
 | 
			
		||||
import android.graphics.ImageDecoder;
 | 
			
		||||
import android.graphics.Matrix;
 | 
			
		||||
import android.graphics.Paint;
 | 
			
		||||
import android.graphics.Rect;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.media.ExifInterface;
 | 
			
		||||
import android.media.MediaRecorder;
 | 
			
		||||
import android.media.MediaScannerConnection;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.Build;
 | 
			
		||||
import android.os.Environment;
 | 
			
		||||
import android.provider.MediaStore;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.text.format.DateFormat;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.webkit.MimeTypeMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +64,7 @@ import java.io.File;
 | 
			
		|||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.nio.channels.FileChannel;
 | 
			
		||||
import java.text.SimpleDateFormat;
 | 
			
		||||
import java.util.Calendar;
 | 
			
		||||
| 
						 | 
				
			
			@ -69,10 +79,12 @@ import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		|||
import app.fedilab.android.BuildConfig;
 | 
			
		||||
import app.fedilab.android.R;
 | 
			
		||||
import app.fedilab.android.activities.ComposeActivity;
 | 
			
		||||
import app.fedilab.android.activities.MainActivity;
 | 
			
		||||
import app.fedilab.android.client.entities.api.Attachment;
 | 
			
		||||
import app.fedilab.android.databinding.DatetimePickerBinding;
 | 
			
		||||
import app.fedilab.android.databinding.PopupRecordBinding;
 | 
			
		||||
import es.dmoral.toasty.Toasty;
 | 
			
		||||
import okhttp3.MediaType;
 | 
			
		||||
 | 
			
		||||
public class MediaHelper {
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -420,4 +432,146 @@ public class MediaHelper {
 | 
			
		|||
    public interface OnSchedule {
 | 
			
		||||
        void scheduledAt(String scheduledDate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static void ResizedImageRequestBody(Context context, Uri uri, String fullpatch) throws IOException {
 | 
			
		||||
 | 
			
		||||
        BitmapFactory.Options opts = new BitmapFactory.Options();
 | 
			
		||||
        opts.inJustDecodeBounds = true;
 | 
			
		||||
        String contentType;
 | 
			
		||||
        if ("file".equals(uri.getScheme())) {
 | 
			
		||||
            BitmapFactory.decodeFile(uri.getPath(), opts);
 | 
			
		||||
            contentType = MediaHelper.getFileMediaType(new File(uri.getPath())).type();
 | 
			
		||||
        } else {
 | 
			
		||||
            try (InputStream in = context.getContentResolver().openInputStream(uri)) {
 | 
			
		||||
                BitmapFactory.decodeStream(in, null, opts);
 | 
			
		||||
            }
 | 
			
		||||
            contentType = context.getContentResolver().getType(uri);
 | 
			
		||||
        }
 | 
			
		||||
        if (TextUtils.isEmpty(contentType))
 | 
			
		||||
            contentType = "image/jpeg";
 | 
			
		||||
        Bitmap bitmap;
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= 28) {
 | 
			
		||||
            ImageDecoder.Source source;
 | 
			
		||||
            if ("file".equals(uri.getScheme())) {
 | 
			
		||||
                source = ImageDecoder.createSource(new File(uri.getPath()));
 | 
			
		||||
            } else {
 | 
			
		||||
                source = ImageDecoder.createSource(context.getContentResolver(), uri);
 | 
			
		||||
            }
 | 
			
		||||
            BitmapFactory.Options finalOpts = opts;
 | 
			
		||||
            bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, _source) -> {
 | 
			
		||||
                int[] size = getTargetSize(info.getSize().getWidth(), info.getSize().getHeight());
 | 
			
		||||
                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
 | 
			
		||||
                if (needResize(finalOpts.outWidth, finalOpts.outHeight)) {
 | 
			
		||||
                    decoder.setTargetSize(size[0], size[1]);
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            int[] size = getTargetSize(opts.outWidth, opts.outHeight);
 | 
			
		||||
            int targetWidth;
 | 
			
		||||
            int targetHeight;
 | 
			
		||||
            if (needResize(opts.outWidth, opts.outHeight)) {
 | 
			
		||||
                targetWidth = size[0];
 | 
			
		||||
                targetHeight = size[1];
 | 
			
		||||
            } else {
 | 
			
		||||
                targetWidth = opts.outWidth;
 | 
			
		||||
                targetHeight = opts.outHeight;
 | 
			
		||||
            }
 | 
			
		||||
            float factor = opts.outWidth / (float) targetWidth;
 | 
			
		||||
            opts = new BitmapFactory.Options();
 | 
			
		||||
            opts.inSampleSize = (int) factor;
 | 
			
		||||
            int orientation = 0;
 | 
			
		||||
            String[] projection = {MediaStore.Images.ImageColumns.ORIENTATION};
 | 
			
		||||
            try {
 | 
			
		||||
                Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
 | 
			
		||||
                if (cursor.moveToFirst()) {
 | 
			
		||||
                    int photoRotation = cursor.getInt(0);
 | 
			
		||||
                }
 | 
			
		||||
                cursor.close();
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ("file".equals(uri.getScheme())) {
 | 
			
		||||
                ExifInterface exif = new ExifInterface(uri.getPath());
 | 
			
		||||
                orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
 | 
			
		||||
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
			
		||||
                try (InputStream in = context.getContentResolver().openInputStream(uri)) {
 | 
			
		||||
                    ExifInterface exif = new ExifInterface(in);
 | 
			
		||||
                    orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ("file".equals(uri.getScheme())) {
 | 
			
		||||
                bitmap = BitmapFactory.decodeFile(uri.getPath(), opts);
 | 
			
		||||
            } else {
 | 
			
		||||
                try (InputStream in = context.getContentResolver().openInputStream(uri)) {
 | 
			
		||||
                    bitmap = BitmapFactory.decodeStream(in, null, opts);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (factor % 1f != 0f) {
 | 
			
		||||
                Rect srcBounds = null;
 | 
			
		||||
                Rect dstBounds;
 | 
			
		||||
                dstBounds = new Rect(0, 0, targetWidth, targetHeight);
 | 
			
		||||
                Bitmap scaled = Bitmap.createBitmap(dstBounds.width(), dstBounds.height(), Bitmap.Config.ARGB_8888);
 | 
			
		||||
                new Canvas(scaled).drawBitmap(bitmap, srcBounds, dstBounds, new Paint(Paint.FILTER_BITMAP_FLAG));
 | 
			
		||||
                bitmap = scaled;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            int rotation = 0;
 | 
			
		||||
            switch (orientation) {
 | 
			
		||||
                case ExifInterface.ORIENTATION_ROTATE_90:
 | 
			
		||||
                    rotation = 90;
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExifInterface.ORIENTATION_ROTATE_180:
 | 
			
		||||
                    rotation = 180;
 | 
			
		||||
                    break;
 | 
			
		||||
                case ExifInterface.ORIENTATION_ROTATE_270:
 | 
			
		||||
                    rotation = 270;
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            if (rotation != 0) {
 | 
			
		||||
                Matrix matrix = new Matrix();
 | 
			
		||||
                matrix.setRotate(rotation);
 | 
			
		||||
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        boolean isPNG = "image/png".equals(contentType);
 | 
			
		||||
        File tempFile = new File(fullpatch);
 | 
			
		||||
        try (FileOutputStream out = new FileOutputStream(tempFile)) {
 | 
			
		||||
            if (isPNG) {
 | 
			
		||||
                bitmap.compress(Bitmap.CompressFormat.PNG, 0, out);
 | 
			
		||||
            } else {
 | 
			
		||||
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private static int[] getTargetSize(int srcWidth, int srcHeight) {
 | 
			
		||||
        int maxSize = 1;
 | 
			
		||||
        if (MainActivity.instanceInfo != null && MainActivity.instanceInfo.configuration != null && MainActivity.instanceInfo.configuration.media_attachments != null) {
 | 
			
		||||
            maxSize = MainActivity.instanceInfo.configuration.media_attachments.image_size_limit;
 | 
			
		||||
        }
 | 
			
		||||
        int targetWidth = Math.round((float) Math.sqrt((float) maxSize * ((float) srcWidth / srcHeight)));
 | 
			
		||||
        int targetHeight = Math.round((float) Math.sqrt((float) maxSize * ((float) srcHeight / srcWidth)));
 | 
			
		||||
        return new int[]{targetWidth, targetHeight};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static boolean needResize(int srcWidth, int srcHeight) {
 | 
			
		||||
        int maxSize;
 | 
			
		||||
        if (MainActivity.instanceInfo != null && MainActivity.instanceInfo.configuration != null && MainActivity.instanceInfo.configuration.media_attachments != null) {
 | 
			
		||||
            maxSize = MainActivity.instanceInfo.configuration.media_attachments.image_size_limit;
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return srcWidth * srcHeight > maxSize;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static MediaType getFileMediaType(File file) {
 | 
			
		||||
        String name = file.getName();
 | 
			
		||||
        return MediaType.parse(MimeTypeMap.getSingleton().getMimeTypeFromExtension(name.substring(name.lastIndexOf('.') + 1)));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,10 +10,12 @@ import android.graphics.Typeface;
 | 
			
		|||
import android.net.Uri;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.provider.MediaStore;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.view.MotionEvent;
 | 
			
		||||
import android.view.View;
 | 
			
		||||
import android.view.animation.AnticipateOvershootInterpolator;
 | 
			
		||||
 | 
			
		||||
import androidx.activity.result.ActivityResultLauncher;
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.appcompat.app.AlertDialog;
 | 
			
		||||
import androidx.constraintlayout.widget.ConstraintSet;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +27,12 @@ import androidx.recyclerview.widget.LinearLayoutManager;
 | 
			
		|||
import androidx.transition.ChangeBounds;
 | 
			
		||||
import androidx.transition.TransitionManager;
 | 
			
		||||
 | 
			
		||||
import com.canhub.cropper.CropImage;
 | 
			
		||||
import com.canhub.cropper.CropImageContract;
 | 
			
		||||
import com.canhub.cropper.CropImageContractOptions;
 | 
			
		||||
import com.canhub.cropper.CropImageOptions;
 | 
			
		||||
import com.canhub.cropper.CropImageView;
 | 
			
		||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
 | 
			
		||||
import com.theartofdev.edmodo.cropper.CropImage;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
| 
						 | 
				
			
			@ -73,6 +79,8 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
 | 
			
		|||
    private Uri uri;
 | 
			
		||||
    private boolean exit;
 | 
			
		||||
    private ActivityEditImageBinding binding;
 | 
			
		||||
    CropImageContractOptions cropImageContractOptions;
 | 
			
		||||
    ActivityResultLauncher<CropImageContractOptions> cropImageContractOptionsActivityResultLauncher;
 | 
			
		||||
 | 
			
		||||
    private static int exifToDegrees(int exifOrientation) {
 | 
			
		||||
        if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
 | 
			
		||||
| 
						 | 
				
			
			@ -146,6 +154,35 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cropImageContractOptions = new CropImageContractOptions(uri, new CropImageOptions())
 | 
			
		||||
                .setGuidelines(CropImageView.Guidelines.ON)
 | 
			
		||||
                .setCropShape(CropImageView.CropShape.RECTANGLE)
 | 
			
		||||
                .setAllowRotation(true)
 | 
			
		||||
                .setAllowFlipping(true)
 | 
			
		||||
                .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
 | 
			
		||||
                .setAllowCounterRotation(true)
 | 
			
		||||
                .setImageSource(true, false)
 | 
			
		||||
                .setScaleType(CropImageView.ScaleType.CENTER);
 | 
			
		||||
        cropImageContractOptionsActivityResultLauncher = registerForActivityResult(
 | 
			
		||||
                new CropImageContract(),
 | 
			
		||||
                result -> {
 | 
			
		||||
                    if (result.isSuccessful()) {
 | 
			
		||||
                        Uri resultUri = result.getUriContent();
 | 
			
		||||
                        if (resultUri != null) {
 | 
			
		||||
                            binding.photoEditorView.getSource().setImageURI(resultUri);
 | 
			
		||||
                            if (uri != null && uri.getPath() != null) {
 | 
			
		||||
                                File fdelete = new File(uri.getPath());
 | 
			
		||||
                                if (fdelete.exists()) {
 | 
			
		||||
                                    //noinspection ResultOfMethodCallIgnored
 | 
			
		||||
                                    fdelete.delete();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            uri = resultUri;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Log.e(Helper.TAG, "onActivityResult...Error CropImage: " + result.getError());
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
        mPhotoEditor.setFilterEffect(PhotoFilter.NONE);
 | 
			
		||||
        binding.send.setOnClickListener(v -> {
 | 
			
		||||
            exit = true;
 | 
			
		||||
| 
						 | 
				
			
			@ -286,6 +323,7 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
 | 
			
		|||
                                }
 | 
			
		||||
                                intentImage.putExtra("focusX", focusX);
 | 
			
		||||
                                intentImage.putExtra("focusY", focusY);
 | 
			
		||||
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            LocalBroadcastManager.getInstance(EditImageActivity.this).sendBroadcast(intentImage);
 | 
			
		||||
| 
						 | 
				
			
			@ -351,22 +389,37 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
 | 
			
		|||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                case CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE:
 | 
			
		||||
 | 
			
		||||
                    CropImage.ActivityResult result = CropImage.getActivityResult(data);
 | 
			
		||||
                    if (result != null) {
 | 
			
		||||
                        Uri resultUri = result.getUri();
 | 
			
		||||
                        if (resultUri != null) {
 | 
			
		||||
                            binding.photoEditorView.getSource().setImageURI(resultUri);
 | 
			
		||||
                            binding.photoEditorView.getSource().setRotation(rotationInDegrees);
 | 
			
		||||
                            if (uri != null && uri.getPath() != null) {
 | 
			
		||||
                                File fdelete = new File(uri.getPath());
 | 
			
		||||
                                if (fdelete.exists()) {
 | 
			
		||||
                                    //noinspection ResultOfMethodCallIgnored
 | 
			
		||||
                                    fdelete.delete();
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            uri = resultUri;
 | 
			
		||||
                        }
 | 
			
		||||
                    if (data != null && data.getData() != null) {
 | 
			
		||||
                        CropImageContractOptions cropImageContractOptions = new CropImageContractOptions(data.getData(), new CropImageOptions())
 | 
			
		||||
                                .setGuidelines(CropImageView.Guidelines.ON)
 | 
			
		||||
                                .setCropShape(CropImageView.CropShape.RECTANGLE)
 | 
			
		||||
                                .setAllowRotation(true)
 | 
			
		||||
                                .setAllowFlipping(true)
 | 
			
		||||
                                .setOutputCompressFormat(Bitmap.CompressFormat.PNG)
 | 
			
		||||
                                .setAllowCounterRotation(true)
 | 
			
		||||
                                .setImageSource(true, false)
 | 
			
		||||
                                .setScaleType(CropImageView.ScaleType.CENTER);
 | 
			
		||||
                        ActivityResultLauncher<CropImageContractOptions> cropImageContractOptionsActivityResultLauncher = registerForActivityResult(
 | 
			
		||||
                                new CropImageContract(),
 | 
			
		||||
                                result -> {
 | 
			
		||||
                                    if (result.isSuccessful()) {
 | 
			
		||||
                                        Uri resultUri = result.getUriContent();
 | 
			
		||||
                                        if (resultUri != null) {
 | 
			
		||||
                                            binding.photoEditorView.getSource().setImageURI(resultUri);
 | 
			
		||||
                                            if (uri != null && uri.getPath() != null) {
 | 
			
		||||
                                                File fdelete = new File(uri.getPath());
 | 
			
		||||
                                                if (fdelete.exists()) {
 | 
			
		||||
                                                    //noinspection ResultOfMethodCallIgnored
 | 
			
		||||
                                                    fdelete.delete();
 | 
			
		||||
                                                }
 | 
			
		||||
                                            }
 | 
			
		||||
                                            uri = resultUri;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        Log.e(Helper.TAG, "onActivityResult...Error CropImage: " + result.getError());
 | 
			
		||||
                                    }
 | 
			
		||||
                                });
 | 
			
		||||
                        cropImageContractOptionsActivityResultLauncher.launch(cropImageContractOptions);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -454,8 +507,8 @@ public class EditImageActivity extends BaseActivity implements OnPhotoEditorList
 | 
			
		|||
                mPropertiesBSFragment.show(getSupportFragmentManager(), mPropertiesBSFragment.getTag());
 | 
			
		||||
                break;
 | 
			
		||||
            case CROP:
 | 
			
		||||
                CropImage.activity(uri)
 | 
			
		||||
                        .start(this);
 | 
			
		||||
 | 
			
		||||
                cropImageContractOptionsActivityResultLauncher.launch(cropImageContractOptions);
 | 
			
		||||
                break;
 | 
			
		||||
            case FOCUS:
 | 
			
		||||
                binding.focusCircle.setVisibility(View.VISIBLE);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,5 @@
 | 
			
		|||
package app.fedilab.android.interfaces;
 | 
			
		||||
 | 
			
		||||
public interface ProgressListener {
 | 
			
		||||
    void onProgress(long transferred, long total);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -341,10 +341,14 @@ public class ComposeWorker extends Worker {
 | 
			
		|||
    private static String postAttachment(MastodonStatusesService mastodonStatusesService, DataPost dataPost, MultipartBody.Part fileMultipartBody, Attachment attachment) {
 | 
			
		||||
 | 
			
		||||
        RequestBody descriptionBody = null;
 | 
			
		||||
        RequestBody focusBody = null;
 | 
			
		||||
        if (attachment.description != null && attachment.description.trim().length() > 0) {
 | 
			
		||||
            descriptionBody = RequestBody.create(MediaType.parse("text/plain"), attachment.description);
 | 
			
		||||
        }
 | 
			
		||||
        Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, descriptionBody, attachment.focus);
 | 
			
		||||
        if (attachment.focus != null && attachment.focus.trim().length() > 0) {
 | 
			
		||||
            focusBody = RequestBody.create(MediaType.parse("text/plain"), attachment.focus);
 | 
			
		||||
        }
 | 
			
		||||
        Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(dataPost.token, fileMultipartBody, null, descriptionBody, focusBody);
 | 
			
		||||
 | 
			
		||||
        if (attachmentCall != null) {
 | 
			
		||||
            try {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,7 +127,11 @@ public class StatusesVM extends AndroidViewModel {
 | 
			
		|||
            if (description != null && description.trim().length() > 0) {
 | 
			
		||||
                descriptionBody = RequestBody.create(MediaType.parse("text/plain"), description);
 | 
			
		||||
            }
 | 
			
		||||
            Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(token, fileMultipartBody, thumbnailMultipartBody, descriptionBody, focus);
 | 
			
		||||
            RequestBody focusBody = null;
 | 
			
		||||
            if (focus != null && focus.trim().length() > 0) {
 | 
			
		||||
                focusBody = RequestBody.create(MediaType.parse("text/plain"), focus);
 | 
			
		||||
            }
 | 
			
		||||
            Call<Attachment> attachmentCall = mastodonStatusesService.postMedia(token, fileMultipartBody, thumbnailMultipartBody, descriptionBody, focusBody);
 | 
			
		||||
            Attachment attachment = null;
 | 
			
		||||
            if (attachmentCall != null) {
 | 
			
		||||
                try {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +0,0 @@
 | 
			
		|||
apply plugin: 'com.android.library'
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
 | 
			
		||||
    compileSdk 31
 | 
			
		||||
    defaultConfig {
 | 
			
		||||
        minSdkVersion 14
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    lintOptions {
 | 
			
		||||
        abortOnError false
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api 'androidx.appcompat:appcompat:1.4.2'
 | 
			
		||||
    implementation "androidx.exifinterface:exifinterface:1.3.3"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
<manifest package="com.theartofdev.edmodo.cropper">
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,354 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.AsyncTask;
 | 
			
		||||
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Task to crop bitmap asynchronously from the UI thread.
 | 
			
		||||
 */
 | 
			
		||||
final class BitmapCroppingWorkerTask
 | 
			
		||||
        extends AsyncTask<Void, Void, BitmapCroppingWorkerTask.Result> {
 | 
			
		||||
 | 
			
		||||
    // region: Fields and Consts
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Use a WeakReference to ensure the ImageView can be garbage collected
 | 
			
		||||
     */
 | 
			
		||||
    private final WeakReference<CropImageView> mCropImageViewReference;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the bitmap to crop
 | 
			
		||||
     */
 | 
			
		||||
    private final Bitmap mBitmap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The Android URI of the image to load
 | 
			
		||||
     */
 | 
			
		||||
    private final Uri mUri;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The context of the crop image view widget used for loading of bitmap by Android URI
 | 
			
		||||
     */
 | 
			
		||||
    private final Context mContext;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Required cropping 4 points (x0,y0,x1,y1,x2,y2,x3,y3)
 | 
			
		||||
     */
 | 
			
		||||
    private final float[] mCropPoints;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Degrees the image was rotated after loading
 | 
			
		||||
     */
 | 
			
		||||
    private final int mDegreesRotated;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the original width of the image to be cropped (for image loaded from URI)
 | 
			
		||||
     */
 | 
			
		||||
    private final int mOrgWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the original height of the image to be cropped (for image loaded from URI)
 | 
			
		||||
     */
 | 
			
		||||
    private final int mOrgHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * is there is fixed aspect ratio for the crop rectangle
 | 
			
		||||
     */
 | 
			
		||||
    private final boolean mFixAspectRatio;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the X aspect ration of the crop rectangle
 | 
			
		||||
     */
 | 
			
		||||
    private final int mAspectRatioX;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the Y aspect ration of the crop rectangle
 | 
			
		||||
     */
 | 
			
		||||
    private final int mAspectRatioY;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * required width of the cropping image
 | 
			
		||||
     */
 | 
			
		||||
    private final int mReqWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * required height of the cropping image
 | 
			
		||||
     */
 | 
			
		||||
    private final int mReqHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * is the image flipped horizontally
 | 
			
		||||
     */
 | 
			
		||||
    private final boolean mFlipHorizontally;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * is the image flipped vertically
 | 
			
		||||
     */
 | 
			
		||||
    private final boolean mFlipVertically;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The option to handle requested width/height
 | 
			
		||||
     */
 | 
			
		||||
    private final CropImageView.RequestSizeOptions mReqSizeOptions;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the Android Uri to save the cropped image to
 | 
			
		||||
     */
 | 
			
		||||
    private final Uri mSaveUri;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the compression format to use when writing the image
 | 
			
		||||
     */
 | 
			
		||||
    private final Bitmap.CompressFormat mSaveCompressFormat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the quality (if applicable) to use when writing the image (0 - 100)
 | 
			
		||||
     */
 | 
			
		||||
    private final int mSaveCompressQuality;
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    BitmapCroppingWorkerTask(
 | 
			
		||||
            CropImageView cropImageView,
 | 
			
		||||
            Bitmap bitmap,
 | 
			
		||||
            float[] cropPoints,
 | 
			
		||||
            int degreesRotated,
 | 
			
		||||
            boolean fixAspectRatio,
 | 
			
		||||
            int aspectRatioX,
 | 
			
		||||
            int aspectRatioY,
 | 
			
		||||
            int reqWidth,
 | 
			
		||||
            int reqHeight,
 | 
			
		||||
            boolean flipHorizontally,
 | 
			
		||||
            boolean flipVertically,
 | 
			
		||||
            CropImageView.RequestSizeOptions options,
 | 
			
		||||
            Uri saveUri,
 | 
			
		||||
            Bitmap.CompressFormat saveCompressFormat,
 | 
			
		||||
            int saveCompressQuality) {
 | 
			
		||||
 | 
			
		||||
        mCropImageViewReference = new WeakReference<>(cropImageView);
 | 
			
		||||
        mContext = cropImageView.getContext();
 | 
			
		||||
        mBitmap = bitmap;
 | 
			
		||||
        mCropPoints = cropPoints;
 | 
			
		||||
        mUri = null;
 | 
			
		||||
        mDegreesRotated = degreesRotated;
 | 
			
		||||
        mFixAspectRatio = fixAspectRatio;
 | 
			
		||||
        mAspectRatioX = aspectRatioX;
 | 
			
		||||
        mAspectRatioY = aspectRatioY;
 | 
			
		||||
        mReqWidth = reqWidth;
 | 
			
		||||
        mReqHeight = reqHeight;
 | 
			
		||||
        mFlipHorizontally = flipHorizontally;
 | 
			
		||||
        mFlipVertically = flipVertically;
 | 
			
		||||
        mReqSizeOptions = options;
 | 
			
		||||
        mSaveUri = saveUri;
 | 
			
		||||
        mSaveCompressFormat = saveCompressFormat;
 | 
			
		||||
        mSaveCompressQuality = saveCompressQuality;
 | 
			
		||||
        mOrgWidth = 0;
 | 
			
		||||
        mOrgHeight = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BitmapCroppingWorkerTask(
 | 
			
		||||
            CropImageView cropImageView,
 | 
			
		||||
            Uri uri,
 | 
			
		||||
            float[] cropPoints,
 | 
			
		||||
            int degreesRotated,
 | 
			
		||||
            int orgWidth,
 | 
			
		||||
            int orgHeight,
 | 
			
		||||
            boolean fixAspectRatio,
 | 
			
		||||
            int aspectRatioX,
 | 
			
		||||
            int aspectRatioY,
 | 
			
		||||
            int reqWidth,
 | 
			
		||||
            int reqHeight,
 | 
			
		||||
            boolean flipHorizontally,
 | 
			
		||||
            boolean flipVertically,
 | 
			
		||||
            CropImageView.RequestSizeOptions options,
 | 
			
		||||
            Uri saveUri,
 | 
			
		||||
            Bitmap.CompressFormat saveCompressFormat,
 | 
			
		||||
            int saveCompressQuality) {
 | 
			
		||||
 | 
			
		||||
        mCropImageViewReference = new WeakReference<>(cropImageView);
 | 
			
		||||
        mContext = cropImageView.getContext();
 | 
			
		||||
        mUri = uri;
 | 
			
		||||
        mCropPoints = cropPoints;
 | 
			
		||||
        mDegreesRotated = degreesRotated;
 | 
			
		||||
        mFixAspectRatio = fixAspectRatio;
 | 
			
		||||
        mAspectRatioX = aspectRatioX;
 | 
			
		||||
        mAspectRatioY = aspectRatioY;
 | 
			
		||||
        mOrgWidth = orgWidth;
 | 
			
		||||
        mOrgHeight = orgHeight;
 | 
			
		||||
        mReqWidth = reqWidth;
 | 
			
		||||
        mReqHeight = reqHeight;
 | 
			
		||||
        mFlipHorizontally = flipHorizontally;
 | 
			
		||||
        mFlipVertically = flipVertically;
 | 
			
		||||
        mReqSizeOptions = options;
 | 
			
		||||
        mSaveUri = saveUri;
 | 
			
		||||
        mSaveCompressFormat = saveCompressFormat;
 | 
			
		||||
        mSaveCompressQuality = saveCompressQuality;
 | 
			
		||||
        mBitmap = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The Android URI that this task is currently loading.
 | 
			
		||||
     */
 | 
			
		||||
    public Uri getUri() {
 | 
			
		||||
        return mUri;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Crop image in background.
 | 
			
		||||
     *
 | 
			
		||||
     * @param params ignored
 | 
			
		||||
     * @return the decoded bitmap data
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected BitmapCroppingWorkerTask.Result doInBackground(Void... params) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (!isCancelled()) {
 | 
			
		||||
 | 
			
		||||
                BitmapUtils.BitmapSampled bitmapSampled;
 | 
			
		||||
                if (mUri != null) {
 | 
			
		||||
                    bitmapSampled =
 | 
			
		||||
                            BitmapUtils.cropBitmap(
 | 
			
		||||
                                    mContext,
 | 
			
		||||
                                    mUri,
 | 
			
		||||
                                    mCropPoints,
 | 
			
		||||
                                    mDegreesRotated,
 | 
			
		||||
                                    mOrgWidth,
 | 
			
		||||
                                    mOrgHeight,
 | 
			
		||||
                                    mFixAspectRatio,
 | 
			
		||||
                                    mAspectRatioX,
 | 
			
		||||
                                    mAspectRatioY,
 | 
			
		||||
                                    mReqWidth,
 | 
			
		||||
                                    mReqHeight,
 | 
			
		||||
                                    mFlipHorizontally,
 | 
			
		||||
                                    mFlipVertically);
 | 
			
		||||
                } else if (mBitmap != null) {
 | 
			
		||||
                    bitmapSampled =
 | 
			
		||||
                            BitmapUtils.cropBitmapObjectHandleOOM(
 | 
			
		||||
                                    mBitmap,
 | 
			
		||||
                                    mCropPoints,
 | 
			
		||||
                                    mDegreesRotated,
 | 
			
		||||
                                    mFixAspectRatio,
 | 
			
		||||
                                    mAspectRatioX,
 | 
			
		||||
                                    mAspectRatioY,
 | 
			
		||||
                                    mFlipHorizontally,
 | 
			
		||||
                                    mFlipVertically);
 | 
			
		||||
                } else {
 | 
			
		||||
                    return new Result((Bitmap) null, 1);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Bitmap bitmap =
 | 
			
		||||
                        BitmapUtils.resizeBitmap(bitmapSampled.bitmap, mReqWidth, mReqHeight, mReqSizeOptions);
 | 
			
		||||
 | 
			
		||||
                if (mSaveUri == null) {
 | 
			
		||||
                    return new Result(bitmap, bitmapSampled.sampleSize);
 | 
			
		||||
                } else {
 | 
			
		||||
                    BitmapUtils.writeBitmapToUri(
 | 
			
		||||
                            mContext, bitmap, mSaveUri, mSaveCompressFormat, mSaveCompressQuality);
 | 
			
		||||
                    if (bitmap != null) {
 | 
			
		||||
                        bitmap.recycle();
 | 
			
		||||
                    }
 | 
			
		||||
                    return new Result(mSaveUri, bitmapSampled.sampleSize);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return new Result(e, mSaveUri != null);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Once complete, see if ImageView is still around and set bitmap.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result the result of bitmap cropping
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onPostExecute(Result result) {
 | 
			
		||||
        if (result != null) {
 | 
			
		||||
            boolean completeCalled = false;
 | 
			
		||||
            if (!isCancelled()) {
 | 
			
		||||
                CropImageView cropImageView = mCropImageViewReference.get();
 | 
			
		||||
                if (cropImageView != null) {
 | 
			
		||||
                    completeCalled = true;
 | 
			
		||||
                    cropImageView.onImageCroppingAsyncComplete(result);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!completeCalled && result.bitmap != null) {
 | 
			
		||||
                // fast release of unused bitmap
 | 
			
		||||
                result.bitmap.recycle();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // region: Inner class: Result
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The result of BitmapCroppingWorkerTask async loading.
 | 
			
		||||
     */
 | 
			
		||||
    static final class Result {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The cropped bitmap
 | 
			
		||||
         */
 | 
			
		||||
        public final Bitmap bitmap;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The saved cropped bitmap uri
 | 
			
		||||
         */
 | 
			
		||||
        public final Uri uri;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The error that occurred during async bitmap cropping.
 | 
			
		||||
         */
 | 
			
		||||
        final Exception error;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * is the cropping request was to get a bitmap or to save it to uri
 | 
			
		||||
         */
 | 
			
		||||
        final boolean isSave;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * sample size used creating the crop bitmap to lower its size
 | 
			
		||||
         */
 | 
			
		||||
        final int sampleSize;
 | 
			
		||||
 | 
			
		||||
        Result(Bitmap bitmap, int sampleSize) {
 | 
			
		||||
            this.bitmap = bitmap;
 | 
			
		||||
            this.uri = null;
 | 
			
		||||
            this.error = null;
 | 
			
		||||
            this.isSave = false;
 | 
			
		||||
            this.sampleSize = sampleSize;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Result(Uri uri, int sampleSize) {
 | 
			
		||||
            this.bitmap = null;
 | 
			
		||||
            this.uri = uri;
 | 
			
		||||
            this.error = null;
 | 
			
		||||
            this.isSave = true;
 | 
			
		||||
            this.sampleSize = sampleSize;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Result(Exception error, boolean isSave) {
 | 
			
		||||
            this.bitmap = null;
 | 
			
		||||
            this.uri = null;
 | 
			
		||||
            this.error = error;
 | 
			
		||||
            this.isSave = isSave;
 | 
			
		||||
            this.sampleSize = 1;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,176 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.AsyncTask;
 | 
			
		||||
import android.util.DisplayMetrics;
 | 
			
		||||
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Task to load bitmap asynchronously from the UI thread.
 | 
			
		||||
 */
 | 
			
		||||
final class BitmapLoadingWorkerTask extends AsyncTask<Void, Void, BitmapLoadingWorkerTask.Result> {
 | 
			
		||||
 | 
			
		||||
    // region: Fields and Consts
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Use a WeakReference to ensure the ImageView can be garbage collected
 | 
			
		||||
     */
 | 
			
		||||
    private final WeakReference<CropImageView> mCropImageViewReference;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The Android URI of the image to load
 | 
			
		||||
     */
 | 
			
		||||
    private final Uri mUri;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The context of the crop image view widget used for loading of bitmap by Android URI
 | 
			
		||||
     */
 | 
			
		||||
    private final Context mContext;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * required width of the cropping image after density adjustment
 | 
			
		||||
     */
 | 
			
		||||
    private final int mWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * required height of the cropping image after density adjustment
 | 
			
		||||
     */
 | 
			
		||||
    private final int mHeight;
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri) {
 | 
			
		||||
        mUri = uri;
 | 
			
		||||
        mCropImageViewReference = new WeakReference<>(cropImageView);
 | 
			
		||||
 | 
			
		||||
        mContext = cropImageView.getContext();
 | 
			
		||||
 | 
			
		||||
        DisplayMetrics metrics = cropImageView.getResources().getDisplayMetrics();
 | 
			
		||||
        double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1;
 | 
			
		||||
        mWidth = (int) (metrics.widthPixels * densityAdj);
 | 
			
		||||
        mHeight = (int) (metrics.heightPixels * densityAdj);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The Android URI that this task is currently loading.
 | 
			
		||||
     */
 | 
			
		||||
    public Uri getUri() {
 | 
			
		||||
        return mUri;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decode image in background.
 | 
			
		||||
     *
 | 
			
		||||
     * @param params ignored
 | 
			
		||||
     * @return the decoded bitmap data
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Result doInBackground(Void... params) {
 | 
			
		||||
        try {
 | 
			
		||||
            if (!isCancelled()) {
 | 
			
		||||
 | 
			
		||||
                BitmapUtils.BitmapSampled decodeResult =
 | 
			
		||||
                        BitmapUtils.decodeSampledBitmap(mContext, mUri, mWidth, mHeight);
 | 
			
		||||
 | 
			
		||||
                if (!isCancelled()) {
 | 
			
		||||
 | 
			
		||||
                    BitmapUtils.RotateBitmapResult rotateResult =
 | 
			
		||||
                            BitmapUtils.rotateBitmapByExif(decodeResult.bitmap, mContext, mUri);
 | 
			
		||||
 | 
			
		||||
                    return new Result(
 | 
			
		||||
                            mUri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return null;
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            return new Result(mUri, e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Once complete, see if ImageView is still around and set bitmap.
 | 
			
		||||
     *
 | 
			
		||||
     * @param result the result of bitmap loading
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void onPostExecute(Result result) {
 | 
			
		||||
        if (result != null) {
 | 
			
		||||
            boolean completeCalled = false;
 | 
			
		||||
            if (!isCancelled()) {
 | 
			
		||||
                CropImageView cropImageView = mCropImageViewReference.get();
 | 
			
		||||
                if (cropImageView != null) {
 | 
			
		||||
                    completeCalled = true;
 | 
			
		||||
                    cropImageView.onSetImageUriAsyncComplete(result);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (!completeCalled && result.bitmap != null) {
 | 
			
		||||
                // fast release of unused bitmap
 | 
			
		||||
                result.bitmap.recycle();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // region: Inner class: Result
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The result of BitmapLoadingWorkerTask async loading.
 | 
			
		||||
     */
 | 
			
		||||
    public static final class Result {
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The Android URI of the image to load
 | 
			
		||||
         */
 | 
			
		||||
        public final Uri uri;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The loaded bitmap
 | 
			
		||||
         */
 | 
			
		||||
        public final Bitmap bitmap;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The sample size used to load the given bitmap
 | 
			
		||||
         */
 | 
			
		||||
        public final int loadSampleSize;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The degrees the image was rotated
 | 
			
		||||
         */
 | 
			
		||||
        public final int degreesRotated;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The error that occurred during async bitmap loading.
 | 
			
		||||
         */
 | 
			
		||||
        public final Exception error;
 | 
			
		||||
 | 
			
		||||
        Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotated) {
 | 
			
		||||
            this.uri = uri;
 | 
			
		||||
            this.bitmap = bitmap;
 | 
			
		||||
            this.loadSampleSize = loadSampleSize;
 | 
			
		||||
            this.degreesRotated = degreesRotated;
 | 
			
		||||
            this.error = null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Result(Uri uri, Exception error) {
 | 
			
		||||
            this.uri = uri;
 | 
			
		||||
            this.bitmap = null;
 | 
			
		||||
            this.loadSampleSize = 0;
 | 
			
		||||
            this.degreesRotated = 0;
 | 
			
		||||
            this.error = error;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,923 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.content.ContentResolver;
 | 
			
		||||
import android.content.Context;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.BitmapFactory;
 | 
			
		||||
import android.graphics.BitmapRegionDecoder;
 | 
			
		||||
import android.graphics.Matrix;
 | 
			
		||||
import android.graphics.Rect;
 | 
			
		||||
import android.graphics.RectF;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.util.Log;
 | 
			
		||||
import android.util.Pair;
 | 
			
		||||
 | 
			
		||||
import androidx.exifinterface.media.ExifInterface;
 | 
			
		||||
 | 
			
		||||
import java.io.Closeable;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileNotFoundException;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.OutputStream;
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
 | 
			
		||||
import javax.microedition.khronos.egl.EGL10;
 | 
			
		||||
import javax.microedition.khronos.egl.EGLConfig;
 | 
			
		||||
import javax.microedition.khronos.egl.EGLContext;
 | 
			
		||||
import javax.microedition.khronos.egl.EGLDisplay;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility class that deals with operations with an ImageView.
 | 
			
		||||
 */
 | 
			
		||||
final class BitmapUtils {
 | 
			
		||||
 | 
			
		||||
  static final Rect EMPTY_RECT = new Rect();
 | 
			
		||||
 | 
			
		||||
  static final RectF EMPTY_RECT_F = new RectF();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reusable rectangle for general internal usage
 | 
			
		||||
   */
 | 
			
		||||
  static final RectF RECT = new RectF();
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reusable point for general internal usage
 | 
			
		||||
   */
 | 
			
		||||
  static final float[] POINTS = new float[6];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Reusable point for general internal usage
 | 
			
		||||
   */
 | 
			
		||||
  static final float[] POINTS2 = new float[6];
 | 
			
		||||
  /**
 | 
			
		||||
   * used to save bitmaps during state save and restore so not to reload them.
 | 
			
		||||
   */
 | 
			
		||||
  static Pair<String, WeakReference<Bitmap>> mStateBitmap;
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to know the max texture size allowed to be rendered
 | 
			
		||||
   */
 | 
			
		||||
  private static int mMaxTextureSize;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rotate the given image by reading the Exif value of the image (uri).<br>
 | 
			
		||||
   * If no rotation is required the image will not be rotated.<br>
 | 
			
		||||
   * New bitmap is created and the old one is recycled.
 | 
			
		||||
   */
 | 
			
		||||
  static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, Context context, Uri uri) {
 | 
			
		||||
    ExifInterface ei = null;
 | 
			
		||||
    try {
 | 
			
		||||
      InputStream is = context.getContentResolver().openInputStream(uri);
 | 
			
		||||
      if (is != null) {
 | 
			
		||||
        ei = new ExifInterface(is);
 | 
			
		||||
        is.close();
 | 
			
		||||
      }
 | 
			
		||||
    } catch (Exception ignored) {
 | 
			
		||||
    }
 | 
			
		||||
    return ei != null ? rotateBitmapByExif(bitmap, ei) : new RotateBitmapResult(bitmap, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rotate the given image by given Exif value.<br>
 | 
			
		||||
   * If no rotation is required the image will not be rotated.<br>
 | 
			
		||||
   * New bitmap is created and the old one is recycled.
 | 
			
		||||
   */
 | 
			
		||||
  static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, ExifInterface exif) {
 | 
			
		||||
    int degrees;
 | 
			
		||||
    int orientation =
 | 
			
		||||
            exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
 | 
			
		||||
    switch (orientation) {
 | 
			
		||||
      case ExifInterface.ORIENTATION_ROTATE_90:
 | 
			
		||||
        degrees = 90;
 | 
			
		||||
        break;
 | 
			
		||||
      case ExifInterface.ORIENTATION_ROTATE_180:
 | 
			
		||||
        degrees = 180;
 | 
			
		||||
        break;
 | 
			
		||||
      case ExifInterface.ORIENTATION_ROTATE_270:
 | 
			
		||||
        degrees = 270;
 | 
			
		||||
        break;
 | 
			
		||||
      default:
 | 
			
		||||
        degrees = 0;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return new RotateBitmapResult(bitmap, degrees);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decode bitmap from stream using sampling to get bitmap with the requested limit.
 | 
			
		||||
   */
 | 
			
		||||
  static BitmapSampled decodeSampledBitmap(Context context, Uri uri, int reqWidth, int reqHeight) {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      ContentResolver resolver = context.getContentResolver();
 | 
			
		||||
 | 
			
		||||
      // First decode with inJustDecodeBounds=true to check dimensions
 | 
			
		||||
      BitmapFactory.Options options = decodeImageForOption(resolver, uri);
 | 
			
		||||
 | 
			
		||||
      if (options.outWidth == -1 && options.outHeight == -1)
 | 
			
		||||
        throw new RuntimeException("File is not a picture");
 | 
			
		||||
 | 
			
		||||
      // Calculate inSampleSize
 | 
			
		||||
      options.inSampleSize =
 | 
			
		||||
              Math.max(
 | 
			
		||||
                      calculateInSampleSizeByReqestedSize(
 | 
			
		||||
                              options.outWidth, options.outHeight, reqWidth, reqHeight),
 | 
			
		||||
                      calculateInSampleSizeByMaxTextureSize(options.outWidth, options.outHeight));
 | 
			
		||||
 | 
			
		||||
      // Decode bitmap with inSampleSize set
 | 
			
		||||
      Bitmap bitmap = decodeImage(resolver, uri, options);
 | 
			
		||||
 | 
			
		||||
      return new BitmapSampled(bitmap, options.inSampleSize);
 | 
			
		||||
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      throw new RuntimeException(
 | 
			
		||||
              "Failed to load sampled bitmap: " + uri + "\r\n" + e.getMessage(), e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Crop image bitmap from given bitmap using the given points in the original bitmap and the given
 | 
			
		||||
   * rotation.<br>
 | 
			
		||||
   * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the
 | 
			
		||||
   * image that contains the requires rectangle, rotate and then crop again a sub rectangle.<br>
 | 
			
		||||
   * If crop fails due to OOM we scale the cropping image by 0.5 every time it fails until it is
 | 
			
		||||
   * small enough.
 | 
			
		||||
   */
 | 
			
		||||
  static BitmapSampled cropBitmapObjectHandleOOM(
 | 
			
		||||
          Bitmap bitmap,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY,
 | 
			
		||||
          boolean flipHorizontally,
 | 
			
		||||
          boolean flipVertically) {
 | 
			
		||||
    int scale = 1;
 | 
			
		||||
    while (true) {
 | 
			
		||||
      try {
 | 
			
		||||
        Bitmap cropBitmap =
 | 
			
		||||
                cropBitmapObjectWithScale(
 | 
			
		||||
                        bitmap,
 | 
			
		||||
                        points,
 | 
			
		||||
                        degreesRotated,
 | 
			
		||||
                        fixAspectRatio,
 | 
			
		||||
                        aspectRatioX,
 | 
			
		||||
                        aspectRatioY,
 | 
			
		||||
                        1 / (float) scale,
 | 
			
		||||
                        flipHorizontally,
 | 
			
		||||
                        flipVertically);
 | 
			
		||||
        return new BitmapSampled(cropBitmap, scale);
 | 
			
		||||
      } catch (OutOfMemoryError e) {
 | 
			
		||||
        scale *= 2;
 | 
			
		||||
        if (scale > 8) {
 | 
			
		||||
          throw e;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Crop image bitmap from given bitmap using the given points in the original bitmap and the given
 | 
			
		||||
   * rotation.<br>
 | 
			
		||||
   * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the
 | 
			
		||||
   * image that contains the requires rectangle, rotate and then crop again a sub rectangle.
 | 
			
		||||
   *
 | 
			
		||||
   * @param scale how much to scale the cropped image part, use 0.5 to lower the image by half (OOM
 | 
			
		||||
   *              handling)
 | 
			
		||||
   */
 | 
			
		||||
  private static Bitmap cropBitmapObjectWithScale(
 | 
			
		||||
          Bitmap bitmap,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY,
 | 
			
		||||
          float scale,
 | 
			
		||||
          boolean flipHorizontally,
 | 
			
		||||
          boolean flipVertically) {
 | 
			
		||||
 | 
			
		||||
    // get the rectangle in original image that contains the required cropped area (larger for non
 | 
			
		||||
    // rectangular crop)
 | 
			
		||||
    Rect rect =
 | 
			
		||||
            getRectFromPoints(
 | 
			
		||||
                    points,
 | 
			
		||||
                    bitmap.getWidth(),
 | 
			
		||||
                    bitmap.getHeight(),
 | 
			
		||||
                    fixAspectRatio,
 | 
			
		||||
                    aspectRatioX,
 | 
			
		||||
                    aspectRatioY);
 | 
			
		||||
 | 
			
		||||
    // crop and rotate the cropped image in one operation
 | 
			
		||||
    Matrix matrix = new Matrix();
 | 
			
		||||
    matrix.setRotate(degreesRotated, bitmap.getWidth() / 2, bitmap.getHeight() / 2);
 | 
			
		||||
    matrix.postScale(flipHorizontally ? -scale : scale, flipVertically ? -scale : scale);
 | 
			
		||||
    Bitmap result =
 | 
			
		||||
            Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), matrix, true);
 | 
			
		||||
 | 
			
		||||
    if (result == bitmap) {
 | 
			
		||||
      // corner case when all bitmap is selected, no worth optimizing for it
 | 
			
		||||
      result = bitmap.copy(bitmap.getConfig(), false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping
 | 
			
		||||
    if (degreesRotated % 90 != 0) {
 | 
			
		||||
 | 
			
		||||
      // extra crop because non rectangular crop cannot be done directly on the image without
 | 
			
		||||
      // rotating first
 | 
			
		||||
      result =
 | 
			
		||||
              cropForRotatedImage(
 | 
			
		||||
                      result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Crop image bitmap from URI by decoding it with specific width and height to down-sample if
 | 
			
		||||
   * required.<br>
 | 
			
		||||
   * Additionally if OOM is thrown try to increase the sampling (2,4,8).
 | 
			
		||||
   */
 | 
			
		||||
  static BitmapSampled cropBitmap(
 | 
			
		||||
          Context context,
 | 
			
		||||
          Uri loadedImageUri,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          int orgWidth,
 | 
			
		||||
          int orgHeight,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY,
 | 
			
		||||
          int reqWidth,
 | 
			
		||||
          int reqHeight,
 | 
			
		||||
          boolean flipHorizontally,
 | 
			
		||||
          boolean flipVertically) {
 | 
			
		||||
    int sampleMulti = 1;
 | 
			
		||||
    while (true) {
 | 
			
		||||
      try {
 | 
			
		||||
        // if successful, just return the resulting bitmap
 | 
			
		||||
        return cropBitmap(
 | 
			
		||||
                context,
 | 
			
		||||
                loadedImageUri,
 | 
			
		||||
                points,
 | 
			
		||||
                degreesRotated,
 | 
			
		||||
                orgWidth,
 | 
			
		||||
                orgHeight,
 | 
			
		||||
                fixAspectRatio,
 | 
			
		||||
                aspectRatioX,
 | 
			
		||||
                aspectRatioY,
 | 
			
		||||
                reqWidth,
 | 
			
		||||
                reqHeight,
 | 
			
		||||
                flipHorizontally,
 | 
			
		||||
                flipVertically,
 | 
			
		||||
                sampleMulti);
 | 
			
		||||
      } catch (OutOfMemoryError e) {
 | 
			
		||||
        // if OOM try to increase the sampling to lower the memory usage
 | 
			
		||||
        sampleMulti *= 2;
 | 
			
		||||
        if (sampleMulti > 16) {
 | 
			
		||||
          throw new RuntimeException(
 | 
			
		||||
                  "Failed to handle OOM by sampling ("
 | 
			
		||||
                          + sampleMulti
 | 
			
		||||
                          + "): "
 | 
			
		||||
                          + loadedImageUri
 | 
			
		||||
                          + "\r\n"
 | 
			
		||||
                          + e.getMessage(),
 | 
			
		||||
                  e);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get left value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectLeft(float[] points) {
 | 
			
		||||
    return Math.min(Math.min(Math.min(points[0], points[2]), points[4]), points[6]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get top value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectTop(float[] points) {
 | 
			
		||||
    return Math.min(Math.min(Math.min(points[1], points[3]), points[5]), points[7]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get right value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectRight(float[] points) {
 | 
			
		||||
    return Math.max(Math.max(Math.max(points[0], points[2]), points[4]), points[6]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get bottom value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectBottom(float[] points) {
 | 
			
		||||
    return Math.max(Math.max(Math.max(points[1], points[3]), points[5]), points[7]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get width of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectWidth(float[] points) {
 | 
			
		||||
    return getRectRight(points) - getRectLeft(points);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get height of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectHeight(float[] points) {
 | 
			
		||||
    return getRectBottom(points) - getRectTop(points);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get horizontal center value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectCenterX(float[] points) {
 | 
			
		||||
    return (getRectRight(points) + getRectLeft(points)) / 2f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get vertical center value of the bounding rectangle of the given points.
 | 
			
		||||
   */
 | 
			
		||||
  static float getRectCenterY(float[] points) {
 | 
			
		||||
    return (getRectBottom(points) + getRectTop(points)) / 2f;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get a rectangle for the given 4 points (x0,y0,x1,y1,x2,y2,x3,y3) by finding the min/max 2
 | 
			
		||||
   * points that contains the given 4 points and is a straight rectangle.
 | 
			
		||||
   */
 | 
			
		||||
  static Rect getRectFromPoints(
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int imageWidth,
 | 
			
		||||
          int imageHeight,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY) {
 | 
			
		||||
    int left = Math.round(Math.max(0, getRectLeft(points)));
 | 
			
		||||
    int top = Math.round(Math.max(0, getRectTop(points)));
 | 
			
		||||
    int right = Math.round(Math.min(imageWidth, getRectRight(points)));
 | 
			
		||||
    int bottom = Math.round(Math.min(imageHeight, getRectBottom(points)));
 | 
			
		||||
 | 
			
		||||
    Rect rect = new Rect(left, top, right, bottom);
 | 
			
		||||
    if (fixAspectRatio) {
 | 
			
		||||
      fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return rect;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fix the given rectangle if it doesn't confirm to aspect ration rule.<br>
 | 
			
		||||
   * Make sure that width and height are equal if 1:1 fixed aspect ratio is requested.
 | 
			
		||||
   */
 | 
			
		||||
  private static void fixRectForAspectRatio(Rect rect, int aspectRatioX, int aspectRatioY) {
 | 
			
		||||
    if (aspectRatioX == aspectRatioY && rect.width() != rect.height()) {
 | 
			
		||||
      if (rect.height() > rect.width()) {
 | 
			
		||||
        rect.bottom -= rect.height() - rect.width();
 | 
			
		||||
      } else {
 | 
			
		||||
        rect.right -= rect.width() - rect.height();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Write given bitmap to a temp file. If file already exists no-op as we already saved the file in
 | 
			
		||||
   * this session. Uses JPEG 95% compression.
 | 
			
		||||
   *
 | 
			
		||||
   * @param uri the uri to write the bitmap to, if null
 | 
			
		||||
   * @return the uri where the image was saved in, either the given uri or new pointing to temp
 | 
			
		||||
   * file.
 | 
			
		||||
   */
 | 
			
		||||
  static Uri writeTempStateStoreBitmap(Context context, Bitmap bitmap, Uri uri) {
 | 
			
		||||
    try {
 | 
			
		||||
      boolean needSave = true;
 | 
			
		||||
      if (uri == null) {
 | 
			
		||||
        uri =
 | 
			
		||||
                Uri.fromFile(
 | 
			
		||||
                        File.createTempFile("aic_state_store_temp", ".jpg", context.getCacheDir()));
 | 
			
		||||
      } else if (new File(uri.getPath()).exists()) {
 | 
			
		||||
        needSave = false;
 | 
			
		||||
      }
 | 
			
		||||
      if (needSave) {
 | 
			
		||||
        writeBitmapToUri(context, bitmap, uri, Bitmap.CompressFormat.JPEG, 95);
 | 
			
		||||
      }
 | 
			
		||||
      return uri;
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Log.w("AIC", "Failed to write bitmap to temp file for image-cropper save instance state", e);
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Write the given bitmap to the given uri using the given compression.
 | 
			
		||||
   */
 | 
			
		||||
  static void writeBitmapToUri(
 | 
			
		||||
          Context context,
 | 
			
		||||
          Bitmap bitmap,
 | 
			
		||||
          Uri uri,
 | 
			
		||||
          Bitmap.CompressFormat compressFormat,
 | 
			
		||||
          int compressQuality)
 | 
			
		||||
          throws FileNotFoundException {
 | 
			
		||||
    OutputStream outputStream = null;
 | 
			
		||||
    try {
 | 
			
		||||
      outputStream = context.getContentResolver().openOutputStream(uri);
 | 
			
		||||
      bitmap.compress(compressFormat, compressQuality, outputStream);
 | 
			
		||||
    } finally {
 | 
			
		||||
      closeSafe(outputStream);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Resize the given bitmap to the given width/height by the given option.<br>
 | 
			
		||||
   */
 | 
			
		||||
  static Bitmap resizeBitmap(
 | 
			
		||||
          Bitmap bitmap, int reqWidth, int reqHeight, CropImageView.RequestSizeOptions options) {
 | 
			
		||||
    try {
 | 
			
		||||
      if (reqWidth > 0
 | 
			
		||||
              && reqHeight > 0
 | 
			
		||||
              && (options == CropImageView.RequestSizeOptions.RESIZE_FIT
 | 
			
		||||
              || options == CropImageView.RequestSizeOptions.RESIZE_INSIDE
 | 
			
		||||
              || options == CropImageView.RequestSizeOptions.RESIZE_EXACT)) {
 | 
			
		||||
 | 
			
		||||
        Bitmap resized = null;
 | 
			
		||||
        if (options == CropImageView.RequestSizeOptions.RESIZE_EXACT) {
 | 
			
		||||
          resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
 | 
			
		||||
        } else {
 | 
			
		||||
          int width = bitmap.getWidth();
 | 
			
		||||
          int height = bitmap.getHeight();
 | 
			
		||||
          float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);
 | 
			
		||||
          if (scale > 1 || options == CropImageView.RequestSizeOptions.RESIZE_FIT) {
 | 
			
		||||
            resized =
 | 
			
		||||
                    Bitmap.createScaledBitmap(
 | 
			
		||||
                            bitmap, (int) (width / scale), (int) (height / scale), false);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (resized != null) {
 | 
			
		||||
          if (resized != bitmap) {
 | 
			
		||||
            bitmap.recycle();
 | 
			
		||||
          }
 | 
			
		||||
          return resized;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e);
 | 
			
		||||
    }
 | 
			
		||||
    return bitmap;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // region: Private methods
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Crop image bitmap from URI by decoding it with specific width and height to down-sample if
 | 
			
		||||
   * required.
 | 
			
		||||
   *
 | 
			
		||||
   * @param orgWidth    used to get rectangle from points (handle edge cases to limit rectangle)
 | 
			
		||||
   * @param orgHeight   used to get rectangle from points (handle edge cases to limit rectangle)
 | 
			
		||||
   * @param sampleMulti used to increase the sampling of the image to handle memory issues.
 | 
			
		||||
   */
 | 
			
		||||
  private static BitmapSampled cropBitmap(
 | 
			
		||||
          Context context,
 | 
			
		||||
          Uri loadedImageUri,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          int orgWidth,
 | 
			
		||||
          int orgHeight,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY,
 | 
			
		||||
          int reqWidth,
 | 
			
		||||
          int reqHeight,
 | 
			
		||||
          boolean flipHorizontally,
 | 
			
		||||
          boolean flipVertically,
 | 
			
		||||
          int sampleMulti) {
 | 
			
		||||
 | 
			
		||||
    // get the rectangle in original image that contains the required cropped area (larger for non
 | 
			
		||||
    // rectangular crop)
 | 
			
		||||
    Rect rect =
 | 
			
		||||
            getRectFromPoints(points, orgWidth, orgHeight, fixAspectRatio, aspectRatioX, aspectRatioY);
 | 
			
		||||
 | 
			
		||||
    int width = reqWidth > 0 ? reqWidth : rect.width();
 | 
			
		||||
    int height = reqHeight > 0 ? reqHeight : rect.height();
 | 
			
		||||
 | 
			
		||||
    Bitmap result = null;
 | 
			
		||||
    int sampleSize = 1;
 | 
			
		||||
    try {
 | 
			
		||||
      // decode only the required image from URI, optionally sub-sampling if reqWidth/reqHeight is
 | 
			
		||||
      // given.
 | 
			
		||||
      BitmapSampled bitmapSampled =
 | 
			
		||||
              decodeSampledBitmapRegion(context, loadedImageUri, rect, width, height, sampleMulti);
 | 
			
		||||
      result = bitmapSampled.bitmap;
 | 
			
		||||
      sampleSize = bitmapSampled.sampleSize;
 | 
			
		||||
    } catch (Exception ignored) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (result != null) {
 | 
			
		||||
      try {
 | 
			
		||||
        // rotate the decoded region by the required amount
 | 
			
		||||
        result = rotateAndFlipBitmapInt(result, degreesRotated, flipHorizontally, flipVertically);
 | 
			
		||||
 | 
			
		||||
        // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping
 | 
			
		||||
        if (degreesRotated % 90 != 0) {
 | 
			
		||||
 | 
			
		||||
          // extra crop because non rectangular crop cannot be done directly on the image without
 | 
			
		||||
          // rotating first
 | 
			
		||||
          result =
 | 
			
		||||
                  cropForRotatedImage(
 | 
			
		||||
                          result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY);
 | 
			
		||||
        }
 | 
			
		||||
      } catch (OutOfMemoryError e) {
 | 
			
		||||
        if (result != null) {
 | 
			
		||||
          result.recycle();
 | 
			
		||||
        }
 | 
			
		||||
        throw e;
 | 
			
		||||
      }
 | 
			
		||||
      return new BitmapSampled(result, sampleSize);
 | 
			
		||||
    } else {
 | 
			
		||||
      // failed to decode region, may be skia issue, try full decode and then crop
 | 
			
		||||
      return cropBitmap(
 | 
			
		||||
              context,
 | 
			
		||||
              loadedImageUri,
 | 
			
		||||
              points,
 | 
			
		||||
              degreesRotated,
 | 
			
		||||
              fixAspectRatio,
 | 
			
		||||
              aspectRatioX,
 | 
			
		||||
              aspectRatioY,
 | 
			
		||||
              sampleMulti,
 | 
			
		||||
              rect,
 | 
			
		||||
              width,
 | 
			
		||||
              height,
 | 
			
		||||
              flipHorizontally,
 | 
			
		||||
              flipVertically);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Crop bitmap by fully loading the original and then cropping it, fallback in case cropping
 | 
			
		||||
   * region failed.
 | 
			
		||||
   */
 | 
			
		||||
  private static BitmapSampled cropBitmap(
 | 
			
		||||
          Context context,
 | 
			
		||||
          Uri loadedImageUri,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY,
 | 
			
		||||
          int sampleMulti,
 | 
			
		||||
          Rect rect,
 | 
			
		||||
          int width,
 | 
			
		||||
          int height,
 | 
			
		||||
          boolean flipHorizontally,
 | 
			
		||||
          boolean flipVertically) {
 | 
			
		||||
    Bitmap result = null;
 | 
			
		||||
    int sampleSize;
 | 
			
		||||
    try {
 | 
			
		||||
      BitmapFactory.Options options = new BitmapFactory.Options();
 | 
			
		||||
      options.inSampleSize =
 | 
			
		||||
              sampleSize =
 | 
			
		||||
                      sampleMulti
 | 
			
		||||
                              * calculateInSampleSizeByReqestedSize(rect.width(), rect.height(), width, height);
 | 
			
		||||
 | 
			
		||||
      Bitmap fullBitmap = decodeImage(context.getContentResolver(), loadedImageUri, options);
 | 
			
		||||
      if (fullBitmap != null) {
 | 
			
		||||
        try {
 | 
			
		||||
          // adjust crop points by the sampling because the image is smaller
 | 
			
		||||
          float[] points2 = new float[points.length];
 | 
			
		||||
          System.arraycopy(points, 0, points2, 0, points.length);
 | 
			
		||||
          for (int i = 0; i < points2.length; i++) {
 | 
			
		||||
            points2[i] = points2[i] / options.inSampleSize;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          result =
 | 
			
		||||
                  cropBitmapObjectWithScale(
 | 
			
		||||
                          fullBitmap,
 | 
			
		||||
                          points2,
 | 
			
		||||
                          degreesRotated,
 | 
			
		||||
                          fixAspectRatio,
 | 
			
		||||
                          aspectRatioX,
 | 
			
		||||
                          aspectRatioY,
 | 
			
		||||
                          1,
 | 
			
		||||
                          flipHorizontally,
 | 
			
		||||
                          flipVertically);
 | 
			
		||||
        } finally {
 | 
			
		||||
          if (result != fullBitmap) {
 | 
			
		||||
            fullBitmap.recycle();
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (OutOfMemoryError e) {
 | 
			
		||||
      if (result != null) {
 | 
			
		||||
        result.recycle();
 | 
			
		||||
      }
 | 
			
		||||
      throw e;
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      throw new RuntimeException(
 | 
			
		||||
              "Failed to load sampled bitmap: " + loadedImageUri + "\r\n" + e.getMessage(), e);
 | 
			
		||||
    }
 | 
			
		||||
    return new BitmapSampled(result, sampleSize);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decode image from uri using "inJustDecodeBounds" to get the image dimensions.
 | 
			
		||||
   */
 | 
			
		||||
  private static BitmapFactory.Options decodeImageForOption(ContentResolver resolver, Uri uri)
 | 
			
		||||
          throws FileNotFoundException {
 | 
			
		||||
    InputStream stream = null;
 | 
			
		||||
    try {
 | 
			
		||||
      stream = resolver.openInputStream(uri);
 | 
			
		||||
      BitmapFactory.Options options = new BitmapFactory.Options();
 | 
			
		||||
      options.inJustDecodeBounds = true;
 | 
			
		||||
      BitmapFactory.decodeStream(stream, EMPTY_RECT, options);
 | 
			
		||||
      options.inJustDecodeBounds = false;
 | 
			
		||||
      return options;
 | 
			
		||||
    } finally {
 | 
			
		||||
      closeSafe(stream);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decode image from uri using given "inSampleSize", but if failed due to out-of-memory then raise
 | 
			
		||||
   * the inSampleSize until success.
 | 
			
		||||
   */
 | 
			
		||||
  private static Bitmap decodeImage(
 | 
			
		||||
          ContentResolver resolver, Uri uri, BitmapFactory.Options options)
 | 
			
		||||
          throws FileNotFoundException {
 | 
			
		||||
    do {
 | 
			
		||||
      InputStream stream = null;
 | 
			
		||||
      try {
 | 
			
		||||
        stream = resolver.openInputStream(uri);
 | 
			
		||||
        return BitmapFactory.decodeStream(stream, EMPTY_RECT, options);
 | 
			
		||||
      } catch (OutOfMemoryError e) {
 | 
			
		||||
        options.inSampleSize *= 2;
 | 
			
		||||
      } finally {
 | 
			
		||||
        closeSafe(stream);
 | 
			
		||||
      }
 | 
			
		||||
    } while (options.inSampleSize <= 512);
 | 
			
		||||
    throw new RuntimeException("Failed to decode image: " + uri);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Decode specific rectangle bitmap from stream using sampling to get bitmap with the requested
 | 
			
		||||
   * limit.
 | 
			
		||||
   *
 | 
			
		||||
   * @param sampleMulti used to increase the sampling of the image to handle memory issues.
 | 
			
		||||
   */
 | 
			
		||||
  private static BitmapSampled decodeSampledBitmapRegion(
 | 
			
		||||
          Context context, Uri uri, Rect rect, int reqWidth, int reqHeight, int sampleMulti) {
 | 
			
		||||
    InputStream stream = null;
 | 
			
		||||
    BitmapRegionDecoder decoder = null;
 | 
			
		||||
    try {
 | 
			
		||||
      BitmapFactory.Options options = new BitmapFactory.Options();
 | 
			
		||||
      options.inSampleSize =
 | 
			
		||||
              sampleMulti
 | 
			
		||||
                      * calculateInSampleSizeByReqestedSize(
 | 
			
		||||
                      rect.width(), rect.height(), reqWidth, reqHeight);
 | 
			
		||||
 | 
			
		||||
      stream = context.getContentResolver().openInputStream(uri);
 | 
			
		||||
      decoder = BitmapRegionDecoder.newInstance(stream, false);
 | 
			
		||||
      do {
 | 
			
		||||
        try {
 | 
			
		||||
          return new BitmapSampled(decoder.decodeRegion(rect, options), options.inSampleSize);
 | 
			
		||||
        } catch (OutOfMemoryError e) {
 | 
			
		||||
          options.inSampleSize *= 2;
 | 
			
		||||
        }
 | 
			
		||||
      } while (options.inSampleSize <= 512);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      throw new RuntimeException(
 | 
			
		||||
              "Failed to load sampled bitmap: " + uri + "\r\n" + e.getMessage(), e);
 | 
			
		||||
    } finally {
 | 
			
		||||
      closeSafe(stream);
 | 
			
		||||
      if (decoder != null) {
 | 
			
		||||
        decoder.recycle();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return new BitmapSampled(null, 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Special crop of bitmap rotated by not stright angle, in this case the original crop bitmap
 | 
			
		||||
   * contains parts beyond the required crop area, this method crops the already cropped and rotated
 | 
			
		||||
   * bitmap to the final rectangle.<br>
 | 
			
		||||
   * Note: rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping.
 | 
			
		||||
   */
 | 
			
		||||
  private static Bitmap cropForRotatedImage(
 | 
			
		||||
          Bitmap bitmap,
 | 
			
		||||
          float[] points,
 | 
			
		||||
          Rect rect,
 | 
			
		||||
          int degreesRotated,
 | 
			
		||||
          boolean fixAspectRatio,
 | 
			
		||||
          int aspectRatioX,
 | 
			
		||||
          int aspectRatioY) {
 | 
			
		||||
    if (degreesRotated % 90 != 0) {
 | 
			
		||||
 | 
			
		||||
      int adjLeft = 0, adjTop = 0, width = 0, height = 0;
 | 
			
		||||
      double rads = Math.toRadians(degreesRotated);
 | 
			
		||||
      int compareTo =
 | 
			
		||||
              degreesRotated < 90 || (degreesRotated > 180 && degreesRotated < 270)
 | 
			
		||||
                      ? rect.left
 | 
			
		||||
                      : rect.right;
 | 
			
		||||
      for (int i = 0; i < points.length; i += 2) {
 | 
			
		||||
        if (points[i] >= compareTo - 1 && points[i] <= compareTo + 1) {
 | 
			
		||||
          adjLeft = (int) Math.abs(Math.sin(rads) * (rect.bottom - points[i + 1]));
 | 
			
		||||
          adjTop = (int) Math.abs(Math.cos(rads) * (points[i + 1] - rect.top));
 | 
			
		||||
          width = (int) Math.abs((points[i + 1] - rect.top) / Math.sin(rads));
 | 
			
		||||
          height = (int) Math.abs((rect.bottom - points[i + 1]) / Math.cos(rads));
 | 
			
		||||
          break;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      rect.set(adjLeft, adjTop, adjLeft + width, adjTop + height);
 | 
			
		||||
      if (fixAspectRatio) {
 | 
			
		||||
        fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Bitmap bitmapTmp = bitmap;
 | 
			
		||||
      bitmap = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height());
 | 
			
		||||
      if (bitmapTmp != bitmap) {
 | 
			
		||||
        bitmapTmp.recycle();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return bitmap;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width
 | 
			
		||||
   * larger than the requested height and width.
 | 
			
		||||
   */
 | 
			
		||||
  private static int calculateInSampleSizeByReqestedSize(
 | 
			
		||||
          int width, int height, int reqWidth, int reqHeight) {
 | 
			
		||||
    int inSampleSize = 1;
 | 
			
		||||
    if (height > reqHeight || width > reqWidth) {
 | 
			
		||||
      while ((height / 2 / inSampleSize) > reqHeight && (width / 2 / inSampleSize) > reqWidth) {
 | 
			
		||||
        inSampleSize *= 2;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return inSampleSize;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width
 | 
			
		||||
   * smaller than max texture size allowed for the device.
 | 
			
		||||
   */
 | 
			
		||||
  private static int calculateInSampleSizeByMaxTextureSize(int width, int height) {
 | 
			
		||||
    int inSampleSize = 1;
 | 
			
		||||
    if (mMaxTextureSize == 0) {
 | 
			
		||||
      mMaxTextureSize = getMaxTextureSize();
 | 
			
		||||
    }
 | 
			
		||||
    if (mMaxTextureSize > 0) {
 | 
			
		||||
      while ((height / inSampleSize) > mMaxTextureSize
 | 
			
		||||
              || (width / inSampleSize) > mMaxTextureSize) {
 | 
			
		||||
        inSampleSize *= 2;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return inSampleSize;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rotate the given bitmap by the given degrees.<br>
 | 
			
		||||
   * New bitmap is created and the old one is recycled.
 | 
			
		||||
   */
 | 
			
		||||
  private static Bitmap rotateAndFlipBitmapInt(
 | 
			
		||||
          Bitmap bitmap, int degrees, boolean flipHorizontally, boolean flipVertically) {
 | 
			
		||||
    if (degrees > 0 || flipHorizontally || flipVertically) {
 | 
			
		||||
      Matrix matrix = new Matrix();
 | 
			
		||||
      matrix.setRotate(degrees);
 | 
			
		||||
      matrix.postScale(flipHorizontally ? -1 : 1, flipVertically ? -1 : 1);
 | 
			
		||||
      Bitmap newBitmap =
 | 
			
		||||
              Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
 | 
			
		||||
      if (newBitmap != bitmap) {
 | 
			
		||||
        bitmap.recycle();
 | 
			
		||||
      }
 | 
			
		||||
      return newBitmap;
 | 
			
		||||
    } else {
 | 
			
		||||
      return bitmap;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get the max size of bitmap allowed to be rendered on the device.<br>
 | 
			
		||||
   * http://stackoverflow.com/questions/7428996/hw-accelerated-activity-how-to-get-opengl-texture-size-limit.
 | 
			
		||||
   */
 | 
			
		||||
  private static int getMaxTextureSize() {
 | 
			
		||||
    // Safe minimum default size
 | 
			
		||||
    final int IMAGE_MAX_BITMAP_DIMENSION = 2048;
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      // Get EGL Display
 | 
			
		||||
      EGL10 egl = (EGL10) EGLContext.getEGL();
 | 
			
		||||
      EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
 | 
			
		||||
 | 
			
		||||
      // Initialise
 | 
			
		||||
      int[] version = new int[2];
 | 
			
		||||
      egl.eglInitialize(display, version);
 | 
			
		||||
 | 
			
		||||
      // Query total number of configurations
 | 
			
		||||
      int[] totalConfigurations = new int[1];
 | 
			
		||||
      egl.eglGetConfigs(display, null, 0, totalConfigurations);
 | 
			
		||||
 | 
			
		||||
      // Query actual list configurations
 | 
			
		||||
      EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]];
 | 
			
		||||
      egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations);
 | 
			
		||||
 | 
			
		||||
      int[] textureSize = new int[1];
 | 
			
		||||
      int maximumTextureSize = 0;
 | 
			
		||||
 | 
			
		||||
      // Iterate through all the configurations to located the maximum texture size
 | 
			
		||||
      for (int i = 0; i < totalConfigurations[0]; i++) {
 | 
			
		||||
        // Only need to check for width since opengl textures are always squared
 | 
			
		||||
        egl.eglGetConfigAttrib(
 | 
			
		||||
                display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize);
 | 
			
		||||
 | 
			
		||||
        // Keep track of the maximum texture size
 | 
			
		||||
        if (maximumTextureSize < textureSize[0]) {
 | 
			
		||||
          maximumTextureSize = textureSize[0];
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Release
 | 
			
		||||
      egl.eglTerminate(display);
 | 
			
		||||
 | 
			
		||||
      // Return largest texture size found, or default
 | 
			
		||||
      return Math.max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      return IMAGE_MAX_BITMAP_DIMENSION;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Close the given closeable object (Stream) in a safe way: check if it is null and catch-log
 | 
			
		||||
   * exception thrown.
 | 
			
		||||
   *
 | 
			
		||||
   * @param closeable the closable object to close
 | 
			
		||||
   */
 | 
			
		||||
  private static void closeSafe(Closeable closeable) {
 | 
			
		||||
    if (closeable != null) {
 | 
			
		||||
      try {
 | 
			
		||||
        closeable.close();
 | 
			
		||||
      } catch (IOException ignored) {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // endregion
 | 
			
		||||
 | 
			
		||||
  // region: Inner class: BitmapSampled
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Holds bitmap instance and the sample size that the bitmap was loaded/cropped with.
 | 
			
		||||
   */
 | 
			
		||||
  static final class BitmapSampled {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The bitmap instance
 | 
			
		||||
     */
 | 
			
		||||
    public final Bitmap bitmap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The sample size used to lower the size of the bitmap (1,2,4,8,...)
 | 
			
		||||
     */
 | 
			
		||||
    final int sampleSize;
 | 
			
		||||
 | 
			
		||||
    BitmapSampled(Bitmap bitmap, int sampleSize) {
 | 
			
		||||
      this.bitmap = bitmap;
 | 
			
		||||
      this.sampleSize = sampleSize;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // endregion
 | 
			
		||||
 | 
			
		||||
  // region: Inner class: RotateBitmapResult
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The result of {@link #rotateBitmapByExif(android.graphics.Bitmap, ExifInterface)}.
 | 
			
		||||
   */
 | 
			
		||||
  static final class RotateBitmapResult {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The loaded bitmap
 | 
			
		||||
     */
 | 
			
		||||
    public final Bitmap bitmap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The degrees the image was rotated
 | 
			
		||||
     */
 | 
			
		||||
    final int degrees;
 | 
			
		||||
 | 
			
		||||
    RotateBitmapResult(Bitmap bitmap, int degrees) {
 | 
			
		||||
      this.bitmap = bitmap;
 | 
			
		||||
      this.degrees = degrees;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,367 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.Manifest;
 | 
			
		||||
import android.annotation.SuppressLint;
 | 
			
		||||
import android.app.Activity;
 | 
			
		||||
import android.content.Intent;
 | 
			
		||||
import android.content.pm.PackageManager;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.PorterDuff;
 | 
			
		||||
import android.graphics.drawable.Drawable;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.Bundle;
 | 
			
		||||
import android.view.Menu;
 | 
			
		||||
import android.view.MenuItem;
 | 
			
		||||
import android.widget.Toast;
 | 
			
		||||
 | 
			
		||||
import androidx.annotation.NonNull;
 | 
			
		||||
import androidx.appcompat.app.ActionBar;
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity;
 | 
			
		||||
import androidx.core.content.ContextCompat;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Built-in activity for image cropping.<br>
 | 
			
		||||
 * Use {@link CropImage#activity(Uri)} to create a builder to start this activity.
 | 
			
		||||
 */
 | 
			
		||||
public class CropImageActivity extends AppCompatActivity
 | 
			
		||||
        implements CropImageView.OnSetImageUriCompleteListener,
 | 
			
		||||
        CropImageView.OnCropImageCompleteListener {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The crop image view library widget used in the activity
 | 
			
		||||
   */
 | 
			
		||||
  private CropImageView mCropImageView;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Persist URI image to crop URI if specific permissions are required
 | 
			
		||||
   */
 | 
			
		||||
  private Uri mCropImageUri;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * the options that were set for the crop image
 | 
			
		||||
   */
 | 
			
		||||
  private CropImageOptions mOptions;
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  @SuppressLint("NewApi")
 | 
			
		||||
  public void onCreate(Bundle savedInstanceState) {
 | 
			
		||||
    super.onCreate(savedInstanceState);
 | 
			
		||||
    setContentView(R.layout.crop_image_activity);
 | 
			
		||||
 | 
			
		||||
    mCropImageView = findViewById(R.id.cropImageView);
 | 
			
		||||
 | 
			
		||||
    Bundle bundle = getIntent().getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE);
 | 
			
		||||
    mCropImageUri = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE);
 | 
			
		||||
    mOptions = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS);
 | 
			
		||||
 | 
			
		||||
    if (savedInstanceState == null) {
 | 
			
		||||
      if (mCropImageUri == null || mCropImageUri.equals(Uri.EMPTY)) {
 | 
			
		||||
        if (CropImage.isExplicitCameraPermissionRequired(this)) {
 | 
			
		||||
          // request permissions and handle the result in onRequestPermissionsResult()
 | 
			
		||||
          requestPermissions(
 | 
			
		||||
                  new String[]{Manifest.permission.CAMERA},
 | 
			
		||||
                  CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE);
 | 
			
		||||
        } else {
 | 
			
		||||
          CropImage.startPickImageActivity(this);
 | 
			
		||||
        }
 | 
			
		||||
      } else if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) {
 | 
			
		||||
        // request permissions and handle the result in onRequestPermissionsResult()
 | 
			
		||||
        requestPermissions(
 | 
			
		||||
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
 | 
			
		||||
                CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE);
 | 
			
		||||
      } else {
 | 
			
		||||
        // no permissions required or already grunted, can start crop image activity
 | 
			
		||||
        mCropImageView.setImageUriAsync(mCropImageUri);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ActionBar actionBar = getSupportActionBar();
 | 
			
		||||
    if (actionBar != null) {
 | 
			
		||||
      CharSequence title = mOptions != null &&
 | 
			
		||||
              mOptions.activityTitle != null && mOptions.activityTitle.length() > 0
 | 
			
		||||
              ? mOptions.activityTitle
 | 
			
		||||
              : getResources().getString(R.string.crop_image_activity_title);
 | 
			
		||||
      actionBar.setTitle(title);
 | 
			
		||||
      actionBar.setDisplayHomeAsUpEnabled(true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void onStart() {
 | 
			
		||||
    super.onStart();
 | 
			
		||||
    mCropImageView.setOnSetImageUriCompleteListener(this);
 | 
			
		||||
    mCropImageView.setOnCropImageCompleteListener(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  protected void onStop() {
 | 
			
		||||
    super.onStop();
 | 
			
		||||
    mCropImageView.setOnSetImageUriCompleteListener(null);
 | 
			
		||||
    mCropImageView.setOnCropImageCompleteListener(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean onCreateOptionsMenu(Menu menu) {
 | 
			
		||||
    getMenuInflater().inflate(R.menu.crop_image_menu, menu);
 | 
			
		||||
 | 
			
		||||
    if (!mOptions.allowRotation) {
 | 
			
		||||
      menu.removeItem(R.id.crop_image_menu_rotate_left);
 | 
			
		||||
      menu.removeItem(R.id.crop_image_menu_rotate_right);
 | 
			
		||||
    } else if (mOptions.allowCounterRotation) {
 | 
			
		||||
      menu.findItem(R.id.crop_image_menu_rotate_left).setVisible(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!mOptions.allowFlipping) {
 | 
			
		||||
      menu.removeItem(R.id.crop_image_menu_flip);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mOptions.cropMenuCropButtonTitle != null) {
 | 
			
		||||
      menu.findItem(R.id.crop_image_menu_crop).setTitle(mOptions.cropMenuCropButtonTitle);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Drawable cropIcon = null;
 | 
			
		||||
    try {
 | 
			
		||||
      if (mOptions.cropMenuCropButtonIcon != 0) {
 | 
			
		||||
        cropIcon = ContextCompat.getDrawable(this, mOptions.cropMenuCropButtonIcon);
 | 
			
		||||
        menu.findItem(R.id.crop_image_menu_crop).setIcon(cropIcon);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      e.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mOptions.activityMenuIconColor != 0) {
 | 
			
		||||
      updateMenuItemIconColor(
 | 
			
		||||
              menu, R.id.crop_image_menu_rotate_left, mOptions.activityMenuIconColor);
 | 
			
		||||
      updateMenuItemIconColor(
 | 
			
		||||
              menu, R.id.crop_image_menu_rotate_right, mOptions.activityMenuIconColor);
 | 
			
		||||
      updateMenuItemIconColor(menu, R.id.crop_image_menu_flip, mOptions.activityMenuIconColor);
 | 
			
		||||
      if (cropIcon != null) {
 | 
			
		||||
        updateMenuItemIconColor(menu, R.id.crop_image_menu_crop, mOptions.activityMenuIconColor);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean onOptionsItemSelected(MenuItem item) {
 | 
			
		||||
    if (item.getItemId() == R.id.crop_image_menu_crop) {
 | 
			
		||||
      cropImage();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (item.getItemId() == R.id.crop_image_menu_rotate_left) {
 | 
			
		||||
      rotateImage(-mOptions.rotationDegrees);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (item.getItemId() == R.id.crop_image_menu_rotate_right) {
 | 
			
		||||
      rotateImage(mOptions.rotationDegrees);
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (item.getItemId() == R.id.crop_image_menu_flip_horizontally) {
 | 
			
		||||
      mCropImageView.flipImageHorizontally();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (item.getItemId() == R.id.crop_image_menu_flip_vertically) {
 | 
			
		||||
      mCropImageView.flipImageVertically();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (item.getItemId() == android.R.id.home) {
 | 
			
		||||
      setResultCancel();
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    return super.onOptionsItemSelected(item);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onBackPressed() {
 | 
			
		||||
    super.onBackPressed();
 | 
			
		||||
    setResultCancel();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  @SuppressLint("NewApi")
 | 
			
		||||
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 | 
			
		||||
 | 
			
		||||
    // handle result of pick image chooser
 | 
			
		||||
    if (requestCode == CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE) {
 | 
			
		||||
      if (resultCode == Activity.RESULT_CANCELED) {
 | 
			
		||||
        // User cancelled the picker. We don't have anything to crop
 | 
			
		||||
        setResultCancel();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (resultCode == Activity.RESULT_OK) {
 | 
			
		||||
        mCropImageUri = CropImage.getPickImageResultUri(this, data);
 | 
			
		||||
 | 
			
		||||
        // For API >= 23 we need to check specifically that we have permissions to read external
 | 
			
		||||
        // storage.
 | 
			
		||||
        if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) {
 | 
			
		||||
          // request permissions and handle the result in onRequestPermissionsResult()
 | 
			
		||||
          requestPermissions(
 | 
			
		||||
                  new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
 | 
			
		||||
                  CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE);
 | 
			
		||||
        } else {
 | 
			
		||||
          // no permissions required or already grunted, can start crop image activity
 | 
			
		||||
          mCropImageView.setImageUriAsync(mCropImageUri);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onRequestPermissionsResult(
 | 
			
		||||
          int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
 | 
			
		||||
    if (requestCode == CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE) {
 | 
			
		||||
      if (mCropImageUri != null
 | 
			
		||||
              && grantResults.length > 0
 | 
			
		||||
              && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
 | 
			
		||||
        // required permissions granted, start crop image activity
 | 
			
		||||
        mCropImageView.setImageUriAsync(mCropImageUri);
 | 
			
		||||
      } else {
 | 
			
		||||
        Toast.makeText(this, R.string.crop_image_activity_no_permissions, Toast.LENGTH_LONG).show();
 | 
			
		||||
        setResultCancel();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (requestCode == CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE) {
 | 
			
		||||
      // Irrespective of whether camera permission was given or not, we show the picker
 | 
			
		||||
      // The picker will not add the camera intent if permission is not available
 | 
			
		||||
      CropImage.startPickImageActivity(this);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onSetImageUriComplete(CropImageView view, Uri uri, Exception error) {
 | 
			
		||||
    if (error == null) {
 | 
			
		||||
      if (mOptions.initialCropWindowRectangle != null) {
 | 
			
		||||
        mCropImageView.setCropRect(mOptions.initialCropWindowRectangle);
 | 
			
		||||
      }
 | 
			
		||||
      if (mOptions.initialRotation > -1) {
 | 
			
		||||
        mCropImageView.setRotatedDegrees(mOptions.initialRotation);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      setResult(null, error, 1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void onCropImageComplete(CropImageView view, CropImageView.CropResult result) {
 | 
			
		||||
    setResult(result.getUri(), result.getError(), result.getSampleSize());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // region: Private methods
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Execute crop image and save the result tou output uri.
 | 
			
		||||
   */
 | 
			
		||||
  protected void cropImage() {
 | 
			
		||||
    if (mOptions.noOutputImage) {
 | 
			
		||||
      setResult(null, null, 1);
 | 
			
		||||
    } else {
 | 
			
		||||
      Uri outputUri = getOutputUri();
 | 
			
		||||
      mCropImageView.saveCroppedImageAsync(
 | 
			
		||||
              outputUri,
 | 
			
		||||
              mOptions.outputCompressFormat,
 | 
			
		||||
              mOptions.outputCompressQuality,
 | 
			
		||||
              mOptions.outputRequestWidth,
 | 
			
		||||
              mOptions.outputRequestHeight,
 | 
			
		||||
              mOptions.outputRequestSizeOptions);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Rotate the image in the crop image view.
 | 
			
		||||
   */
 | 
			
		||||
  protected void rotateImage(int degrees) {
 | 
			
		||||
    mCropImageView.rotateImage(degrees);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get Android uri to save the cropped image into.<br>
 | 
			
		||||
   * Use the given in options or create a temp file.
 | 
			
		||||
   */
 | 
			
		||||
  protected Uri getOutputUri() {
 | 
			
		||||
    Uri outputUri = mOptions.outputUri;
 | 
			
		||||
    if (outputUri == null || outputUri.equals(Uri.EMPTY)) {
 | 
			
		||||
      try {
 | 
			
		||||
        String ext =
 | 
			
		||||
                mOptions.outputCompressFormat == Bitmap.CompressFormat.JPEG
 | 
			
		||||
                        ? ".jpg"
 | 
			
		||||
                        : mOptions.outputCompressFormat == Bitmap.CompressFormat.PNG ? ".png" : ".webp";
 | 
			
		||||
        outputUri = Uri.fromFile(File.createTempFile("cropped", ext, getCacheDir()));
 | 
			
		||||
      } catch (IOException e) {
 | 
			
		||||
        throw new RuntimeException("Failed to create temp file for output image", e);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return outputUri;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Result with cropped image data or error if failed.
 | 
			
		||||
   */
 | 
			
		||||
  protected void setResult(Uri uri, Exception error, int sampleSize) {
 | 
			
		||||
    int resultCode = error == null ? RESULT_OK : CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE;
 | 
			
		||||
    setResult(resultCode, getResultIntent(uri, error, sampleSize));
 | 
			
		||||
    finish();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Cancel of cropping activity.
 | 
			
		||||
   */
 | 
			
		||||
  protected void setResultCancel() {
 | 
			
		||||
    setResult(RESULT_CANCELED);
 | 
			
		||||
    finish();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get intent instance to be used for the result of this activity.
 | 
			
		||||
   */
 | 
			
		||||
  protected Intent getResultIntent(Uri uri, Exception error, int sampleSize) {
 | 
			
		||||
    CropImage.ActivityResult result =
 | 
			
		||||
            new CropImage.ActivityResult(
 | 
			
		||||
                    mCropImageView.getImageUri(),
 | 
			
		||||
                    uri,
 | 
			
		||||
                    error,
 | 
			
		||||
                    mCropImageView.getCropPoints(),
 | 
			
		||||
                    mCropImageView.getCropRect(),
 | 
			
		||||
                    mCropImageView.getRotatedDegrees(),
 | 
			
		||||
                    mCropImageView.getWholeImageRect(),
 | 
			
		||||
                    sampleSize);
 | 
			
		||||
    Intent intent = new Intent();
 | 
			
		||||
    intent.putExtras(getIntent());
 | 
			
		||||
    intent.putExtra(CropImage.CROP_IMAGE_EXTRA_RESULT, result);
 | 
			
		||||
    return intent;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Update the color of a specific menu item to the given color.
 | 
			
		||||
   */
 | 
			
		||||
  private void updateMenuItemIconColor(Menu menu, int itemId, int color) {
 | 
			
		||||
    MenuItem menuItem = menu.findItem(itemId);
 | 
			
		||||
    if (menuItem != null) {
 | 
			
		||||
      Drawable menuItemIcon = menuItem.getIcon();
 | 
			
		||||
      if (menuItemIcon != null) {
 | 
			
		||||
        try {
 | 
			
		||||
          menuItemIcon.mutate();
 | 
			
		||||
          menuItemIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
 | 
			
		||||
          menuItem.setIcon(menuItemIcon);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
          e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,123 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.graphics.Matrix;
 | 
			
		||||
import android.graphics.RectF;
 | 
			
		||||
import android.view.animation.AccelerateDecelerateInterpolator;
 | 
			
		||||
import android.view.animation.Animation;
 | 
			
		||||
import android.view.animation.Transformation;
 | 
			
		||||
import android.widget.ImageView;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Animation to handle smooth cropping image matrix transformation change, specifically for
 | 
			
		||||
 * zoom-in/out.
 | 
			
		||||
 */
 | 
			
		||||
final class CropImageAnimation extends Animation implements Animation.AnimationListener {
 | 
			
		||||
 | 
			
		||||
    // region: Fields and Consts
 | 
			
		||||
 | 
			
		||||
    private final ImageView mImageView;
 | 
			
		||||
 | 
			
		||||
    private final CropOverlayView mCropOverlayView;
 | 
			
		||||
 | 
			
		||||
    private final float[] mStartBoundPoints = new float[8];
 | 
			
		||||
 | 
			
		||||
    private final float[] mEndBoundPoints = new float[8];
 | 
			
		||||
 | 
			
		||||
    private final RectF mStartCropWindowRect = new RectF();
 | 
			
		||||
 | 
			
		||||
    private final RectF mEndCropWindowRect = new RectF();
 | 
			
		||||
 | 
			
		||||
    private final float[] mStartImageMatrix = new float[9];
 | 
			
		||||
 | 
			
		||||
    private final float[] mEndImageMatrix = new float[9];
 | 
			
		||||
 | 
			
		||||
    private final RectF mAnimRect = new RectF();
 | 
			
		||||
 | 
			
		||||
    private final float[] mAnimPoints = new float[8];
 | 
			
		||||
 | 
			
		||||
    private final float[] mAnimMatrix = new float[9];
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    public CropImageAnimation(ImageView cropImageView, CropOverlayView cropOverlayView) {
 | 
			
		||||
        mImageView = cropImageView;
 | 
			
		||||
        mCropOverlayView = cropOverlayView;
 | 
			
		||||
 | 
			
		||||
        setDuration(300);
 | 
			
		||||
        setFillAfter(true);
 | 
			
		||||
        setInterpolator(new AccelerateDecelerateInterpolator());
 | 
			
		||||
        setAnimationListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setStartState(float[] boundPoints, Matrix imageMatrix) {
 | 
			
		||||
        reset();
 | 
			
		||||
        System.arraycopy(boundPoints, 0, mStartBoundPoints, 0, 8);
 | 
			
		||||
        mStartCropWindowRect.set(mCropOverlayView.getCropWindowRect());
 | 
			
		||||
        imageMatrix.getValues(mStartImageMatrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEndState(float[] boundPoints, Matrix imageMatrix) {
 | 
			
		||||
        System.arraycopy(boundPoints, 0, mEndBoundPoints, 0, 8);
 | 
			
		||||
        mEndCropWindowRect.set(mCropOverlayView.getCropWindowRect());
 | 
			
		||||
        imageMatrix.getValues(mEndImageMatrix);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void applyTransformation(float interpolatedTime, Transformation t) {
 | 
			
		||||
 | 
			
		||||
        mAnimRect.left =
 | 
			
		||||
                mStartCropWindowRect.left
 | 
			
		||||
                        + (mEndCropWindowRect.left - mStartCropWindowRect.left) * interpolatedTime;
 | 
			
		||||
        mAnimRect.top =
 | 
			
		||||
                mStartCropWindowRect.top
 | 
			
		||||
                        + (mEndCropWindowRect.top - mStartCropWindowRect.top) * interpolatedTime;
 | 
			
		||||
        mAnimRect.right =
 | 
			
		||||
                mStartCropWindowRect.right
 | 
			
		||||
                        + (mEndCropWindowRect.right - mStartCropWindowRect.right) * interpolatedTime;
 | 
			
		||||
        mAnimRect.bottom =
 | 
			
		||||
                mStartCropWindowRect.bottom
 | 
			
		||||
                        + (mEndCropWindowRect.bottom - mStartCropWindowRect.bottom) * interpolatedTime;
 | 
			
		||||
        mCropOverlayView.setCropWindowRect(mAnimRect);
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < mAnimPoints.length; i++) {
 | 
			
		||||
            mAnimPoints[i] =
 | 
			
		||||
                    mStartBoundPoints[i] + (mEndBoundPoints[i] - mStartBoundPoints[i]) * interpolatedTime;
 | 
			
		||||
        }
 | 
			
		||||
        mCropOverlayView.setBounds(mAnimPoints, mImageView.getWidth(), mImageView.getHeight());
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < mAnimMatrix.length; i++) {
 | 
			
		||||
            mAnimMatrix[i] =
 | 
			
		||||
                    mStartImageMatrix[i] + (mEndImageMatrix[i] - mStartImageMatrix[i]) * interpolatedTime;
 | 
			
		||||
        }
 | 
			
		||||
        Matrix m = mImageView.getImageMatrix();
 | 
			
		||||
        m.setValues(mAnimMatrix);
 | 
			
		||||
        mImageView.setImageMatrix(m);
 | 
			
		||||
 | 
			
		||||
        mImageView.invalidate();
 | 
			
		||||
        mCropOverlayView.invalidate();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAnimationStart(Animation animation) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAnimationEnd(Animation animation) {
 | 
			
		||||
        mImageView.clearAnimation();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onAnimationRepeat(Animation animation) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,541 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth;
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end;
 | 
			
		||||
// they begin again;
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn;
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu;
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.content.res.Resources;
 | 
			
		||||
import android.graphics.Bitmap;
 | 
			
		||||
import android.graphics.Color;
 | 
			
		||||
import android.graphics.Rect;
 | 
			
		||||
import android.net.Uri;
 | 
			
		||||
import android.os.Parcel;
 | 
			
		||||
import android.os.Parcelable;
 | 
			
		||||
import android.text.TextUtils;
 | 
			
		||||
import android.util.DisplayMetrics;
 | 
			
		||||
import android.util.TypedValue;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * All the possible options that can be set to customize crop image.<br>
 | 
			
		||||
 * Initialized with default values.
 | 
			
		||||
 */
 | 
			
		||||
public class CropImageOptions implements Parcelable {
 | 
			
		||||
 | 
			
		||||
    public static final Creator<CropImageOptions> CREATOR =
 | 
			
		||||
            new Creator<CropImageOptions>() {
 | 
			
		||||
                @Override
 | 
			
		||||
                public CropImageOptions createFromParcel(Parcel in) {
 | 
			
		||||
                    return new CropImageOptions(in);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                @Override
 | 
			
		||||
                public CropImageOptions[] newArray(int size) {
 | 
			
		||||
                    return new CropImageOptions[size];
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The shape of the cropping window.
 | 
			
		||||
     */
 | 
			
		||||
    public CropImageView.CropShape cropShape;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An edge of the crop window will snap to the corresponding edge of a specified bounding box when
 | 
			
		||||
     * the crop window edge is less than or equal to this distance (in pixels) away from the bounding
 | 
			
		||||
     * box edge. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float snapRadius;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The radius of the touchable area around the handle. (in pixels)<br>
 | 
			
		||||
     * We are basing this value off of the recommended 48dp Rhythm.<br>
 | 
			
		||||
     * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm
 | 
			
		||||
     */
 | 
			
		||||
    public float touchRadius;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * whether the guidelines should be on, off, or only showing when resizing.
 | 
			
		||||
     */
 | 
			
		||||
    public CropImageView.Guidelines guidelines;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The initial scale type of the image in the crop image view
 | 
			
		||||
     */
 | 
			
		||||
    public CropImageView.ScaleType scaleType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if to show crop overlay UI what contains the crop window UI surrounded by background over the
 | 
			
		||||
     * cropping image.<br>
 | 
			
		||||
     * default: true, may disable for animation or frame transition.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showCropOverlay;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if to show progress bar when image async loading/cropping is in progress.<br>
 | 
			
		||||
     * default: true, disable to provide custom progress bar UI.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showProgressBar;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if auto-zoom functionality is enabled.<br>
 | 
			
		||||
     * default: true.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean autoZoomEnabled;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if multi-touch should be enabled on the crop box default: false
 | 
			
		||||
     */
 | 
			
		||||
    public boolean multiTouchEnabled;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The max zoom allowed during cropping.
 | 
			
		||||
     */
 | 
			
		||||
    public int maxZoom;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The initial crop window padding from image borders in percentage of the cropping image
 | 
			
		||||
     * dimensions.
 | 
			
		||||
     */
 | 
			
		||||
    public float initialCropWindowPaddingRatio;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * whether the width to height aspect ratio should be maintained or free to change.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean fixAspectRatio;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the X value of the aspect ratio.
 | 
			
		||||
     */
 | 
			
		||||
    public int aspectRatioX;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the Y value of the aspect ratio.
 | 
			
		||||
     */
 | 
			
		||||
    public int aspectRatioY;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the thickness of the guidelines lines in pixels. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float borderLineThickness;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the color of the guidelines lines
 | 
			
		||||
     */
 | 
			
		||||
    public int borderLineColor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * thickness of the corner line. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float borderCornerThickness;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the offset of corner line from crop window border. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float borderCornerOffset;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the length of the corner line away from the corner. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float borderCornerLength;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the color of the corner line
 | 
			
		||||
     */
 | 
			
		||||
    public int borderCornerColor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the thickness of the guidelines lines. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public float guidelinesThickness;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the color of the guidelines lines
 | 
			
		||||
     */
 | 
			
		||||
    public int guidelinesColor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the color of the overlay background around the crop window cover the image parts not in the
 | 
			
		||||
     * crop window.
 | 
			
		||||
     */
 | 
			
		||||
    public int backgroundColor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the min width the crop window is allowed to be. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int minCropWindowWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the min height the crop window is allowed to be. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int minCropWindowHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the min width the resulting cropping image is allowed to be, affects the cropping window
 | 
			
		||||
     * limits. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int minCropResultWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the min height the resulting cropping image is allowed to be, affects the cropping window
 | 
			
		||||
     * limits. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int minCropResultHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the max width the resulting cropping image is allowed to be, affects the cropping window
 | 
			
		||||
     * limits. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int maxCropResultWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the max height the resulting cropping image is allowed to be, affects the cropping window
 | 
			
		||||
     * limits. (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    public int maxCropResultHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the title of the {@link CropImageActivity}
 | 
			
		||||
     */
 | 
			
		||||
    public CharSequence activityTitle;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the color to use for action bar items icons
 | 
			
		||||
     */
 | 
			
		||||
    public int activityMenuIconColor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the Android Uri to save the cropped image to
 | 
			
		||||
     */
 | 
			
		||||
    public Uri outputUri;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the compression format to use when writing the image
 | 
			
		||||
     */
 | 
			
		||||
    public Bitmap.CompressFormat outputCompressFormat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the quality (if applicable) to use when writing the image (0 - 100)
 | 
			
		||||
     */
 | 
			
		||||
    public int outputCompressQuality;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the width to resize the cropped image to (see options)
 | 
			
		||||
     */
 | 
			
		||||
    public int outputRequestWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the height to resize the cropped image to (see options)
 | 
			
		||||
     */
 | 
			
		||||
    public int outputRequestHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the resize method to use on the cropped bitmap (see options documentation)
 | 
			
		||||
     */
 | 
			
		||||
    public CropImageView.RequestSizeOptions outputRequestSizeOptions;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if the result of crop image activity should not save the cropped image bitmap
 | 
			
		||||
     */
 | 
			
		||||
    public boolean noOutputImage;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the initial rectangle to set on the cropping image after loading
 | 
			
		||||
     */
 | 
			
		||||
    public Rect initialCropWindowRectangle;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the initial rotation to set on the cropping image after loading (0-360 degrees clockwise)
 | 
			
		||||
     */
 | 
			
		||||
    public int initialRotation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if to allow (all) rotation during cropping (activity)
 | 
			
		||||
     */
 | 
			
		||||
    public boolean allowRotation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if to allow (all) flipping during cropping (activity)
 | 
			
		||||
     */
 | 
			
		||||
    public boolean allowFlipping;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if to allow counter-clockwise rotation during cropping (activity)
 | 
			
		||||
     */
 | 
			
		||||
    public boolean allowCounterRotation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the amount of degrees to rotate clockwise or counter-clockwise
 | 
			
		||||
     */
 | 
			
		||||
    public int rotationDegrees;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * whether the image should be flipped horizontally
 | 
			
		||||
     */
 | 
			
		||||
    public boolean flipHorizontally;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * whether the image should be flipped vertically
 | 
			
		||||
     */
 | 
			
		||||
    public boolean flipVertically;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * optional, the text of the crop menu crop button
 | 
			
		||||
     */
 | 
			
		||||
    public CharSequence cropMenuCropButtonTitle;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * optional image resource to be used for crop menu crop icon instead of text
 | 
			
		||||
     */
 | 
			
		||||
    public int cropMenuCropButtonIcon;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Init options with defaults.
 | 
			
		||||
     */
 | 
			
		||||
    public CropImageOptions() {
 | 
			
		||||
 | 
			
		||||
        DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();
 | 
			
		||||
 | 
			
		||||
        cropShape = CropImageView.CropShape.RECTANGLE;
 | 
			
		||||
        snapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);
 | 
			
		||||
        touchRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, dm);
 | 
			
		||||
        guidelines = CropImageView.Guidelines.ON_TOUCH;
 | 
			
		||||
        scaleType = CropImageView.ScaleType.FIT_CENTER;
 | 
			
		||||
        showCropOverlay = true;
 | 
			
		||||
        showProgressBar = true;
 | 
			
		||||
        autoZoomEnabled = true;
 | 
			
		||||
        multiTouchEnabled = false;
 | 
			
		||||
        maxZoom = 4;
 | 
			
		||||
        initialCropWindowPaddingRatio = 0.1f;
 | 
			
		||||
 | 
			
		||||
        fixAspectRatio = false;
 | 
			
		||||
        aspectRatioX = 1;
 | 
			
		||||
        aspectRatioY = 1;
 | 
			
		||||
 | 
			
		||||
        borderLineThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);
 | 
			
		||||
        borderLineColor = Color.argb(170, 255, 255, 255);
 | 
			
		||||
        borderCornerThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm);
 | 
			
		||||
        borderCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, dm);
 | 
			
		||||
        borderCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dm);
 | 
			
		||||
        borderCornerColor = Color.WHITE;
 | 
			
		||||
 | 
			
		||||
        guidelinesThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm);
 | 
			
		||||
        guidelinesColor = Color.argb(170, 255, 255, 255);
 | 
			
		||||
        backgroundColor = Color.argb(119, 0, 0, 0);
 | 
			
		||||
 | 
			
		||||
        minCropWindowWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);
 | 
			
		||||
        minCropWindowHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);
 | 
			
		||||
        minCropResultWidth = 40;
 | 
			
		||||
        minCropResultHeight = 40;
 | 
			
		||||
        maxCropResultWidth = 99999;
 | 
			
		||||
        maxCropResultHeight = 99999;
 | 
			
		||||
 | 
			
		||||
        activityTitle = "";
 | 
			
		||||
        activityMenuIconColor = 0;
 | 
			
		||||
 | 
			
		||||
        outputUri = Uri.EMPTY;
 | 
			
		||||
        outputCompressFormat = Bitmap.CompressFormat.JPEG;
 | 
			
		||||
        outputCompressQuality = 90;
 | 
			
		||||
        outputRequestWidth = 0;
 | 
			
		||||
        outputRequestHeight = 0;
 | 
			
		||||
        outputRequestSizeOptions = CropImageView.RequestSizeOptions.NONE;
 | 
			
		||||
        noOutputImage = false;
 | 
			
		||||
 | 
			
		||||
        initialCropWindowRectangle = null;
 | 
			
		||||
        initialRotation = -1;
 | 
			
		||||
        allowRotation = true;
 | 
			
		||||
        allowFlipping = true;
 | 
			
		||||
        allowCounterRotation = false;
 | 
			
		||||
        rotationDegrees = 90;
 | 
			
		||||
        flipHorizontally = false;
 | 
			
		||||
        flipVertically = false;
 | 
			
		||||
        cropMenuCropButtonTitle = null;
 | 
			
		||||
 | 
			
		||||
        cropMenuCropButtonIcon = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create object from parcel.
 | 
			
		||||
     */
 | 
			
		||||
    protected CropImageOptions(Parcel in) {
 | 
			
		||||
        cropShape = CropImageView.CropShape.values()[in.readInt()];
 | 
			
		||||
        snapRadius = in.readFloat();
 | 
			
		||||
        touchRadius = in.readFloat();
 | 
			
		||||
        guidelines = CropImageView.Guidelines.values()[in.readInt()];
 | 
			
		||||
        scaleType = CropImageView.ScaleType.values()[in.readInt()];
 | 
			
		||||
        showCropOverlay = in.readByte() != 0;
 | 
			
		||||
        showProgressBar = in.readByte() != 0;
 | 
			
		||||
        autoZoomEnabled = in.readByte() != 0;
 | 
			
		||||
        multiTouchEnabled = in.readByte() != 0;
 | 
			
		||||
        maxZoom = in.readInt();
 | 
			
		||||
        initialCropWindowPaddingRatio = in.readFloat();
 | 
			
		||||
        fixAspectRatio = in.readByte() != 0;
 | 
			
		||||
        aspectRatioX = in.readInt();
 | 
			
		||||
        aspectRatioY = in.readInt();
 | 
			
		||||
        borderLineThickness = in.readFloat();
 | 
			
		||||
        borderLineColor = in.readInt();
 | 
			
		||||
        borderCornerThickness = in.readFloat();
 | 
			
		||||
        borderCornerOffset = in.readFloat();
 | 
			
		||||
        borderCornerLength = in.readFloat();
 | 
			
		||||
        borderCornerColor = in.readInt();
 | 
			
		||||
        guidelinesThickness = in.readFloat();
 | 
			
		||||
        guidelinesColor = in.readInt();
 | 
			
		||||
        backgroundColor = in.readInt();
 | 
			
		||||
        minCropWindowWidth = in.readInt();
 | 
			
		||||
        minCropWindowHeight = in.readInt();
 | 
			
		||||
        minCropResultWidth = in.readInt();
 | 
			
		||||
        minCropResultHeight = in.readInt();
 | 
			
		||||
        maxCropResultWidth = in.readInt();
 | 
			
		||||
        maxCropResultHeight = in.readInt();
 | 
			
		||||
        activityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
 | 
			
		||||
        activityMenuIconColor = in.readInt();
 | 
			
		||||
        outputUri = in.readParcelable(Uri.class.getClassLoader());
 | 
			
		||||
        outputCompressFormat = Bitmap.CompressFormat.valueOf(in.readString());
 | 
			
		||||
        outputCompressQuality = in.readInt();
 | 
			
		||||
        outputRequestWidth = in.readInt();
 | 
			
		||||
        outputRequestHeight = in.readInt();
 | 
			
		||||
        outputRequestSizeOptions = CropImageView.RequestSizeOptions.values()[in.readInt()];
 | 
			
		||||
        noOutputImage = in.readByte() != 0;
 | 
			
		||||
        initialCropWindowRectangle = in.readParcelable(Rect.class.getClassLoader());
 | 
			
		||||
        initialRotation = in.readInt();
 | 
			
		||||
        allowRotation = in.readByte() != 0;
 | 
			
		||||
        allowFlipping = in.readByte() != 0;
 | 
			
		||||
        allowCounterRotation = in.readByte() != 0;
 | 
			
		||||
        rotationDegrees = in.readInt();
 | 
			
		||||
        flipHorizontally = in.readByte() != 0;
 | 
			
		||||
        flipVertically = in.readByte() != 0;
 | 
			
		||||
        cropMenuCropButtonTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
 | 
			
		||||
        cropMenuCropButtonIcon = in.readInt();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void writeToParcel(Parcel dest, int flags) {
 | 
			
		||||
        dest.writeInt(cropShape.ordinal());
 | 
			
		||||
        dest.writeFloat(snapRadius);
 | 
			
		||||
        dest.writeFloat(touchRadius);
 | 
			
		||||
        dest.writeInt(guidelines.ordinal());
 | 
			
		||||
        dest.writeInt(scaleType.ordinal());
 | 
			
		||||
        dest.writeByte((byte) (showCropOverlay ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (showProgressBar ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (autoZoomEnabled ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (multiTouchEnabled ? 1 : 0));
 | 
			
		||||
        dest.writeInt(maxZoom);
 | 
			
		||||
        dest.writeFloat(initialCropWindowPaddingRatio);
 | 
			
		||||
        dest.writeByte((byte) (fixAspectRatio ? 1 : 0));
 | 
			
		||||
        dest.writeInt(aspectRatioX);
 | 
			
		||||
        dest.writeInt(aspectRatioY);
 | 
			
		||||
        dest.writeFloat(borderLineThickness);
 | 
			
		||||
        dest.writeInt(borderLineColor);
 | 
			
		||||
        dest.writeFloat(borderCornerThickness);
 | 
			
		||||
        dest.writeFloat(borderCornerOffset);
 | 
			
		||||
        dest.writeFloat(borderCornerLength);
 | 
			
		||||
        dest.writeInt(borderCornerColor);
 | 
			
		||||
        dest.writeFloat(guidelinesThickness);
 | 
			
		||||
        dest.writeInt(guidelinesColor);
 | 
			
		||||
        dest.writeInt(backgroundColor);
 | 
			
		||||
        dest.writeInt(minCropWindowWidth);
 | 
			
		||||
        dest.writeInt(minCropWindowHeight);
 | 
			
		||||
        dest.writeInt(minCropResultWidth);
 | 
			
		||||
        dest.writeInt(minCropResultHeight);
 | 
			
		||||
        dest.writeInt(maxCropResultWidth);
 | 
			
		||||
        dest.writeInt(maxCropResultHeight);
 | 
			
		||||
        TextUtils.writeToParcel(activityTitle, dest, flags);
 | 
			
		||||
        dest.writeInt(activityMenuIconColor);
 | 
			
		||||
        dest.writeParcelable(outputUri, flags);
 | 
			
		||||
        dest.writeString(outputCompressFormat.name());
 | 
			
		||||
        dest.writeInt(outputCompressQuality);
 | 
			
		||||
        dest.writeInt(outputRequestWidth);
 | 
			
		||||
        dest.writeInt(outputRequestHeight);
 | 
			
		||||
        dest.writeInt(outputRequestSizeOptions.ordinal());
 | 
			
		||||
        dest.writeInt(noOutputImage ? 1 : 0);
 | 
			
		||||
        dest.writeParcelable(initialCropWindowRectangle, flags);
 | 
			
		||||
        dest.writeInt(initialRotation);
 | 
			
		||||
        dest.writeByte((byte) (allowRotation ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (allowFlipping ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (allowCounterRotation ? 1 : 0));
 | 
			
		||||
        dest.writeInt(rotationDegrees);
 | 
			
		||||
        dest.writeByte((byte) (flipHorizontally ? 1 : 0));
 | 
			
		||||
        dest.writeByte((byte) (flipVertically ? 1 : 0));
 | 
			
		||||
        TextUtils.writeToParcel(cropMenuCropButtonTitle, dest, flags);
 | 
			
		||||
        dest.writeInt(cropMenuCropButtonIcon);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int describeContents() {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate all the options are withing valid range.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws IllegalArgumentException if any of the options is not valid
 | 
			
		||||
     */
 | 
			
		||||
    public void validate() {
 | 
			
		||||
        if (maxZoom < 0) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot set max zoom to a number < 1");
 | 
			
		||||
        }
 | 
			
		||||
        if (touchRadius < 0) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot set touch radius value to a number <= 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (initialCropWindowPaddingRatio < 0 || initialCropWindowPaddingRatio >= 0.5) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set initial crop window padding value to a number < 0 or >= 0.5");
 | 
			
		||||
        }
 | 
			
		||||
        if (aspectRatioX <= 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set aspect ratio value to a number less than or equal to 0.");
 | 
			
		||||
        }
 | 
			
		||||
        if (aspectRatioY <= 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set aspect ratio value to a number less than or equal to 0.");
 | 
			
		||||
        }
 | 
			
		||||
        if (borderLineThickness < 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set line thickness value to a number less than 0.");
 | 
			
		||||
        }
 | 
			
		||||
        if (borderCornerThickness < 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set corner thickness value to a number less than 0.");
 | 
			
		||||
        }
 | 
			
		||||
        if (guidelinesThickness < 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set guidelines thickness value to a number less than 0.");
 | 
			
		||||
        }
 | 
			
		||||
        if (minCropWindowHeight < 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set min crop window height value to a number < 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (minCropResultWidth < 0) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot set min crop result width value to a number < 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (minCropResultHeight < 0) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set min crop result height value to a number < 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (maxCropResultWidth < minCropResultWidth) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set max crop result width to smaller value than min crop result width");
 | 
			
		||||
        }
 | 
			
		||||
        if (maxCropResultHeight < minCropResultHeight) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set max crop result height to smaller value than min crop result height");
 | 
			
		||||
        }
 | 
			
		||||
        if (outputRequestWidth < 0) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot set request width value to a number < 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (outputRequestHeight < 0) {
 | 
			
		||||
            throw new IllegalArgumentException("Cannot set request height value to a number < 0 ");
 | 
			
		||||
        }
 | 
			
		||||
        if (rotationDegrees < 0 || rotationDegrees > 360) {
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
                    "Cannot set rotation degrees value to a number < 0 or > 360");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,405 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.graphics.RectF;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler from crop window stuff, moving and knowing possition.
 | 
			
		||||
 */
 | 
			
		||||
final class CropWindowHandler {
 | 
			
		||||
 | 
			
		||||
    // region: Fields and Consts
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The 4 edges of the crop window defining its coordinates and size
 | 
			
		||||
     */
 | 
			
		||||
    private final RectF mEdges = new RectF();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rectangle used to return the edges rectangle without ability to change it and without creating
 | 
			
		||||
     * new all the time.
 | 
			
		||||
     */
 | 
			
		||||
    private final RectF mGetEdges = new RectF();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum width in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMinCropWindowWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum height in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMinCropWindowHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum width in pixels that the crop window can CURRENTLY get.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMaxCropWindowWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum height in pixels that the crop window can CURRENTLY get.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMaxCropWindowHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum width in pixels that the result of cropping an image can get, affects crop window width
 | 
			
		||||
     * adjusted by width scale factor.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMinCropResultWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum height in pixels that the result of cropping an image can get, affects crop window
 | 
			
		||||
     * height adjusted by height scale factor.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMinCropResultHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum width in pixels that the result of cropping an image can get, affects crop window width
 | 
			
		||||
     * adjusted by width scale factor.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMaxCropResultWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum height in pixels that the result of cropping an image can get, affects crop window
 | 
			
		||||
     * height adjusted by height scale factor.
 | 
			
		||||
     */
 | 
			
		||||
    private float mMaxCropResultHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The width scale factor of shown image and actual image
 | 
			
		||||
     */
 | 
			
		||||
    private float mScaleFactorWidth = 1;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The height scale factor of shown image and actual image
 | 
			
		||||
     */
 | 
			
		||||
    private float mScaleFactorHeight = 1;
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the specified coordinate is in the target touch zone for a corner handle.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x            the x-coordinate of the touch point
 | 
			
		||||
     * @param y            the y-coordinate of the touch point
 | 
			
		||||
     * @param handleX      the x-coordinate of the corner handle
 | 
			
		||||
     * @param handleY      the y-coordinate of the corner handle
 | 
			
		||||
     * @param targetRadius the target radius in pixels
 | 
			
		||||
     * @return true if the touch point is in the target touch zone; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean isInCornerTargetZone(
 | 
			
		||||
            float x, float y, float handleX, float handleY, float targetRadius) {
 | 
			
		||||
        return Math.abs(x - handleX) <= targetRadius && Math.abs(y - handleY) <= targetRadius;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the specified coordinate is in the target touch zone for a horizontal bar handle.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x            the x-coordinate of the touch point
 | 
			
		||||
     * @param y            the y-coordinate of the touch point
 | 
			
		||||
     * @param handleXStart the left x-coordinate of the horizontal bar handle
 | 
			
		||||
     * @param handleXEnd   the right x-coordinate of the horizontal bar handle
 | 
			
		||||
     * @param handleY      the y-coordinate of the horizontal bar handle
 | 
			
		||||
     * @param targetRadius the target radius in pixels
 | 
			
		||||
     * @return true if the touch point is in the target touch zone; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean isInHorizontalTargetZone(
 | 
			
		||||
            float x, float y, float handleXStart, float handleXEnd, float handleY, float targetRadius) {
 | 
			
		||||
        return x > handleXStart && x < handleXEnd && Math.abs(y - handleY) <= targetRadius;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the specified coordinate is in the target touch zone for a vertical bar handle.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x            the x-coordinate of the touch point
 | 
			
		||||
     * @param y            the y-coordinate of the touch point
 | 
			
		||||
     * @param handleX      the x-coordinate of the vertical bar handle
 | 
			
		||||
     * @param handleYStart the top y-coordinate of the vertical bar handle
 | 
			
		||||
     * @param handleYEnd   the bottom y-coordinate of the vertical bar handle
 | 
			
		||||
     * @param targetRadius the target radius in pixels
 | 
			
		||||
     * @return true if the touch point is in the target touch zone; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean isInVerticalTargetZone(
 | 
			
		||||
            float x, float y, float handleX, float handleYStart, float handleYEnd, float targetRadius) {
 | 
			
		||||
        return Math.abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the specified coordinate falls anywhere inside the given bounds.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x      the x-coordinate of the touch point
 | 
			
		||||
     * @param y      the y-coordinate of the touch point
 | 
			
		||||
     * @param left   the x-coordinate of the left bound
 | 
			
		||||
     * @param top    the y-coordinate of the top bound
 | 
			
		||||
     * @param right  the x-coordinate of the right bound
 | 
			
		||||
     * @param bottom the y-coordinate of the bottom bound
 | 
			
		||||
     * @return true if the touch point is inside the bounding rectangle; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private static boolean isInCenterTargetZone(
 | 
			
		||||
            float x, float y, float left, float top, float right, float bottom) {
 | 
			
		||||
        return x > left && x < right && y > top && y < bottom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the left/top/right/bottom coordinates of the crop window.
 | 
			
		||||
     */
 | 
			
		||||
    public RectF getRect() {
 | 
			
		||||
        mGetEdges.set(mEdges);
 | 
			
		||||
        return mGetEdges;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the left/top/right/bottom coordinates of the crop window.
 | 
			
		||||
     */
 | 
			
		||||
    public void setRect(RectF rect) {
 | 
			
		||||
        mEdges.set(rect);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum width in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    public float getMinCropWidth() {
 | 
			
		||||
        return Math.max(mMinCropWindowWidth, mMinCropResultWidth / mScaleFactorWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum height in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    public float getMinCropHeight() {
 | 
			
		||||
        return Math.max(mMinCropWindowHeight, mMinCropResultHeight / mScaleFactorHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum width in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    public float getMaxCropWidth() {
 | 
			
		||||
        return Math.min(mMaxCropWindowWidth, mMaxCropResultWidth / mScaleFactorWidth);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum height in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    public float getMaxCropHeight() {
 | 
			
		||||
        return Math.min(mMaxCropWindowHeight, mMaxCropResultHeight / mScaleFactorHeight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get the scale factor (on width) of the showen image to original image.
 | 
			
		||||
     */
 | 
			
		||||
    public float getScaleFactorWidth() {
 | 
			
		||||
        return mScaleFactorWidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get the scale factor (on height) of the showen image to original image.
 | 
			
		||||
     */
 | 
			
		||||
    public float getScaleFactorHeight() {
 | 
			
		||||
        return mScaleFactorHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the min size the resulting cropping image is allowed to be, affects the cropping window limits
 | 
			
		||||
     * (in pixels).<br>
 | 
			
		||||
     */
 | 
			
		||||
    public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) {
 | 
			
		||||
        mMinCropResultWidth = minCropResultWidth;
 | 
			
		||||
        mMinCropResultHeight = minCropResultHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * the max size the resulting cropping image is allowed to be, affects the cropping window limits
 | 
			
		||||
     * (in pixels).<br>
 | 
			
		||||
     */
 | 
			
		||||
    public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {
 | 
			
		||||
        mMaxCropResultWidth = maxCropResultWidth;
 | 
			
		||||
        mMaxCropResultHeight = maxCropResultHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // region: Private methods
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * set the max width/height and scale factor of the showen image to original image to scale the
 | 
			
		||||
     * limits appropriately.
 | 
			
		||||
     */
 | 
			
		||||
    public void setCropWindowLimits(
 | 
			
		||||
            float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) {
 | 
			
		||||
        mMaxCropWindowWidth = maxWidth;
 | 
			
		||||
        mMaxCropWindowHeight = maxHeight;
 | 
			
		||||
        mScaleFactorWidth = scaleFactorWidth;
 | 
			
		||||
        mScaleFactorHeight = scaleFactorHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the variables to be used during crop window handling.
 | 
			
		||||
     */
 | 
			
		||||
    public void setInitialAttributeValues(CropImageOptions options) {
 | 
			
		||||
        mMinCropWindowWidth = options.minCropWindowWidth;
 | 
			
		||||
        mMinCropWindowHeight = options.minCropWindowHeight;
 | 
			
		||||
        mMinCropResultWidth = options.minCropResultWidth;
 | 
			
		||||
        mMinCropResultHeight = options.minCropResultHeight;
 | 
			
		||||
        mMaxCropResultWidth = options.maxCropResultWidth;
 | 
			
		||||
        mMaxCropResultHeight = options.maxCropResultHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates whether the crop window is small enough that the guidelines should be shown. Public
 | 
			
		||||
     * because this function is also used to determine if the center handle should be focused.
 | 
			
		||||
     *
 | 
			
		||||
     * @return boolean Whether the guidelines should be shown or not
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showGuidelines() {
 | 
			
		||||
        return !(mEdges.width() < 100 || mEdges.height() < 100);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding
 | 
			
		||||
     * box, and the touch radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x            the x-coordinate of the touch point
 | 
			
		||||
     * @param y            the y-coordinate of the touch point
 | 
			
		||||
     * @param targetRadius the target radius in pixels
 | 
			
		||||
     * @return the Handle that was pressed; null if no Handle was pressed
 | 
			
		||||
     */
 | 
			
		||||
    public CropWindowMoveHandler getMoveHandler(
 | 
			
		||||
            float x, float y, float targetRadius, CropImageView.CropShape cropShape) {
 | 
			
		||||
        CropWindowMoveHandler.Type type =
 | 
			
		||||
                cropShape == CropImageView.CropShape.OVAL
 | 
			
		||||
                        ? getOvalPressedMoveType(x, y)
 | 
			
		||||
                        : getRectanglePressedMoveType(x, y, targetRadius);
 | 
			
		||||
        return type != null ? new CropWindowMoveHandler(type, this, x, y) : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding
 | 
			
		||||
     * box, and the touch radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x            the x-coordinate of the touch point
 | 
			
		||||
     * @param y            the y-coordinate of the touch point
 | 
			
		||||
     * @param targetRadius the target radius in pixels
 | 
			
		||||
     * @return the Handle that was pressed; null if no Handle was pressed
 | 
			
		||||
     */
 | 
			
		||||
    private CropWindowMoveHandler.Type getRectanglePressedMoveType(
 | 
			
		||||
            float x, float y, float targetRadius) {
 | 
			
		||||
        CropWindowMoveHandler.Type moveType = null;
 | 
			
		||||
 | 
			
		||||
        // Note: corner-handles take precedence, then side-handles, then center.
 | 
			
		||||
        if (CropWindowHandler.isInCornerTargetZone(x, y, mEdges.left, mEdges.top, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.TOP_LEFT;
 | 
			
		||||
        } else if (CropWindowHandler.isInCornerTargetZone(
 | 
			
		||||
                x, y, mEdges.right, mEdges.top, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.TOP_RIGHT;
 | 
			
		||||
        } else if (CropWindowHandler.isInCornerTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.bottom, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT;
 | 
			
		||||
        } else if (CropWindowHandler.isInCornerTargetZone(
 | 
			
		||||
                x, y, mEdges.right, mEdges.bottom, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT;
 | 
			
		||||
        } else if (CropWindowHandler.isInCenterTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom)
 | 
			
		||||
                && focusCenter()) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.CENTER;
 | 
			
		||||
        } else if (CropWindowHandler.isInHorizontalTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.right, mEdges.top, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.TOP;
 | 
			
		||||
        } else if (CropWindowHandler.isInHorizontalTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.right, mEdges.bottom, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.BOTTOM;
 | 
			
		||||
        } else if (CropWindowHandler.isInVerticalTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.top, mEdges.bottom, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.LEFT;
 | 
			
		||||
        } else if (CropWindowHandler.isInVerticalTargetZone(
 | 
			
		||||
                x, y, mEdges.right, mEdges.top, mEdges.bottom, targetRadius)) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.RIGHT;
 | 
			
		||||
        } else if (CropWindowHandler.isInCenterTargetZone(
 | 
			
		||||
                x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom)
 | 
			
		||||
                && !focusCenter()) {
 | 
			
		||||
            moveType = CropWindowMoveHandler.Type.CENTER;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return moveType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding
 | 
			
		||||
     * box/oval, and the touch radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate of the touch point
 | 
			
		||||
     * @param y the y-coordinate of the touch point
 | 
			
		||||
     * @return the Handle that was pressed; null if no Handle was pressed
 | 
			
		||||
     */
 | 
			
		||||
    private CropWindowMoveHandler.Type getOvalPressedMoveType(float x, float y) {
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
       Use a 6x6 grid system divided into 9 "handles", with the center the biggest region. While
 | 
			
		||||
       this is not perfect, it's a good quick-to-ship approach.
 | 
			
		||||
 | 
			
		||||
       TL T T T T TR
 | 
			
		||||
        L C C C C R
 | 
			
		||||
        L C C C C R
 | 
			
		||||
        L C C C C R
 | 
			
		||||
        L C C C C R
 | 
			
		||||
       BL B B B B BR
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
        float cellLength = mEdges.width() / 6;
 | 
			
		||||
        float leftCenter = mEdges.left + cellLength;
 | 
			
		||||
        float rightCenter = mEdges.left + (5 * cellLength);
 | 
			
		||||
 | 
			
		||||
        float cellHeight = mEdges.height() / 6;
 | 
			
		||||
        float topCenter = mEdges.top + cellHeight;
 | 
			
		||||
        float bottomCenter = mEdges.top + 5 * cellHeight;
 | 
			
		||||
 | 
			
		||||
        CropWindowMoveHandler.Type moveType;
 | 
			
		||||
        if (x < leftCenter) {
 | 
			
		||||
            if (y < topCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.TOP_LEFT;
 | 
			
		||||
            } else if (y < bottomCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.LEFT;
 | 
			
		||||
            } else {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (x < rightCenter) {
 | 
			
		||||
            if (y < topCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.TOP;
 | 
			
		||||
            } else if (y < bottomCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.CENTER;
 | 
			
		||||
            } else {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.BOTTOM;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (y < topCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.TOP_RIGHT;
 | 
			
		||||
            } else if (y < bottomCenter) {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.RIGHT;
 | 
			
		||||
            } else {
 | 
			
		||||
                moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return moveType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the cropper should focus on the center handle or the side handles. If it is a
 | 
			
		||||
     * small image, focus on the center handle so the user can move it. If it is a large image, focus
 | 
			
		||||
     * on the side handles so user can grab them. Corresponds to the appearance of the
 | 
			
		||||
     * RuleOfThirdsGuidelines.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if it is small enough such that it should focus on the center; less than
 | 
			
		||||
     * show_guidelines limit
 | 
			
		||||
     */
 | 
			
		||||
    private boolean focusCenter() {
 | 
			
		||||
        return !showGuidelines();
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,786 +0,0 @@
 | 
			
		|||
// "Therefore those skilled at the unorthodox
 | 
			
		||||
// are infinite as heaven and earth,
 | 
			
		||||
// inexhaustible as the great rivers.
 | 
			
		||||
// When they come to an end,
 | 
			
		||||
// they begin again,
 | 
			
		||||
// like the days and months;
 | 
			
		||||
// they die and are reborn,
 | 
			
		||||
// like the four seasons."
 | 
			
		||||
//
 | 
			
		||||
// - Sun Tsu,
 | 
			
		||||
// "The Art of War"
 | 
			
		||||
 | 
			
		||||
package com.theartofdev.edmodo.cropper;
 | 
			
		||||
 | 
			
		||||
import android.graphics.Matrix;
 | 
			
		||||
import android.graphics.PointF;
 | 
			
		||||
import android.graphics.RectF;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to update crop window edges by the move type - Horizontal, Vertical, Corner or Center.
 | 
			
		||||
 * <br>
 | 
			
		||||
 */
 | 
			
		||||
final class CropWindowMoveHandler {
 | 
			
		||||
 | 
			
		||||
    // region: Fields and Consts
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Matrix used for rectangle rotation handling
 | 
			
		||||
     */
 | 
			
		||||
    private static final Matrix MATRIX = new Matrix();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum width in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private final float mMinCropWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Minimum width in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private final float mMinCropHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum height in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private final float mMaxCropWidth;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Maximum height in pixels that the crop window can get.
 | 
			
		||||
     */
 | 
			
		||||
    private final float mMaxCropHeight;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The type of crop window move that is handled.
 | 
			
		||||
     */
 | 
			
		||||
    private final Type mType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Holds the x and y offset between the exact touch location and the exact handle location that is
 | 
			
		||||
     * activated. There may be an offset because we allow for some leeway (specified by mHandleRadius)
 | 
			
		||||
     * in activating a handle. However, we want to maintain these offset values while the handle is
 | 
			
		||||
     * being dragged so that the handle doesn't jump.
 | 
			
		||||
     */
 | 
			
		||||
    private final PointF mTouchOffset = new PointF();
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param edgeMoveType      the type of move this handler is executing
 | 
			
		||||
     * @param horizontalEdge    the primary edge associated with this handle; may be null
 | 
			
		||||
     * @param verticalEdge      the secondary edge associated with this handle; may be null
 | 
			
		||||
     * @param cropWindowHandler main crop window handle to get and update the crop window edges
 | 
			
		||||
     * @param touchX            the location of the initial toch possition to measure move distance
 | 
			
		||||
     * @param touchY            the location of the initial toch possition to measure move distance
 | 
			
		||||
     */
 | 
			
		||||
    public CropWindowMoveHandler(
 | 
			
		||||
            Type type, CropWindowHandler cropWindowHandler, float touchX, float touchY) {
 | 
			
		||||
        mType = type;
 | 
			
		||||
        mMinCropWidth = cropWindowHandler.getMinCropWidth();
 | 
			
		||||
        mMinCropHeight = cropWindowHandler.getMinCropHeight();
 | 
			
		||||
        mMaxCropWidth = cropWindowHandler.getMaxCropWidth();
 | 
			
		||||
        mMaxCropHeight = cropWindowHandler.getMaxCropHeight();
 | 
			
		||||
        calculateTouchOffset(cropWindowHandler.getRect(), touchX, touchY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the aspect ratio given a rectangle.
 | 
			
		||||
     */
 | 
			
		||||
    private static float calculateAspectRatio(float left, float top, float right, float bottom) {
 | 
			
		||||
        return (right - left) / (bottom - top);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // region: Private methods
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the crop window by change in the toch location.<br>
 | 
			
		||||
     * Move type handled by this instance, as initialized in creation, affects how the change in toch
 | 
			
		||||
     * location changes the crop window position and size.<br>
 | 
			
		||||
     * After the crop window position/size is changed by toch move it may result in values that
 | 
			
		||||
     * vialate contraints: outside the bounds of the shown bitmap, smaller/larger than min/max size or
 | 
			
		||||
     * missmatch in aspect ratio. So a series of fixes is executed on "secondary" edges to adjust it
 | 
			
		||||
     * by the "primary" edge movement.<br>
 | 
			
		||||
     * Primary is the edge directly affected by move type, secondary is the other edge.<br>
 | 
			
		||||
     * The crop window is changed by directly setting the Edge coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x                the new x-coordinate of this handle
 | 
			
		||||
     * @param y                the new y-coordinate of this handle
 | 
			
		||||
     * @param bounds           the bounding rectangle of the image
 | 
			
		||||
     * @param viewWidth        The bounding image view width used to know the crop overlay is at view edges.
 | 
			
		||||
     * @param viewHeight       The bounding image view height used to know the crop overlay is at view
 | 
			
		||||
     *                         edges.
 | 
			
		||||
     * @param parentView       the parent View containing the image
 | 
			
		||||
     * @param snapMargin       the maximum distance (in pixels) at which the crop window should snap to the
 | 
			
		||||
     *                         image
 | 
			
		||||
     * @param fixedAspectRatio is the aspect ration fixed and 'targetAspectRatio' should be used
 | 
			
		||||
     * @param aspectRatio      the aspect ratio to maintain
 | 
			
		||||
     */
 | 
			
		||||
    public void move(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float x,
 | 
			
		||||
            float y,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            int viewWidth,
 | 
			
		||||
            int viewHeight,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            boolean fixedAspectRatio,
 | 
			
		||||
            float aspectRatio) {
 | 
			
		||||
 | 
			
		||||
        // Adjust the coordinates for the finger position's offset (i.e. the
 | 
			
		||||
        // distance from the initial touch to the precise handle location).
 | 
			
		||||
        // We want to maintain the initial touch's distance to the pressed
 | 
			
		||||
        // handle so that the crop window size does not "jump".
 | 
			
		||||
        float adjX = x + mTouchOffset.x;
 | 
			
		||||
        float adjY = y + mTouchOffset.y;
 | 
			
		||||
 | 
			
		||||
        if (mType == Type.CENTER) {
 | 
			
		||||
            moveCenter(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin);
 | 
			
		||||
        } else {
 | 
			
		||||
            if (fixedAspectRatio) {
 | 
			
		||||
                moveSizeWithFixedAspectRatio(
 | 
			
		||||
                        rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin, aspectRatio);
 | 
			
		||||
            } else {
 | 
			
		||||
                moveSizeWithFreeAspectRatio(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the offset of the touch point from the precise location of the specified handle.<br>
 | 
			
		||||
     * Save these values in a member variable since we want to maintain this offset as we drag the
 | 
			
		||||
     * handle.
 | 
			
		||||
     */
 | 
			
		||||
    private void calculateTouchOffset(RectF rect, float touchX, float touchY) {
 | 
			
		||||
 | 
			
		||||
        float touchOffsetX = 0;
 | 
			
		||||
        float touchOffsetY = 0;
 | 
			
		||||
 | 
			
		||||
        // Calculate the offset from the appropriate handle.
 | 
			
		||||
        switch (mType) {
 | 
			
		||||
            case TOP_LEFT:
 | 
			
		||||
                touchOffsetX = rect.left - touchX;
 | 
			
		||||
                touchOffsetY = rect.top - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP_RIGHT:
 | 
			
		||||
                touchOffsetX = rect.right - touchX;
 | 
			
		||||
                touchOffsetY = rect.top - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_LEFT:
 | 
			
		||||
                touchOffsetX = rect.left - touchX;
 | 
			
		||||
                touchOffsetY = rect.bottom - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_RIGHT:
 | 
			
		||||
                touchOffsetX = rect.right - touchX;
 | 
			
		||||
                touchOffsetY = rect.bottom - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case LEFT:
 | 
			
		||||
                touchOffsetX = rect.left - touchX;
 | 
			
		||||
                touchOffsetY = 0;
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP:
 | 
			
		||||
                touchOffsetX = 0;
 | 
			
		||||
                touchOffsetY = rect.top - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case RIGHT:
 | 
			
		||||
                touchOffsetX = rect.right - touchX;
 | 
			
		||||
                touchOffsetY = 0;
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM:
 | 
			
		||||
                touchOffsetX = 0;
 | 
			
		||||
                touchOffsetY = rect.bottom - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            case CENTER:
 | 
			
		||||
                touchOffsetX = rect.centerX() - touchX;
 | 
			
		||||
                touchOffsetY = rect.centerY() - touchY;
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mTouchOffset.x = touchOffsetX;
 | 
			
		||||
        mTouchOffset.y = touchOffsetY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Center move only changes the position of the crop window without changing the size.
 | 
			
		||||
     */
 | 
			
		||||
    private void moveCenter(
 | 
			
		||||
            RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapRadius) {
 | 
			
		||||
        float dx = x - rect.centerX();
 | 
			
		||||
        float dy = y - rect.centerY();
 | 
			
		||||
        if (rect.left + dx < 0
 | 
			
		||||
                || rect.right + dx > viewWidth
 | 
			
		||||
                || rect.left + dx < bounds.left
 | 
			
		||||
                || rect.right + dx > bounds.right) {
 | 
			
		||||
            dx /= 1.05f;
 | 
			
		||||
            mTouchOffset.x -= dx / 2;
 | 
			
		||||
        }
 | 
			
		||||
        if (rect.top + dy < 0
 | 
			
		||||
                || rect.bottom + dy > viewHeight
 | 
			
		||||
                || rect.top + dy < bounds.top
 | 
			
		||||
                || rect.bottom + dy > bounds.bottom) {
 | 
			
		||||
            dy /= 1.05f;
 | 
			
		||||
            mTouchOffset.y -= dy / 2;
 | 
			
		||||
        }
 | 
			
		||||
        rect.offset(dx, dy);
 | 
			
		||||
        snapEdgesToBounds(rect, bounds, snapRadius);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change the size of the crop window on the required edge (or edges for corner size move) without
 | 
			
		||||
     * affecting "secondary" edges.<br>
 | 
			
		||||
     * Only the primary edge(s) are fixed to stay within limits.
 | 
			
		||||
     */
 | 
			
		||||
    private void moveSizeWithFreeAspectRatio(
 | 
			
		||||
            RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapMargin) {
 | 
			
		||||
        switch (mType) {
 | 
			
		||||
            case TOP_LEFT:
 | 
			
		||||
                adjustTop(rect, y, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                adjustLeft(rect, x, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP_RIGHT:
 | 
			
		||||
                adjustTop(rect, y, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_LEFT:
 | 
			
		||||
                adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);
 | 
			
		||||
                adjustLeft(rect, x, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_RIGHT:
 | 
			
		||||
                adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);
 | 
			
		||||
                adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case LEFT:
 | 
			
		||||
                adjustLeft(rect, x, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP:
 | 
			
		||||
                adjustTop(rect, y, bounds, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case RIGHT:
 | 
			
		||||
                adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM:
 | 
			
		||||
                adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Change the size of the crop window on the required "primary" edge WITH affect to relevant
 | 
			
		||||
     * "secondary" edge via aspect ratio.<br>
 | 
			
		||||
     * Example: change in the left edge (primary) will affect top and bottom edges (secondary) to
 | 
			
		||||
     * preserve the given aspect ratio.
 | 
			
		||||
     */
 | 
			
		||||
    private void moveSizeWithFixedAspectRatio(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float x,
 | 
			
		||||
            float y,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            int viewWidth,
 | 
			
		||||
            int viewHeight,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            float aspectRatio) {
 | 
			
		||||
        switch (mType) {
 | 
			
		||||
            case TOP_LEFT:
 | 
			
		||||
                if (calculateAspectRatio(x, y, rect.right, rect.bottom) < aspectRatio) {
 | 
			
		||||
                    adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, false);
 | 
			
		||||
                    adjustLeftByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                } else {
 | 
			
		||||
                    adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, false);
 | 
			
		||||
                    adjustTopByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP_RIGHT:
 | 
			
		||||
                if (calculateAspectRatio(rect.left, y, x, rect.bottom) < aspectRatio) {
 | 
			
		||||
                    adjustTop(rect, y, bounds, snapMargin, aspectRatio, false, true);
 | 
			
		||||
                    adjustRightByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                } else {
 | 
			
		||||
                    adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, false);
 | 
			
		||||
                    adjustTopByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_LEFT:
 | 
			
		||||
                if (calculateAspectRatio(x, rect.top, rect.right, y) < aspectRatio) {
 | 
			
		||||
                    adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, false);
 | 
			
		||||
                    adjustLeftByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                } else {
 | 
			
		||||
                    adjustLeft(rect, x, bounds, snapMargin, aspectRatio, false, true);
 | 
			
		||||
                    adjustBottomByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM_RIGHT:
 | 
			
		||||
                if (calculateAspectRatio(rect.left, rect.top, x, y) < aspectRatio) {
 | 
			
		||||
                    adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, false, true);
 | 
			
		||||
                    adjustRightByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                } else {
 | 
			
		||||
                    adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, false, true);
 | 
			
		||||
                    adjustBottomByAspectRatio(rect, aspectRatio);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case LEFT:
 | 
			
		||||
                adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, true);
 | 
			
		||||
                adjustTopBottomByAspectRatio(rect, bounds, aspectRatio);
 | 
			
		||||
                break;
 | 
			
		||||
            case TOP:
 | 
			
		||||
                adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, true);
 | 
			
		||||
                adjustLeftRightByAspectRatio(rect, bounds, aspectRatio);
 | 
			
		||||
                break;
 | 
			
		||||
            case RIGHT:
 | 
			
		||||
                adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, true);
 | 
			
		||||
                adjustTopBottomByAspectRatio(rect, bounds, aspectRatio);
 | 
			
		||||
                break;
 | 
			
		||||
            case BOTTOM:
 | 
			
		||||
                adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, true);
 | 
			
		||||
                adjustLeftRightByAspectRatio(rect, bounds, aspectRatio);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if edges have gone out of bounds (including snap margin), and fix if needed.
 | 
			
		||||
     */
 | 
			
		||||
    private void snapEdgesToBounds(RectF edges, RectF bounds, float margin) {
 | 
			
		||||
        if (edges.left < bounds.left + margin) {
 | 
			
		||||
            edges.offset(bounds.left - edges.left, 0);
 | 
			
		||||
        }
 | 
			
		||||
        if (edges.top < bounds.top + margin) {
 | 
			
		||||
            edges.offset(0, bounds.top - edges.top);
 | 
			
		||||
        }
 | 
			
		||||
        if (edges.right > bounds.right - margin) {
 | 
			
		||||
            edges.offset(bounds.right - edges.right, 0);
 | 
			
		||||
        }
 | 
			
		||||
        if (edges.bottom > bounds.bottom - margin) {
 | 
			
		||||
            edges.offset(0, bounds.bottom - edges.bottom);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the resulting x-position of the left edge of the crop window given the handle's position
 | 
			
		||||
     * and the image's bounding box and snap radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param left       the position that the left edge is dragged to
 | 
			
		||||
     * @param bounds     the bounding box of the image that is being cropped
 | 
			
		||||
     * @param snapMargin the snap distance to the image edge (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustLeft(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float left,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            float aspectRatio,
 | 
			
		||||
            boolean topMoves,
 | 
			
		||||
            boolean bottomMoves) {
 | 
			
		||||
 | 
			
		||||
        float newLeft = left;
 | 
			
		||||
 | 
			
		||||
        if (newLeft < 0) {
 | 
			
		||||
            newLeft /= 1.05f;
 | 
			
		||||
            mTouchOffset.x -= newLeft / 1.1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newLeft < bounds.left) {
 | 
			
		||||
            mTouchOffset.x -= (newLeft - bounds.left) / 2f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newLeft - bounds.left < snapMargin) {
 | 
			
		||||
            newLeft = bounds.left;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too small horizontally
 | 
			
		||||
        if (rect.right - newLeft < mMinCropWidth) {
 | 
			
		||||
            newLeft = rect.right - mMinCropWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too large horizontally
 | 
			
		||||
        if (rect.right - newLeft > mMaxCropWidth) {
 | 
			
		||||
            newLeft = rect.right - mMaxCropWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newLeft - bounds.left < snapMargin) {
 | 
			
		||||
            newLeft = bounds.left;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check vertical bounds if aspect ratio is in play
 | 
			
		||||
        if (aspectRatio > 0) {
 | 
			
		||||
            float newHeight = (rect.right - newLeft) / aspectRatio;
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too small vertically
 | 
			
		||||
            if (newHeight < mMinCropHeight) {
 | 
			
		||||
                newLeft = Math.max(bounds.left, rect.right - mMinCropHeight * aspectRatio);
 | 
			
		||||
                newHeight = (rect.right - newLeft) / aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too large vertically
 | 
			
		||||
            if (newHeight > mMaxCropHeight) {
 | 
			
		||||
                newLeft = Math.max(bounds.left, rect.right - mMaxCropHeight * aspectRatio);
 | 
			
		||||
                newHeight = (rect.right - newLeft) / aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // if top AND bottom edge moves by aspect ratio check that it is within full height bounds
 | 
			
		||||
            if (topMoves && bottomMoves) {
 | 
			
		||||
                newLeft =
 | 
			
		||||
                        Math.max(newLeft, Math.max(bounds.left, rect.right - bounds.height() * aspectRatio));
 | 
			
		||||
            } else {
 | 
			
		||||
                // if top edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (topMoves && rect.bottom - newHeight < bounds.top) {
 | 
			
		||||
                    newLeft = Math.max(bounds.left, rect.right - (rect.bottom - bounds.top) * aspectRatio);
 | 
			
		||||
                    newHeight = (rect.right - newLeft) / aspectRatio;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // if bottom edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (bottomMoves && rect.top + newHeight > bounds.bottom) {
 | 
			
		||||
                    newLeft =
 | 
			
		||||
                            Math.max(
 | 
			
		||||
                                    newLeft,
 | 
			
		||||
                                    Math.max(bounds.left, rect.right - (bounds.bottom - rect.top) * aspectRatio));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        rect.left = newLeft;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the resulting x-position of the right edge of the crop window given the handle's position
 | 
			
		||||
     * and the image's bounding box and snap radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param right      the position that the right edge is dragged to
 | 
			
		||||
     * @param bounds     the bounding box of the image that is being cropped
 | 
			
		||||
     * @param viewWidth
 | 
			
		||||
     * @param snapMargin the snap distance to the image edge (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustRight(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float right,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            int viewWidth,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            float aspectRatio,
 | 
			
		||||
            boolean topMoves,
 | 
			
		||||
            boolean bottomMoves) {
 | 
			
		||||
 | 
			
		||||
        float newRight = right;
 | 
			
		||||
 | 
			
		||||
        if (newRight > viewWidth) {
 | 
			
		||||
            newRight = viewWidth + (newRight - viewWidth) / 1.05f;
 | 
			
		||||
            mTouchOffset.x -= (newRight - viewWidth) / 1.1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newRight > bounds.right) {
 | 
			
		||||
            mTouchOffset.x -= (newRight - bounds.right) / 2f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If close to the edge
 | 
			
		||||
        if (bounds.right - newRight < snapMargin) {
 | 
			
		||||
            newRight = bounds.right;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too small horizontally
 | 
			
		||||
        if (newRight - rect.left < mMinCropWidth) {
 | 
			
		||||
            newRight = rect.left + mMinCropWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too large horizontally
 | 
			
		||||
        if (newRight - rect.left > mMaxCropWidth) {
 | 
			
		||||
            newRight = rect.left + mMaxCropWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If close to the edge
 | 
			
		||||
        if (bounds.right - newRight < snapMargin) {
 | 
			
		||||
            newRight = bounds.right;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check vertical bounds if aspect ratio is in play
 | 
			
		||||
        if (aspectRatio > 0) {
 | 
			
		||||
            float newHeight = (newRight - rect.left) / aspectRatio;
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too small vertically
 | 
			
		||||
            if (newHeight < mMinCropHeight) {
 | 
			
		||||
                newRight = Math.min(bounds.right, rect.left + mMinCropHeight * aspectRatio);
 | 
			
		||||
                newHeight = (newRight - rect.left) / aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too large vertically
 | 
			
		||||
            if (newHeight > mMaxCropHeight) {
 | 
			
		||||
                newRight = Math.min(bounds.right, rect.left + mMaxCropHeight * aspectRatio);
 | 
			
		||||
                newHeight = (newRight - rect.left) / aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // if top AND bottom edge moves by aspect ratio check that it is within full height bounds
 | 
			
		||||
            if (topMoves && bottomMoves) {
 | 
			
		||||
                newRight =
 | 
			
		||||
                        Math.min(newRight, Math.min(bounds.right, rect.left + bounds.height() * aspectRatio));
 | 
			
		||||
            } else {
 | 
			
		||||
                // if top edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (topMoves && rect.bottom - newHeight < bounds.top) {
 | 
			
		||||
                    newRight = Math.min(bounds.right, rect.left + (rect.bottom - bounds.top) * aspectRatio);
 | 
			
		||||
                    newHeight = (newRight - rect.left) / aspectRatio;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // if bottom edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (bottomMoves && rect.top + newHeight > bounds.bottom) {
 | 
			
		||||
                    newRight =
 | 
			
		||||
                            Math.min(
 | 
			
		||||
                                    newRight,
 | 
			
		||||
                                    Math.min(bounds.right, rect.left + (bounds.bottom - rect.top) * aspectRatio));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        rect.right = newRight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the resulting y-position of the top edge of the crop window given the handle's position and
 | 
			
		||||
     * the image's bounding box and snap radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param top        the x-position that the top edge is dragged to
 | 
			
		||||
     * @param bounds     the bounding box of the image that is being cropped
 | 
			
		||||
     * @param snapMargin the snap distance to the image edge (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustTop(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float top,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            float aspectRatio,
 | 
			
		||||
            boolean leftMoves,
 | 
			
		||||
            boolean rightMoves) {
 | 
			
		||||
 | 
			
		||||
        float newTop = top;
 | 
			
		||||
 | 
			
		||||
        if (newTop < 0) {
 | 
			
		||||
            newTop /= 1.05f;
 | 
			
		||||
            mTouchOffset.y -= newTop / 1.1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newTop < bounds.top) {
 | 
			
		||||
            mTouchOffset.y -= (newTop - bounds.top) / 2f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newTop - bounds.top < snapMargin) {
 | 
			
		||||
            newTop = bounds.top;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too small vertically
 | 
			
		||||
        if (rect.bottom - newTop < mMinCropHeight) {
 | 
			
		||||
            newTop = rect.bottom - mMinCropHeight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too large vertically
 | 
			
		||||
        if (rect.bottom - newTop > mMaxCropHeight) {
 | 
			
		||||
            newTop = rect.bottom - mMaxCropHeight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newTop - bounds.top < snapMargin) {
 | 
			
		||||
            newTop = bounds.top;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check horizontal bounds if aspect ratio is in play
 | 
			
		||||
        if (aspectRatio > 0) {
 | 
			
		||||
            float newWidth = (rect.bottom - newTop) * aspectRatio;
 | 
			
		||||
 | 
			
		||||
            // Checks if the crop window is too small horizontally due to aspect ratio adjustment
 | 
			
		||||
            if (newWidth < mMinCropWidth) {
 | 
			
		||||
                newTop = Math.max(bounds.top, rect.bottom - (mMinCropWidth / aspectRatio));
 | 
			
		||||
                newWidth = (rect.bottom - newTop) * aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Checks if the crop window is too large horizontally due to aspect ratio adjustment
 | 
			
		||||
            if (newWidth > mMaxCropWidth) {
 | 
			
		||||
                newTop = Math.max(bounds.top, rect.bottom - (mMaxCropWidth / aspectRatio));
 | 
			
		||||
                newWidth = (rect.bottom - newTop) * aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // if left AND right edge moves by aspect ratio check that it is within full width bounds
 | 
			
		||||
            if (leftMoves && rightMoves) {
 | 
			
		||||
                newTop = Math.max(newTop, Math.max(bounds.top, rect.bottom - bounds.width() / aspectRatio));
 | 
			
		||||
            } else {
 | 
			
		||||
                // if left edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (leftMoves && rect.right - newWidth < bounds.left) {
 | 
			
		||||
                    newTop = Math.max(bounds.top, rect.bottom - (rect.right - bounds.left) / aspectRatio);
 | 
			
		||||
                    newWidth = (rect.bottom - newTop) * aspectRatio;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // if right edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (rightMoves && rect.left + newWidth > bounds.right) {
 | 
			
		||||
                    newTop =
 | 
			
		||||
                            Math.max(
 | 
			
		||||
                                    newTop,
 | 
			
		||||
                                    Math.max(bounds.top, rect.bottom - (bounds.right - rect.left) / aspectRatio));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        rect.top = newTop;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the resulting y-position of the bottom edge of the crop window given the handle's position
 | 
			
		||||
     * and the image's bounding box and snap radius.
 | 
			
		||||
     *
 | 
			
		||||
     * @param bottom     the position that the bottom edge is dragged to
 | 
			
		||||
     * @param bounds     the bounding box of the image that is being cropped
 | 
			
		||||
     * @param viewHeight
 | 
			
		||||
     * @param snapMargin the snap distance to the image edge (in pixels)
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustBottom(
 | 
			
		||||
            RectF rect,
 | 
			
		||||
            float bottom,
 | 
			
		||||
            RectF bounds,
 | 
			
		||||
            int viewHeight,
 | 
			
		||||
            float snapMargin,
 | 
			
		||||
            float aspectRatio,
 | 
			
		||||
            boolean leftMoves,
 | 
			
		||||
            boolean rightMoves) {
 | 
			
		||||
 | 
			
		||||
        float newBottom = bottom;
 | 
			
		||||
 | 
			
		||||
        if (newBottom > viewHeight) {
 | 
			
		||||
            newBottom = viewHeight + (newBottom - viewHeight) / 1.05f;
 | 
			
		||||
            mTouchOffset.y -= (newBottom - viewHeight) / 1.1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (newBottom > bounds.bottom) {
 | 
			
		||||
            mTouchOffset.y -= (newBottom - bounds.bottom) / 2f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (bounds.bottom - newBottom < snapMargin) {
 | 
			
		||||
            newBottom = bounds.bottom;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too small vertically
 | 
			
		||||
        if (newBottom - rect.top < mMinCropHeight) {
 | 
			
		||||
            newBottom = rect.top + mMinCropHeight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Checks if the window is too small vertically
 | 
			
		||||
        if (newBottom - rect.top > mMaxCropHeight) {
 | 
			
		||||
            newBottom = rect.top + mMaxCropHeight;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (bounds.bottom - newBottom < snapMargin) {
 | 
			
		||||
            newBottom = bounds.bottom;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check horizontal bounds if aspect ratio is in play
 | 
			
		||||
        if (aspectRatio > 0) {
 | 
			
		||||
            float newWidth = (newBottom - rect.top) * aspectRatio;
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too small horizontally
 | 
			
		||||
            if (newWidth < mMinCropWidth) {
 | 
			
		||||
                newBottom = Math.min(bounds.bottom, rect.top + mMinCropWidth / aspectRatio);
 | 
			
		||||
                newWidth = (newBottom - rect.top) * aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Checks if the window is too large horizontally
 | 
			
		||||
            if (newWidth > mMaxCropWidth) {
 | 
			
		||||
                newBottom = Math.min(bounds.bottom, rect.top + mMaxCropWidth / aspectRatio);
 | 
			
		||||
                newWidth = (newBottom - rect.top) * aspectRatio;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // if left AND right edge moves by aspect ratio check that it is within full width bounds
 | 
			
		||||
            if (leftMoves && rightMoves) {
 | 
			
		||||
                newBottom =
 | 
			
		||||
                        Math.min(newBottom, Math.min(bounds.bottom, rect.top + bounds.width() / aspectRatio));
 | 
			
		||||
            } else {
 | 
			
		||||
                // if left edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (leftMoves && rect.right - newWidth < bounds.left) {
 | 
			
		||||
                    newBottom = Math.min(bounds.bottom, rect.top + (rect.right - bounds.left) / aspectRatio);
 | 
			
		||||
                    newWidth = (newBottom - rect.top) * aspectRatio;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // if right edge moves by aspect ratio check that it is within bounds
 | 
			
		||||
                if (rightMoves && rect.left + newWidth > bounds.right) {
 | 
			
		||||
                    newBottom =
 | 
			
		||||
                            Math.min(
 | 
			
		||||
                                    newBottom,
 | 
			
		||||
                                    Math.min(bounds.bottom, rect.top + (bounds.right - rect.left) / aspectRatio));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        rect.bottom = newBottom;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust left edge by current crop window height and the given aspect ratio, the right edge
 | 
			
		||||
     * remains in possition while the left adjusts to keep aspect ratio to the height.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustLeftByAspectRatio(RectF rect, float aspectRatio) {
 | 
			
		||||
        rect.left = rect.right - rect.height() * aspectRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust top edge by current crop window width and the given aspect ratio, the bottom edge
 | 
			
		||||
     * remains in possition while the top adjusts to keep aspect ratio to the width.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustTopByAspectRatio(RectF rect, float aspectRatio) {
 | 
			
		||||
        rect.top = rect.bottom - rect.width() / aspectRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust right edge by current crop window height and the given aspect ratio, the left edge
 | 
			
		||||
     * remains in possition while the left adjusts to keep aspect ratio to the height.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustRightByAspectRatio(RectF rect, float aspectRatio) {
 | 
			
		||||
        rect.right = rect.left + rect.height() * aspectRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust bottom edge by current crop window width and the given aspect ratio, the top edge
 | 
			
		||||
     * remains in possition while the top adjusts to keep aspect ratio to the width.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustBottomByAspectRatio(RectF rect, float aspectRatio) {
 | 
			
		||||
        rect.bottom = rect.top + rect.width() / aspectRatio;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust left and right edges by current crop window height and the given aspect ratio, both
 | 
			
		||||
     * right and left edges adjusts equally relative to center to keep aspect ratio to the height.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustLeftRightByAspectRatio(RectF rect, RectF bounds, float aspectRatio) {
 | 
			
		||||
        rect.inset((rect.width() - rect.height() * aspectRatio) / 2, 0);
 | 
			
		||||
        if (rect.left < bounds.left) {
 | 
			
		||||
            rect.offset(bounds.left - rect.left, 0);
 | 
			
		||||
        }
 | 
			
		||||
        if (rect.right > bounds.right) {
 | 
			
		||||
            rect.offset(bounds.right - rect.right, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjust top and bottom edges by current crop window width and the given aspect ratio, both top
 | 
			
		||||
     * and bottom edges adjusts equally relative to center to keep aspect ratio to the width.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustTopBottomByAspectRatio(RectF rect, RectF bounds, float aspectRatio) {
 | 
			
		||||
        rect.inset(0, (rect.height() - rect.width() / aspectRatio) / 2);
 | 
			
		||||
        if (rect.top < bounds.top) {
 | 
			
		||||
            rect.offset(0, bounds.top - rect.top);
 | 
			
		||||
        }
 | 
			
		||||
        if (rect.bottom > bounds.bottom) {
 | 
			
		||||
            rect.offset(0, bounds.bottom - rect.bottom);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
 | 
			
		||||
    // region: Inner class: Type
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The type of crop window move that is handled.
 | 
			
		||||
     */
 | 
			
		||||
    public enum Type {
 | 
			
		||||
        TOP_LEFT,
 | 
			
		||||
        TOP_RIGHT,
 | 
			
		||||
        BOTTOM_LEFT,
 | 
			
		||||
        BOTTOM_RIGHT,
 | 
			
		||||
        LEFT,
 | 
			
		||||
        TOP,
 | 
			
		||||
        RIGHT,
 | 
			
		||||
        BOTTOM,
 | 
			
		||||
        CENTER
 | 
			
		||||
    }
 | 
			
		||||
    // endregion
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 262 B  | 
| 
		 Before Width: | Height: | Size: 634 B  | 
| 
		 Before Width: | Height: | Size: 617 B  | 
| 
		 Before Width: | Height: | Size: 259 B  | 
| 
		 Before Width: | Height: | Size: 798 B  | 
| 
		 Before Width: | Height: | Size: 787 B  | 
| 
		 Before Width: | Height: | Size: 417 B  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB  | 
| 
		 Before Width: | Height: | Size: 1.1 KiB  | 
| 
		 Before Width: | Height: | Size: 508 B  | 
| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<com.theartofdev.edmodo.cropper.CropImageView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:id="@+id/cropImageView"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent" />
 | 
			
		||||
| 
						 | 
				
			
			@ -1,25 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
 | 
			
		||||
    <ImageView
 | 
			
		||||
        android:id="@+id/ImageView_image"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:adjustViewBounds="true"
 | 
			
		||||
        android:scaleType="centerInside"
 | 
			
		||||
        tools:ignore="contentDescription" />
 | 
			
		||||
 | 
			
		||||
    <com.theartofdev.edmodo.cropper.CropOverlayView
 | 
			
		||||
        android:id="@+id/CropOverlayView"
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="match_parent"
 | 
			
		||||
        android:visibility="invisible" />
 | 
			
		||||
 | 
			
		||||
    <ProgressBar
 | 
			
		||||
        android:id="@+id/CropProgressBar"
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center" />
 | 
			
		||||
 | 
			
		||||
</merge>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:app="http://schemas.android.com/apk/res-auto">
 | 
			
		||||
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/crop_image_menu_rotate_left"
 | 
			
		||||
        android:icon="@drawable/crop_image_menu_rotate_left"
 | 
			
		||||
        android:title="@string/crop_image_menu_rotate_left"
 | 
			
		||||
        android:visible="false"
 | 
			
		||||
        app:showAsAction="ifRoom" />
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/crop_image_menu_rotate_right"
 | 
			
		||||
        android:icon="@drawable/crop_image_menu_rotate_right"
 | 
			
		||||
        android:title="@string/crop_image_menu_rotate_right"
 | 
			
		||||
        app:showAsAction="ifRoom" />
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/crop_image_menu_flip"
 | 
			
		||||
        android:icon="@drawable/crop_image_menu_flip"
 | 
			
		||||
        android:title="@string/crop_image_menu_flip"
 | 
			
		||||
        app:showAsAction="ifRoom">
 | 
			
		||||
        <menu>
 | 
			
		||||
            <item
 | 
			
		||||
                android:id="@+id/crop_image_menu_flip_horizontally"
 | 
			
		||||
                android:title="@string/crop_image_menu_flip_horizontally" />
 | 
			
		||||
            <item
 | 
			
		||||
                android:id="@+id/crop_image_menu_flip_vertically"
 | 
			
		||||
                android:title="@string/crop_image_menu_flip_vertically" />
 | 
			
		||||
        </menu>
 | 
			
		||||
    </item>
 | 
			
		||||
    <item
 | 
			
		||||
        android:id="@+id/crop_image_menu_crop"
 | 
			
		||||
        android:title="@string/crop_image_menu_crop"
 | 
			
		||||
        app:showAsAction="always" />
 | 
			
		||||
 | 
			
		||||
</menu>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">أدر عكس اتجاه عقارب الساعة</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">أدر</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">قُصّ</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">اقلب</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">اقلب أفقيًا</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">اقلب رأسيًا</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">اختر مصدرًا</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">إلغاء؛ الأذونات المطلوبة غير ممنوحة</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Otočit proti směru hodinových ručiček</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Otočit</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Oříznout</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Překlopit</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Překlopit vodorovně</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Překlopit svisle</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Vybrat zdroj</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Probíhá storno, požadovaná povolení nejsou udělena</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">gegen den Uhrzeigersinn drehen</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">drehen</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">zuschneiden</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">spiegeln</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">horizontal spiegeln</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">vertikal spiegeln</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Quelle wählen</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Vorgang wird abgebrochen, benötigte Berechtigungen wurden nicht erteilt.</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Girar a la izquierda</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Girar a la derecha</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Cortar</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Dar la vuelta</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Voltear horizontalmente</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Voltear verticalmente</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Seleccionar fuente</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Cancelando, los permisos requeridos no se otorgaron</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Rotar a la izquierda</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Rotar a la derecha</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Cortar</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Dar la vuelta</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Voltear horizontalmente</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Voltear verticalmente</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Seleccionar fuente</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Cancelando, los permisos requeridos no han sido otorgados</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">چرخش در جهت عقربه های ساعت</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">چرخش</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">بریدن (کراپ)</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">آیینه کردن</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">آیینه کردن به صورت افقی</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">آیینه کردن به صورت عمودی</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">منبع را انتخاب کنید</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">لغو، مجوزهای مورد نیاز ارائه نشده</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Pivoter à gauche</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Pivoter à droite</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Redimensionner</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Retourner</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Retourner horizontalement</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Retourner verticalement</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Sélectionner la source</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Annulation, il manque des permissions requises</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">סובב נגד כיוון השעון</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">סובב</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">חתוך</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">הפוך</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">הפוך אופקית</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">הפוך אנכית</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">בחר מקור</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">ההרשאות הנדרשות חסרות, מבטל</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">घड़ी की सुई के विपरीत दिशा में घुमाइए</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">घुमाएँ</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">फ़सल</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">फ्लिप</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">क्षैतिज फ्लिप</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">लंबवत फ्लिप करें</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">सोर्स चुनें</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">रद्द करना, आवश्यक अनुमतियां नहीं दी गई हैं</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Putar berlawanan arah jarum jam</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Putar</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Potong</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Balik</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Balik secara horizontal</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Balik secara vertikal</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Pilih sumber</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Membatalkan, tidak mendapatkan izin yang diperlukan</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Putar berlawanan arah jarum jam</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Putar</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Potong</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Balik</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Balik secara horizontal</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Balik secara vertikal</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Pilih sumber</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Membatalkan, tidak mendapatkan izin yang diperlukan</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Ruota in senso antiorario</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Ruota</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Ritaglia</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Capovolgi</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Capovolgi orizzontalmente</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Capovolgi verticalmente</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Seleziona origine</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Annullamento in corso, autorizzazione richieste non concesse</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">左回転</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">右回転</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">切り取り</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">反転</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">左右反転</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">上下反転</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">画像を選択</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">必要な権限がありません、キャンセルしています。</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">반시계 회전</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">회전</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">자르기</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">반전</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">좌우반전</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">상하반전</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">이미지 선택</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">필수 권한이 없어서 취소합니다.</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Putar arah berlawanan jam</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Putar</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Potong</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Flip</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Flip melintang</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Flip menegak</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Pilih sumber</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Membatal, tidak mendapat kebenaran yang diperlukan</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Roter teller med urviseren</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Roter</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Beskjær</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Vend</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Vend vannrett</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Vend loddrett</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Velg kilde</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Avbryter, nødvendige tillatelser er ikke gitt</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Tegen de klok in draaien</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Draaien</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Bijsnijden</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Spiegelen</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Horizontaal spiegelen</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Verticaal spiegelen</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Bron selecteren</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Wordt geannuleerd, vereiste machtigingen zijn niet toegekend</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Obróć w lewo</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Obróć</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Przytnij</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Odbij</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Odbij poziomo</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Odbij pionowo</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Wybierz źródło</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Przerywaniem, potrzebne uprawnienia nie zostały nadane</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,14 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Girar para a esquerda</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Girar para a direita</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Cortar</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Espelhar</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Espelhar na horizontal</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Espelhar na vertifcal</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Escolher foto a partir de</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Повернуть налево</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Повернуть направо</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Обрезать</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Отразить</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Отразить по горизонтали</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Отразить по вертикали</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Выбрать источник</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Rotera vänster</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Rotera höger</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Beskär</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Vänd</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Vänd horisontellt</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Vänd vertikalt</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Välj bild</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Avbryter, nödvändiga behörigheter beviljas inte</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Saat yönünde döndür</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">döndürmek</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">ekin</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">fiske</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Yatay olarak çevir</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Dikey olarak çevir</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Kaynağı seçin</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">İptal ediliyor, gerekli izinler verilmiyor</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">گھڑی وار گھڑی گھومیں</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">گھمائیں</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">فصل</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">پلٹائیں</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">افقی پلٹائیں</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">عمودی طور پر پلٹائیں</string>
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">ذریعہ منتخب کریں</string>
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">منسوخ کرنا، ضروری اجازت نہیں دی جاتی ہیں</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Xoay theo chiều kim đồng hồ</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Xoay</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Cắt</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Lật</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Lật theo chiều ngang</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Lật theo chiều dọc</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Chọn nguồn</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Đang hủy, các quyền đã yêu cầu không được cấp</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">逆时针旋转</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">旋转</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">裁切</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">翻转</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">水平翻转</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">垂直翻转</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">选择来源</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">取消中,未授予所需权限</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">逆時針旋轉</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">旋轉</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">裁切</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">翻轉</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">水平翻轉</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">垂直翻轉</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">選擇來源</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">取消中,未授予所需權限</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">逆时针旋转</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">旋转</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">裁剪</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">翻转</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">水平翻转</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">垂直翻转</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">选择来源</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">正在取消,该操作未获得所需权限。</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,50 +0,0 @@
 | 
			
		|||
<resources>
 | 
			
		||||
 | 
			
		||||
    <declare-styleable name="CropImageView">
 | 
			
		||||
        <attr name="cropGuidelines">
 | 
			
		||||
            <enum name="off" value="0" />
 | 
			
		||||
            <enum name="onTouch" value="1" />
 | 
			
		||||
            <enum name="on" value="2" />
 | 
			
		||||
        </attr>
 | 
			
		||||
        <attr name="cropScaleType">
 | 
			
		||||
            <enum name="fitCenter" value="0" />
 | 
			
		||||
            <enum name="center" value="1" />
 | 
			
		||||
            <enum name="centerCrop" value="2" />
 | 
			
		||||
            <enum name="centerInside" value="3" />
 | 
			
		||||
        </attr>
 | 
			
		||||
        <attr name="cropShape">
 | 
			
		||||
            <enum name="rectangle" value="0" />
 | 
			
		||||
            <enum name="oval" value="1" />
 | 
			
		||||
        </attr>
 | 
			
		||||
        <attr name="cropAutoZoomEnabled" format="boolean" />
 | 
			
		||||
        <attr name="cropMaxZoom" format="integer" />
 | 
			
		||||
        <attr name="cropMultiTouchEnabled" format="boolean" />
 | 
			
		||||
        <attr name="cropFixAspectRatio" format="boolean" />
 | 
			
		||||
        <attr name="cropAspectRatioX" format="integer" />
 | 
			
		||||
        <attr name="cropAspectRatioY" format="integer" />
 | 
			
		||||
        <attr name="cropInitialCropWindowPaddingRatio" format="float" />
 | 
			
		||||
        <attr name="cropBorderLineThickness" format="dimension" />
 | 
			
		||||
        <attr name="cropBorderLineColor" format="color" />
 | 
			
		||||
        <attr name="cropBorderCornerThickness" format="dimension" />
 | 
			
		||||
        <attr name="cropBorderCornerOffset" format="dimension" />
 | 
			
		||||
        <attr name="cropBorderCornerLength" format="dimension" />
 | 
			
		||||
        <attr name="cropBorderCornerColor" format="color" />
 | 
			
		||||
        <attr name="cropGuidelinesThickness" format="dimension" />
 | 
			
		||||
        <attr name="cropGuidelinesColor" format="color" />
 | 
			
		||||
        <attr name="cropBackgroundColor" format="color" />
 | 
			
		||||
        <attr name="cropSnapRadius" format="dimension" />
 | 
			
		||||
        <attr name="cropTouchRadius" format="dimension" />
 | 
			
		||||
        <attr name="cropSaveBitmapToInstanceState" format="boolean" />
 | 
			
		||||
        <attr name="cropShowCropOverlay" format="boolean" />
 | 
			
		||||
        <attr name="cropShowProgressBar" format="boolean" />
 | 
			
		||||
        <attr name="cropMinCropWindowWidth" format="dimension" />
 | 
			
		||||
        <attr name="cropMinCropWindowHeight" format="dimension" />
 | 
			
		||||
        <attr name="cropMinCropResultWidthPX" format="float" />
 | 
			
		||||
        <attr name="cropMinCropResultHeightPX" format="float" />
 | 
			
		||||
        <attr name="cropMaxCropResultWidthPX" format="float" />
 | 
			
		||||
        <attr name="cropMaxCropResultHeightPX" format="float" />
 | 
			
		||||
        <attr name="cropFlipHorizontally" format="boolean" />
 | 
			
		||||
        <attr name="cropFlipVertically" format="boolean" />
 | 
			
		||||
    </declare-styleable>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_title"></string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_left">Rotate counter clockwise</string>
 | 
			
		||||
    <string name="crop_image_menu_rotate_right">Rotate</string>
 | 
			
		||||
    <string name="crop_image_menu_crop">Crop</string>
 | 
			
		||||
    <string name="crop_image_menu_flip">Flip</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_horizontally">Flip horizontally</string>
 | 
			
		||||
    <string name="crop_image_menu_flip_vertically">Flip vertically</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pick_image_intent_chooser_title">Select source</string>
 | 
			
		||||
 | 
			
		||||
    <string name="crop_image_activity_no_permissions">Cancelling, required permissions are not granted</string>
 | 
			
		||||
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -3,4 +3,3 @@ include ':app'
 | 
			
		|||
include ':autoimageslider'
 | 
			
		||||
include ':mytransl'
 | 
			
		||||
include ':ratethisapp'
 | 
			
		||||
include ':cropper'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||