[
  {
    "path": ".gitignore",
    "content": "\n# built application files\n*.apk\n*.ap_\n\n# files for the dex VM\n*.dex\n\n# Java class files\n*.class\n\n# generated files\nbin/\ngen/\nout/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Mac OS X internal files\n.DS_Store\n\n# Eclipse generated files/folders\n.metadata/\n.settings/\n\n#IntelliJ IDEA\n.idea\n*.iml\n*.ipr\n*.iws\nout\n\n# Gradle folder\n.gradle/\nbuild/\n\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\njdk: oraclejdk8\nsudo: false\n\nandroid:\n  components:\n    - tools\n    - platform-tools\n    - build-tools-28.0.3\n    - android-28\n    - extra-android-m2repository\n\nscript:\n - ./gradlew clean build\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2016, Arthur Teplitzki 2013, Edmodo, Inc.\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "Android Image Cropper\n=======\n\n# :triangular_flag_on_post: The Project is NOT currently maintained :triangular_flag_on_post:\n\n## Please use **[CanHub's fork](https://github.com/CanHub/Android-Image-Cropper)!**\n\n### Thank everybody for using the library. It was very fun to create and a privilage to help you build awesome apps.\n### The same way I took an unmaintained initial implementation from [edmodo](https://github.com/edmodo/cropper), I'm happy to see [CanHub](https://github.com/CanHub/Android-Image-Cropper) taking it now.\n### Good luck and happy coding :octocat:\n\n\n----\n----\n[![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android--Image--Cropper-green.svg?style=true)](https://android-arsenal.com/details/1/3487)\n[![Build Status](https://travis-ci.org/ArthurHub/Android-Image-Cropper.svg?branch=master)](https://travis-ci.org/ArthurHub/Android-Image-Cropper)\n\n**Powerful** (Zoom, Rotation, Multi-Source), **customizable** (Shape, Limits, Style), **optimized** (Async, Sampling, Matrix) and **simple** image cropping library for Android.\n\n![Crop](https://github.com/ArthurHub/Android-Image-Cropper/blob/master/art/demo.gif?raw=true)\n\n## Usage\n*For a working implementation, please have a look at the Sample Project*\n\n[See GitHub Wiki for more info.](https://github.com/ArthurHub/Android-Image-Cropper/wiki)\n\n1. Include the library\n\n ```\n dependencies {\n     api 'com.theartofdev.edmodo:android-image-cropper:2.8.+'\n }\n ```\n\nAdd permissions to manifest\n\n ```\n <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n ```\nAdd this line to your Proguard config file\n\n```\n-keep class androidx.appcompat.widget.** { *; }\n```\n### Using Activity\n\n2. Add `CropImageActivity` into your AndroidManifest.xml\n ```xml\n <activity android:name=\"com.theartofdev.edmodo.cropper.CropImageActivity\"\n   android:theme=\"@style/Base.Theme.AppCompat\"/> <!-- optional (needed if default theme has no action bar) -->\n ```\n\n3. Start `CropImageActivity` using builder pattern from your activity\n ```java\n // start picker to get image for cropping and then use the image in cropping activity\n CropImage.activity()\n   .setGuidelines(CropImageView.Guidelines.ON)\n   .start(this);\n\n // start cropping activity for pre-acquired image saved on the device\n CropImage.activity(imageUri)\n  .start(this);\n\n // for fragment (DO NOT use `getActivity()`)\n CropImage.activity()\n   .start(getContext(), this);\n ```\n\n4. Override `onActivityResult` method in your activity to get crop result\n ```java\n @Override\n public void onActivityResult(int requestCode, int resultCode, Intent data) {\n   if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {\n     CropImage.ActivityResult result = CropImage.getActivityResult(data);\n     if (resultCode == RESULT_OK) {\n       Uri resultUri = result.getUri();\n     } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {\n       Exception error = result.getError();\n     }\n   }\n }\n ```\n\n### Using View\n2. Add `CropImageView` into your activity\n ```xml\n <!-- Image Cropper fill the remaining available height -->\n <com.theartofdev.edmodo.cropper.CropImageView\n   xmlns:custom=\"http://schemas.android.com/apk/res-auto\"\n   android:id=\"@+id/cropImageView\"\n   android:layout_width=\"match_parent\"\n   android:layout_height=\"0dp\"\n   android:layout_weight=\"1\"/>\n ```\n\n3. Set image to crop\n ```java\n cropImageView.setImageUriAsync(uri);\n // or (prefer using uri for performance and better user experience)\n cropImageView.setImageBitmap(bitmap);\n ```\n\n4. Get cropped image\n ```java\n // subscribe to async event using cropImageView.setOnCropImageCompleteListener(listener)\n cropImageView.getCroppedImageAsync();\n // or\n Bitmap cropped = cropImageView.getCroppedImage();\n ```\n\n## Features\n- Built-in `CropImageActivity`.\n- Set cropping image as Bitmap, Resource or Android URI (Gallery, Camera, Dropbox, etc.).\n- Image rotation/flipping during cropping.\n- Auto zoom-in/out to relevant cropping area.\n- Auto rotate bitmap by image Exif data.\n- Set result image min/max limits in pixels.\n- Set initial crop window size/location.\n- Request cropped image resize to specific size.\n- Bitmap memory optimization, OOM handling (should never occur)!\n- API Level 14.\n- More..\n \n## Customizations\n- Cropping window shape: Rectangular or Oval (cube/circle by fixing aspect ratio).\n- Cropping window aspect ratio: Free, 1:1, 4:3, 16:9 or Custom.\n- Guidelines appearance: Off / Always On / Show on Toch.\n- Cropping window Border line, border corner and guidelines thickness and color.\n- Cropping background color.\n\nFor more information, see the [GitHub Wiki](https://github.com/ArthurHub/Android-Image-Cropper/wiki). \n\n## Posts\n - [Android cropping image from camera or gallery](http://theartofdev.com/2015/02/15/android-cropping-image-from-camera-or-gallery/)\n - [Android Image Cropper async support and custom progress UI](http://theartofdev.com/2016/01/15/android-image-cropper-async-support-and-custom-progress-ui/)\n - [Adding auto-zoom feature to Android-Image-Cropper](https://theartofdev.com/2016/04/25/adding-auto-zoom-feature-to-android-image-cropper/)\n\n## Change log\n*2.8.0*\n- Fix crash on Android O (thx @juliooa)\n- Update to support library to AndroidX (thx @mradzinski)\n- Handle failure when selecting non image file (thx @uncledoc)\n- More translations (thx @jkwiecien, @david-serrano)\n\n*2.7.0*\n- Update gradle wrapper to 4.4\n- Update support library to 27.1.1 and set is statically! (thx @androideveloper)\n- Fix NPE in activity creation by tools (thx @unverbraucht)\n- More translations (thx @gwharvey, @dlackty, @JairoGeek, @shaymargolis)\n\nSee [full change log](https://github.com/ArthurHub/Android-Image-Cropper/wiki/Change-Log).\n\n## License\nOriginally forked from [edmodo/cropper](https://github.com/edmodo/cropper).\n\nCopyright 2016, Arthur Teplitzki, 2013, Edmodo, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use this work except in compliance with the   License.\nYou may obtain a copy of the License in the LICENSE file, or at:\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \"AS   IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        jcenter()\n        google()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:3.2.1'\n    }\n}\n\nallprojects {\n    repositories {\n        jcenter()\n        maven {\n            url \"https://maven.google.com\"\n        }\n    }\n}\n\next {\n    compileSdkVersion = 28\n    buildToolsVersion = '28.0.3'\n    androidXLibraryVersion = '1.0.0'\n\n    PUBLISH_GROUP_ID = 'com.theartofdev.edmodo'\n    PUBLISH_ARTIFACT_ID = 'android-image-cropper'\n    PUBLISH_VERSION = '2.8.0'\n    // gradlew clean build generateRelease\n}\n"
  },
  {
    "path": "cropper/build.gradle",
    "content": "apply plugin: 'com.android.library'\n// https://docs.gradle.org/current/userguide/publishing_maven.html\n// http://www.flexlabs.org/2013/06/using-local-aar-android-library-packages-in-gradle-builds\napply plugin: 'maven-publish'\n\nandroid {\n\n    compileSdkVersion rootProject.compileSdkVersion\n    buildToolsVersion rootProject.buildToolsVersion\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.compileSdkVersion\n        versionCode 1\n        versionName PUBLISH_VERSION\n    }\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_1_7\n        targetCompatibility JavaVersion.VERSION_1_7\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\n// This configuration is used to publish the library to a local repo while a being forked and modified.\n// It should really be set up so that the version are all in line, and set to be a SNAPSHOT.\n// The version listed here is a temp hack to allow me to keep working.\nandroid.libraryVariants\npublishing {\n    publications {\n        maven(MavenPublication) {\n\n            groupId PUBLISH_GROUP_ID\n            artifactId PUBLISH_ARTIFACT_ID\n            version PUBLISH_VERSION + '-SNAPSHOT'\n\n            //artifact bundleRelease\n        }\n    }\n}\n\napply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle'\n\ndependencies {\n    api \"androidx.appcompat:appcompat:$androidXLibraryVersion\"\n    implementation \"androidx.exifinterface:exifinterface:$androidXLibraryVersion\"\n}\n\n"
  },
  {
    "path": "cropper/src/main/AndroidManifest.xml",
    "content": "<manifest\n    package=\"com.theartofdev.edmodo.cropper\">\n\n</manifest>\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.AsyncTask;\n\nimport java.lang.ref.WeakReference;\n\n/** Task to crop bitmap asynchronously from the UI thread. */\nfinal class BitmapCroppingWorkerTask\n    extends AsyncTask<Void, Void, BitmapCroppingWorkerTask.Result> {\n\n  // region: Fields and Consts\n\n  /** Use a WeakReference to ensure the ImageView can be garbage collected */\n  private final WeakReference<CropImageView> mCropImageViewReference;\n\n  /** the bitmap to crop */\n  private final Bitmap mBitmap;\n\n  /** The Android URI of the image to load */\n  private final Uri mUri;\n\n  /** The context of the crop image view widget used for loading of bitmap by Android URI */\n  private final Context mContext;\n\n  /** Required cropping 4 points (x0,y0,x1,y1,x2,y2,x3,y3) */\n  private final float[] mCropPoints;\n\n  /** Degrees the image was rotated after loading */\n  private final int mDegreesRotated;\n\n  /** the original width of the image to be cropped (for image loaded from URI) */\n  private final int mOrgWidth;\n\n  /** the original height of the image to be cropped (for image loaded from URI) */\n  private final int mOrgHeight;\n\n  /** is there is fixed aspect ratio for the crop rectangle */\n  private final boolean mFixAspectRatio;\n\n  /** the X aspect ration of the crop rectangle */\n  private final int mAspectRatioX;\n\n  /** the Y aspect ration of the crop rectangle */\n  private final int mAspectRatioY;\n\n  /** required width of the cropping image */\n  private final int mReqWidth;\n\n  /** required height of the cropping image */\n  private final int mReqHeight;\n\n  /** is the image flipped horizontally */\n  private final boolean mFlipHorizontally;\n\n  /** is the image flipped vertically */\n  private final boolean mFlipVertically;\n\n  /** The option to handle requested width/height */\n  private final CropImageView.RequestSizeOptions mReqSizeOptions;\n\n  /** the Android Uri to save the cropped image to */\n  private final Uri mSaveUri;\n\n  /** the compression format to use when writing the image */\n  private final Bitmap.CompressFormat mSaveCompressFormat;\n\n  /** the quality (if applicable) to use when writing the image (0 - 100) */\n  private final int mSaveCompressQuality;\n  // endregion\n\n  BitmapCroppingWorkerTask(\n      CropImageView cropImageView,\n      Bitmap bitmap,\n      float[] cropPoints,\n      int degreesRotated,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      int reqWidth,\n      int reqHeight,\n      boolean flipHorizontally,\n      boolean flipVertically,\n      CropImageView.RequestSizeOptions options,\n      Uri saveUri,\n      Bitmap.CompressFormat saveCompressFormat,\n      int saveCompressQuality) {\n\n    mCropImageViewReference = new WeakReference<>(cropImageView);\n    mContext = cropImageView.getContext();\n    mBitmap = bitmap;\n    mCropPoints = cropPoints;\n    mUri = null;\n    mDegreesRotated = degreesRotated;\n    mFixAspectRatio = fixAspectRatio;\n    mAspectRatioX = aspectRatioX;\n    mAspectRatioY = aspectRatioY;\n    mReqWidth = reqWidth;\n    mReqHeight = reqHeight;\n    mFlipHorizontally = flipHorizontally;\n    mFlipVertically = flipVertically;\n    mReqSizeOptions = options;\n    mSaveUri = saveUri;\n    mSaveCompressFormat = saveCompressFormat;\n    mSaveCompressQuality = saveCompressQuality;\n    mOrgWidth = 0;\n    mOrgHeight = 0;\n  }\n\n  BitmapCroppingWorkerTask(\n      CropImageView cropImageView,\n      Uri uri,\n      float[] cropPoints,\n      int degreesRotated,\n      int orgWidth,\n      int orgHeight,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      int reqWidth,\n      int reqHeight,\n      boolean flipHorizontally,\n      boolean flipVertically,\n      CropImageView.RequestSizeOptions options,\n      Uri saveUri,\n      Bitmap.CompressFormat saveCompressFormat,\n      int saveCompressQuality) {\n\n    mCropImageViewReference = new WeakReference<>(cropImageView);\n    mContext = cropImageView.getContext();\n    mUri = uri;\n    mCropPoints = cropPoints;\n    mDegreesRotated = degreesRotated;\n    mFixAspectRatio = fixAspectRatio;\n    mAspectRatioX = aspectRatioX;\n    mAspectRatioY = aspectRatioY;\n    mOrgWidth = orgWidth;\n    mOrgHeight = orgHeight;\n    mReqWidth = reqWidth;\n    mReqHeight = reqHeight;\n    mFlipHorizontally = flipHorizontally;\n    mFlipVertically = flipVertically;\n    mReqSizeOptions = options;\n    mSaveUri = saveUri;\n    mSaveCompressFormat = saveCompressFormat;\n    mSaveCompressQuality = saveCompressQuality;\n    mBitmap = null;\n  }\n\n  /** The Android URI that this task is currently loading. */\n  public Uri getUri() {\n    return mUri;\n  }\n\n  /**\n   * Crop image in background.\n   *\n   * @param params ignored\n   * @return the decoded bitmap data\n   */\n  @Override\n  protected BitmapCroppingWorkerTask.Result doInBackground(Void... params) {\n    try {\n      if (!isCancelled()) {\n\n        BitmapUtils.BitmapSampled bitmapSampled;\n        if (mUri != null) {\n          bitmapSampled =\n              BitmapUtils.cropBitmap(\n                  mContext,\n                  mUri,\n                  mCropPoints,\n                  mDegreesRotated,\n                  mOrgWidth,\n                  mOrgHeight,\n                  mFixAspectRatio,\n                  mAspectRatioX,\n                  mAspectRatioY,\n                  mReqWidth,\n                  mReqHeight,\n                  mFlipHorizontally,\n                  mFlipVertically);\n        } else if (mBitmap != null) {\n          bitmapSampled =\n              BitmapUtils.cropBitmapObjectHandleOOM(\n                  mBitmap,\n                  mCropPoints,\n                  mDegreesRotated,\n                  mFixAspectRatio,\n                  mAspectRatioX,\n                  mAspectRatioY,\n                  mFlipHorizontally,\n                  mFlipVertically);\n        } else {\n          return new Result((Bitmap) null, 1);\n        }\n\n        Bitmap bitmap =\n            BitmapUtils.resizeBitmap(bitmapSampled.bitmap, mReqWidth, mReqHeight, mReqSizeOptions);\n\n        if (mSaveUri == null) {\n          return new Result(bitmap, bitmapSampled.sampleSize);\n        } else {\n          BitmapUtils.writeBitmapToUri(\n              mContext, bitmap, mSaveUri, mSaveCompressFormat, mSaveCompressQuality);\n          if (bitmap != null) {\n            bitmap.recycle();\n          }\n          return new Result(mSaveUri, bitmapSampled.sampleSize);\n        }\n      }\n      return null;\n    } catch (Exception e) {\n      return new Result(e, mSaveUri != null);\n    }\n  }\n\n  /**\n   * Once complete, see if ImageView is still around and set bitmap.\n   *\n   * @param result the result of bitmap cropping\n   */\n  @Override\n  protected void onPostExecute(Result result) {\n    if (result != null) {\n      boolean completeCalled = false;\n      if (!isCancelled()) {\n        CropImageView cropImageView = mCropImageViewReference.get();\n        if (cropImageView != null) {\n          completeCalled = true;\n          cropImageView.onImageCroppingAsyncComplete(result);\n        }\n      }\n      if (!completeCalled && result.bitmap != null) {\n        // fast release of unused bitmap\n        result.bitmap.recycle();\n      }\n    }\n  }\n\n  // region: Inner class: Result\n\n  /** The result of BitmapCroppingWorkerTask async loading. */\n  static final class Result {\n\n    /** The cropped bitmap */\n    public final Bitmap bitmap;\n\n    /** The saved cropped bitmap uri */\n    public final Uri uri;\n\n    /** The error that occurred during async bitmap cropping. */\n    final Exception error;\n\n    /** is the cropping request was to get a bitmap or to save it to uri */\n    final boolean isSave;\n\n    /** sample size used creating the crop bitmap to lower its size */\n    final int sampleSize;\n\n    Result(Bitmap bitmap, int sampleSize) {\n      this.bitmap = bitmap;\n      this.uri = null;\n      this.error = null;\n      this.isSave = false;\n      this.sampleSize = sampleSize;\n    }\n\n    Result(Uri uri, int sampleSize) {\n      this.bitmap = null;\n      this.uri = uri;\n      this.error = null;\n      this.isSave = true;\n      this.sampleSize = sampleSize;\n    }\n\n    Result(Exception error, boolean isSave) {\n      this.bitmap = null;\n      this.uri = null;\n      this.error = error;\n      this.isSave = isSave;\n      this.sampleSize = 1;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.util.DisplayMetrics;\n\nimport java.lang.ref.WeakReference;\n\n/** Task to load bitmap asynchronously from the UI thread. */\nfinal class BitmapLoadingWorkerTask extends AsyncTask<Void, Void, BitmapLoadingWorkerTask.Result> {\n\n  // region: Fields and Consts\n\n  /** Use a WeakReference to ensure the ImageView can be garbage collected */\n  private final WeakReference<CropImageView> mCropImageViewReference;\n\n  /** The Android URI of the image to load */\n  private final Uri mUri;\n\n  /** The context of the crop image view widget used for loading of bitmap by Android URI */\n  private final Context mContext;\n\n  /** required width of the cropping image after density adjustment */\n  private final int mWidth;\n\n  /** required height of the cropping image after density adjustment */\n  private final int mHeight;\n  // endregion\n\n  public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri) {\n    mUri = uri;\n    mCropImageViewReference = new WeakReference<>(cropImageView);\n\n    mContext = cropImageView.getContext();\n\n    DisplayMetrics metrics = cropImageView.getResources().getDisplayMetrics();\n    double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1;\n    mWidth = (int) (metrics.widthPixels * densityAdj);\n    mHeight = (int) (metrics.heightPixels * densityAdj);\n  }\n\n  /** The Android URI that this task is currently loading. */\n  public Uri getUri() {\n    return mUri;\n  }\n\n  /**\n   * Decode image in background.\n   *\n   * @param params ignored\n   * @return the decoded bitmap data\n   */\n  @Override\n  protected Result doInBackground(Void... params) {\n    try {\n      if (!isCancelled()) {\n\n        BitmapUtils.BitmapSampled decodeResult =\n            BitmapUtils.decodeSampledBitmap(mContext, mUri, mWidth, mHeight);\n\n        if (!isCancelled()) {\n\n          BitmapUtils.RotateBitmapResult rotateResult =\n              BitmapUtils.rotateBitmapByExif(decodeResult.bitmap, mContext, mUri);\n\n          return new Result(\n              mUri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees);\n        }\n      }\n      return null;\n    } catch (Exception e) {\n      return new Result(mUri, e);\n    }\n  }\n\n  /**\n   * Once complete, see if ImageView is still around and set bitmap.\n   *\n   * @param result the result of bitmap loading\n   */\n  @Override\n  protected void onPostExecute(Result result) {\n    if (result != null) {\n      boolean completeCalled = false;\n      if (!isCancelled()) {\n        CropImageView cropImageView = mCropImageViewReference.get();\n        if (cropImageView != null) {\n          completeCalled = true;\n          cropImageView.onSetImageUriAsyncComplete(result);\n        }\n      }\n      if (!completeCalled && result.bitmap != null) {\n        // fast release of unused bitmap\n        result.bitmap.recycle();\n      }\n    }\n  }\n\n  // region: Inner class: Result\n\n  /** The result of BitmapLoadingWorkerTask async loading. */\n  public static final class Result {\n\n    /** The Android URI of the image to load */\n    public final Uri uri;\n\n    /** The loaded bitmap */\n    public final Bitmap bitmap;\n\n    /** The sample size used to load the given bitmap */\n    public final int loadSampleSize;\n\n    /** The degrees the image was rotated */\n    public final int degreesRotated;\n\n    /** The error that occurred during async bitmap loading. */\n    public final Exception error;\n\n    Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotated) {\n      this.uri = uri;\n      this.bitmap = bitmap;\n      this.loadSampleSize = loadSampleSize;\n      this.degreesRotated = degreesRotated;\n      this.error = null;\n    }\n\n    Result(Uri uri, Exception error) {\n      this.uri = uri;\n      this.bitmap = null;\n      this.loadSampleSize = 0;\n      this.degreesRotated = 0;\n      this.error = error;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.BitmapRegionDecoder;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.net.Uri;\nimport android.util.Log;\nimport android.util.Pair;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.lang.ref.WeakReference;\n\nimport javax.microedition.khronos.egl.EGL10;\nimport javax.microedition.khronos.egl.EGLConfig;\nimport javax.microedition.khronos.egl.EGLContext;\nimport javax.microedition.khronos.egl.EGLDisplay;\n\nimport androidx.exifinterface.media.ExifInterface;\n\n/** Utility class that deals with operations with an ImageView. */\nfinal class BitmapUtils {\n\n  static final Rect EMPTY_RECT = new Rect();\n\n  static final RectF EMPTY_RECT_F = new RectF();\n\n  /** Reusable rectangle for general internal usage */\n  static final RectF RECT = new RectF();\n\n  /** Reusable point for general internal usage */\n  static final float[] POINTS = new float[6];\n\n  /** Reusable point for general internal usage */\n  static final float[] POINTS2 = new float[6];\n\n  /** Used to know the max texture size allowed to be rendered */\n  private static int mMaxTextureSize;\n\n  /** used to save bitmaps during state save and restore so not to reload them. */\n  static Pair<String, WeakReference<Bitmap>> mStateBitmap;\n\n  /**\n   * Rotate the given image by reading the Exif value of the image (uri).<br>\n   * If no rotation is required the image will not be rotated.<br>\n   * New bitmap is created and the old one is recycled.\n   */\n  static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, Context context, Uri uri) {\n    ExifInterface ei = null;\n    try {\n      InputStream is = context.getContentResolver().openInputStream(uri);\n      if (is != null) {\n        ei = new ExifInterface(is);\n        is.close();\n      }\n    } catch (Exception ignored) {\n    }\n    return ei != null ? rotateBitmapByExif(bitmap, ei) : new RotateBitmapResult(bitmap, 0);\n  }\n\n  /**\n   * Rotate the given image by given Exif value.<br>\n   * If no rotation is required the image will not be rotated.<br>\n   * New bitmap is created and the old one is recycled.\n   */\n  static RotateBitmapResult rotateBitmapByExif(Bitmap bitmap, ExifInterface exif) {\n    int degrees;\n    int orientation =\n        exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);\n    switch (orientation) {\n      case ExifInterface.ORIENTATION_ROTATE_90:\n        degrees = 90;\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_180:\n        degrees = 180;\n        break;\n      case ExifInterface.ORIENTATION_ROTATE_270:\n        degrees = 270;\n        break;\n      default:\n        degrees = 0;\n        break;\n    }\n    return new RotateBitmapResult(bitmap, degrees);\n  }\n\n  /** Decode bitmap from stream using sampling to get bitmap with the requested limit. */\n  static BitmapSampled decodeSampledBitmap(Context context, Uri uri, int reqWidth, int reqHeight) {\n\n    try {\n      ContentResolver resolver = context.getContentResolver();\n\n      // First decode with inJustDecodeBounds=true to check dimensions\n      BitmapFactory.Options options = decodeImageForOption(resolver, uri);\n\n      if(options.outWidth  == -1 && options.outHeight == -1)\n        throw new RuntimeException(\"File is not a picture\");\n\n      // Calculate inSampleSize\n      options.inSampleSize =\n          Math.max(\n              calculateInSampleSizeByReqestedSize(\n                  options.outWidth, options.outHeight, reqWidth, reqHeight),\n              calculateInSampleSizeByMaxTextureSize(options.outWidth, options.outHeight));\n\n      // Decode bitmap with inSampleSize set\n      Bitmap bitmap = decodeImage(resolver, uri, options);\n\n      return new BitmapSampled(bitmap, options.inSampleSize);\n\n    } catch (Exception e) {\n      throw new RuntimeException(\n          \"Failed to load sampled bitmap: \" + uri + \"\\r\\n\" + e.getMessage(), e);\n    }\n  }\n\n  /**\n   * Crop image bitmap from given bitmap using the given points in the original bitmap and the given\n   * rotation.<br>\n   * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the\n   * image that contains the requires rectangle, rotate and then crop again a sub rectangle.<br>\n   * If crop fails due to OOM we scale the cropping image by 0.5 every time it fails until it is\n   * small enough.\n   */\n  static BitmapSampled cropBitmapObjectHandleOOM(\n      Bitmap bitmap,\n      float[] points,\n      int degreesRotated,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      boolean flipHorizontally,\n      boolean flipVertically) {\n    int scale = 1;\n    while (true) {\n      try {\n        Bitmap cropBitmap =\n            cropBitmapObjectWithScale(\n                bitmap,\n                points,\n                degreesRotated,\n                fixAspectRatio,\n                aspectRatioX,\n                aspectRatioY,\n                1 / (float) scale,\n                flipHorizontally,\n                flipVertically);\n        return new BitmapSampled(cropBitmap, scale);\n      } catch (OutOfMemoryError e) {\n        scale *= 2;\n        if (scale > 8) {\n          throw e;\n        }\n      }\n    }\n  }\n\n  /**\n   * Crop image bitmap from given bitmap using the given points in the original bitmap and the given\n   * rotation.<br>\n   * if the rotation is not 0,90,180 or 270 degrees then we must first crop a larger area of the\n   * image that contains the requires rectangle, rotate and then crop again a sub rectangle.\n   *\n   * @param scale how much to scale the cropped image part, use 0.5 to lower the image by half (OOM\n   *     handling)\n   */\n  private static Bitmap cropBitmapObjectWithScale(\n      Bitmap bitmap,\n      float[] points,\n      int degreesRotated,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      float scale,\n      boolean flipHorizontally,\n      boolean flipVertically) {\n\n    // get the rectangle in original image that contains the required cropped area (larger for non\n    // rectangular crop)\n    Rect rect =\n        getRectFromPoints(\n            points,\n            bitmap.getWidth(),\n            bitmap.getHeight(),\n            fixAspectRatio,\n            aspectRatioX,\n            aspectRatioY);\n\n    // crop and rotate the cropped image in one operation\n    Matrix matrix = new Matrix();\n    matrix.setRotate(degreesRotated, bitmap.getWidth() / 2, bitmap.getHeight() / 2);\n    matrix.postScale(flipHorizontally ? -scale : scale, flipVertically ? -scale : scale);\n    Bitmap result =\n        Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height(), matrix, true);\n\n    if (result == bitmap) {\n      // corner case when all bitmap is selected, no worth optimizing for it\n      result = bitmap.copy(bitmap.getConfig(), false);\n    }\n\n    // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping\n    if (degreesRotated % 90 != 0) {\n\n      // extra crop because non rectangular crop cannot be done directly on the image without\n      // rotating first\n      result =\n          cropForRotatedImage(\n              result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY);\n    }\n\n    return result;\n  }\n\n  /**\n   * Crop image bitmap from URI by decoding it with specific width and height to down-sample if\n   * required.<br>\n   * Additionally if OOM is thrown try to increase the sampling (2,4,8).\n   */\n  static BitmapSampled cropBitmap(\n      Context context,\n      Uri loadedImageUri,\n      float[] points,\n      int degreesRotated,\n      int orgWidth,\n      int orgHeight,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      int reqWidth,\n      int reqHeight,\n      boolean flipHorizontally,\n      boolean flipVertically) {\n    int sampleMulti = 1;\n    while (true) {\n      try {\n        // if successful, just return the resulting bitmap\n        return cropBitmap(\n            context,\n            loadedImageUri,\n            points,\n            degreesRotated,\n            orgWidth,\n            orgHeight,\n            fixAspectRatio,\n            aspectRatioX,\n            aspectRatioY,\n            reqWidth,\n            reqHeight,\n            flipHorizontally,\n            flipVertically,\n            sampleMulti);\n      } catch (OutOfMemoryError e) {\n        // if OOM try to increase the sampling to lower the memory usage\n        sampleMulti *= 2;\n        if (sampleMulti > 16) {\n          throw new RuntimeException(\n              \"Failed to handle OOM by sampling (\"\n                  + sampleMulti\n                  + \"): \"\n                  + loadedImageUri\n                  + \"\\r\\n\"\n                  + e.getMessage(),\n              e);\n        }\n      }\n    }\n  }\n\n  /** Get left value of the bounding rectangle of the given points. */\n  static float getRectLeft(float[] points) {\n    return Math.min(Math.min(Math.min(points[0], points[2]), points[4]), points[6]);\n  }\n\n  /** Get top value of the bounding rectangle of the given points. */\n  static float getRectTop(float[] points) {\n    return Math.min(Math.min(Math.min(points[1], points[3]), points[5]), points[7]);\n  }\n\n  /** Get right value of the bounding rectangle of the given points. */\n  static float getRectRight(float[] points) {\n    return Math.max(Math.max(Math.max(points[0], points[2]), points[4]), points[6]);\n  }\n\n  /** Get bottom value of the bounding rectangle of the given points. */\n  static float getRectBottom(float[] points) {\n    return Math.max(Math.max(Math.max(points[1], points[3]), points[5]), points[7]);\n  }\n\n  /** Get width of the bounding rectangle of the given points. */\n  static float getRectWidth(float[] points) {\n    return getRectRight(points) - getRectLeft(points);\n  }\n\n  /** Get height of the bounding rectangle of the given points. */\n  static float getRectHeight(float[] points) {\n    return getRectBottom(points) - getRectTop(points);\n  }\n\n  /** Get horizontal center value of the bounding rectangle of the given points. */\n  static float getRectCenterX(float[] points) {\n    return (getRectRight(points) + getRectLeft(points)) / 2f;\n  }\n\n  /** Get vertical center value of the bounding rectangle of the given points. */\n  static float getRectCenterY(float[] points) {\n    return (getRectBottom(points) + getRectTop(points)) / 2f;\n  }\n\n  /**\n   * Get a rectangle for the given 4 points (x0,y0,x1,y1,x2,y2,x3,y3) by finding the min/max 2\n   * points that contains the given 4 points and is a straight rectangle.\n   */\n  static Rect getRectFromPoints(\n      float[] points,\n      int imageWidth,\n      int imageHeight,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY) {\n    int left = Math.round(Math.max(0, getRectLeft(points)));\n    int top = Math.round(Math.max(0, getRectTop(points)));\n    int right = Math.round(Math.min(imageWidth, getRectRight(points)));\n    int bottom = Math.round(Math.min(imageHeight, getRectBottom(points)));\n\n    Rect rect = new Rect(left, top, right, bottom);\n    if (fixAspectRatio) {\n      fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY);\n    }\n\n    return rect;\n  }\n\n  /**\n   * Fix the given rectangle if it doesn't confirm to aspect ration rule.<br>\n   * Make sure that width and height are equal if 1:1 fixed aspect ratio is requested.\n   */\n  private static void fixRectForAspectRatio(Rect rect, int aspectRatioX, int aspectRatioY) {\n    if (aspectRatioX == aspectRatioY && rect.width() != rect.height()) {\n      if (rect.height() > rect.width()) {\n        rect.bottom -= rect.height() - rect.width();\n      } else {\n        rect.right -= rect.width() - rect.height();\n      }\n    }\n  }\n\n  /**\n   * Write given bitmap to a temp file. If file already exists no-op as we already saved the file in\n   * this session. Uses JPEG 95% compression.\n   *\n   * @param uri the uri to write the bitmap to, if null\n   * @return the uri where the image was saved in, either the given uri or new pointing to temp\n   *     file.\n   */\n  static Uri writeTempStateStoreBitmap(Context context, Bitmap bitmap, Uri uri) {\n    try {\n      boolean needSave = true;\n      if (uri == null) {\n        uri =\n            Uri.fromFile(\n                File.createTempFile(\"aic_state_store_temp\", \".jpg\", context.getCacheDir()));\n      } else if (new File(uri.getPath()).exists()) {\n        needSave = false;\n      }\n      if (needSave) {\n        writeBitmapToUri(context, bitmap, uri, Bitmap.CompressFormat.JPEG, 95);\n      }\n      return uri;\n    } catch (Exception e) {\n      Log.w(\"AIC\", \"Failed to write bitmap to temp file for image-cropper save instance state\", e);\n      return null;\n    }\n  }\n\n  /** Write the given bitmap to the given uri using the given compression. */\n  static void writeBitmapToUri(\n      Context context,\n      Bitmap bitmap,\n      Uri uri,\n      Bitmap.CompressFormat compressFormat,\n      int compressQuality)\n      throws FileNotFoundException {\n    OutputStream outputStream = null;\n    try {\n      outputStream = context.getContentResolver().openOutputStream(uri);\n      bitmap.compress(compressFormat, compressQuality, outputStream);\n    } finally {\n      closeSafe(outputStream);\n    }\n  }\n\n  /** Resize the given bitmap to the given width/height by the given option.<br> */\n  static Bitmap resizeBitmap(\n      Bitmap bitmap, int reqWidth, int reqHeight, CropImageView.RequestSizeOptions options) {\n    try {\n      if (reqWidth > 0\n          && reqHeight > 0\n          && (options == CropImageView.RequestSizeOptions.RESIZE_FIT\n              || options == CropImageView.RequestSizeOptions.RESIZE_INSIDE\n              || options == CropImageView.RequestSizeOptions.RESIZE_EXACT)) {\n\n        Bitmap resized = null;\n        if (options == CropImageView.RequestSizeOptions.RESIZE_EXACT) {\n          resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);\n        } else {\n          int width = bitmap.getWidth();\n          int height = bitmap.getHeight();\n          float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);\n          if (scale > 1 || options == CropImageView.RequestSizeOptions.RESIZE_FIT) {\n            resized =\n                Bitmap.createScaledBitmap(\n                    bitmap, (int) (width / scale), (int) (height / scale), false);\n          }\n        }\n        if (resized != null) {\n          if (resized != bitmap) {\n            bitmap.recycle();\n          }\n          return resized;\n        }\n      }\n    } catch (Exception e) {\n      Log.w(\"AIC\", \"Failed to resize cropped image, return bitmap before resize\", e);\n    }\n    return bitmap;\n  }\n\n  // region: Private methods\n\n  /**\n   * Crop image bitmap from URI by decoding it with specific width and height to down-sample if\n   * required.\n   *\n   * @param orgWidth used to get rectangle from points (handle edge cases to limit rectangle)\n   * @param orgHeight used to get rectangle from points (handle edge cases to limit rectangle)\n   * @param sampleMulti used to increase the sampling of the image to handle memory issues.\n   */\n  private static BitmapSampled cropBitmap(\n      Context context,\n      Uri loadedImageUri,\n      float[] points,\n      int degreesRotated,\n      int orgWidth,\n      int orgHeight,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      int reqWidth,\n      int reqHeight,\n      boolean flipHorizontally,\n      boolean flipVertically,\n      int sampleMulti) {\n\n    // get the rectangle in original image that contains the required cropped area (larger for non\n    // rectangular crop)\n    Rect rect =\n        getRectFromPoints(points, orgWidth, orgHeight, fixAspectRatio, aspectRatioX, aspectRatioY);\n\n    int width = reqWidth > 0 ? reqWidth : rect.width();\n    int height = reqHeight > 0 ? reqHeight : rect.height();\n\n    Bitmap result = null;\n    int sampleSize = 1;\n    try {\n      // decode only the required image from URI, optionally sub-sampling if reqWidth/reqHeight is\n      // given.\n      BitmapSampled bitmapSampled =\n          decodeSampledBitmapRegion(context, loadedImageUri, rect, width, height, sampleMulti);\n      result = bitmapSampled.bitmap;\n      sampleSize = bitmapSampled.sampleSize;\n    } catch (Exception ignored) {\n    }\n\n    if (result != null) {\n      try {\n        // rotate the decoded region by the required amount\n        result = rotateAndFlipBitmapInt(result, degreesRotated, flipHorizontally, flipVertically);\n\n        // rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping\n        if (degreesRotated % 90 != 0) {\n\n          // extra crop because non rectangular crop cannot be done directly on the image without\n          // rotating first\n          result =\n              cropForRotatedImage(\n                  result, points, rect, degreesRotated, fixAspectRatio, aspectRatioX, aspectRatioY);\n        }\n      } catch (OutOfMemoryError e) {\n        if (result != null) {\n          result.recycle();\n        }\n        throw e;\n      }\n      return new BitmapSampled(result, sampleSize);\n    } else {\n      // failed to decode region, may be skia issue, try full decode and then crop\n      return cropBitmap(\n          context,\n          loadedImageUri,\n          points,\n          degreesRotated,\n          fixAspectRatio,\n          aspectRatioX,\n          aspectRatioY,\n          sampleMulti,\n          rect,\n          width,\n          height,\n          flipHorizontally,\n          flipVertically);\n    }\n  }\n\n  /**\n   * Crop bitmap by fully loading the original and then cropping it, fallback in case cropping\n   * region failed.\n   */\n  private static BitmapSampled cropBitmap(\n      Context context,\n      Uri loadedImageUri,\n      float[] points,\n      int degreesRotated,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY,\n      int sampleMulti,\n      Rect rect,\n      int width,\n      int height,\n      boolean flipHorizontally,\n      boolean flipVertically) {\n    Bitmap result = null;\n    int sampleSize;\n    try {\n      BitmapFactory.Options options = new BitmapFactory.Options();\n      options.inSampleSize =\n          sampleSize =\n              sampleMulti\n                  * calculateInSampleSizeByReqestedSize(rect.width(), rect.height(), width, height);\n\n      Bitmap fullBitmap = decodeImage(context.getContentResolver(), loadedImageUri, options);\n      if (fullBitmap != null) {\n        try {\n          // adjust crop points by the sampling because the image is smaller\n          float[] points2 = new float[points.length];\n          System.arraycopy(points, 0, points2, 0, points.length);\n          for (int i = 0; i < points2.length; i++) {\n            points2[i] = points2[i] / options.inSampleSize;\n          }\n\n          result =\n              cropBitmapObjectWithScale(\n                  fullBitmap,\n                  points2,\n                  degreesRotated,\n                  fixAspectRatio,\n                  aspectRatioX,\n                  aspectRatioY,\n                  1,\n                  flipHorizontally,\n                  flipVertically);\n        } finally {\n          if (result != fullBitmap) {\n            fullBitmap.recycle();\n          }\n        }\n      }\n    } catch (OutOfMemoryError e) {\n      if (result != null) {\n        result.recycle();\n      }\n      throw e;\n    } catch (Exception e) {\n      throw new RuntimeException(\n          \"Failed to load sampled bitmap: \" + loadedImageUri + \"\\r\\n\" + e.getMessage(), e);\n    }\n    return new BitmapSampled(result, sampleSize);\n  }\n\n  /** Decode image from uri using \"inJustDecodeBounds\" to get the image dimensions. */\n  private static BitmapFactory.Options decodeImageForOption(ContentResolver resolver, Uri uri)\n      throws FileNotFoundException {\n    InputStream stream = null;\n    try {\n      stream = resolver.openInputStream(uri);\n      BitmapFactory.Options options = new BitmapFactory.Options();\n      options.inJustDecodeBounds = true;\n      BitmapFactory.decodeStream(stream, EMPTY_RECT, options);\n      options.inJustDecodeBounds = false;\n      return options;\n    } finally {\n      closeSafe(stream);\n    }\n  }\n\n  /**\n   * Decode image from uri using given \"inSampleSize\", but if failed due to out-of-memory then raise\n   * the inSampleSize until success.\n   */\n  private static Bitmap decodeImage(\n      ContentResolver resolver, Uri uri, BitmapFactory.Options options)\n      throws FileNotFoundException {\n    do {\n      InputStream stream = null;\n      try {\n        stream = resolver.openInputStream(uri);\n        return BitmapFactory.decodeStream(stream, EMPTY_RECT, options);\n      } catch (OutOfMemoryError e) {\n        options.inSampleSize *= 2;\n      } finally {\n        closeSafe(stream);\n      }\n    } while (options.inSampleSize <= 512);\n    throw new RuntimeException(\"Failed to decode image: \" + uri);\n  }\n\n  /**\n   * Decode specific rectangle bitmap from stream using sampling to get bitmap with the requested\n   * limit.\n   *\n   * @param sampleMulti used to increase the sampling of the image to handle memory issues.\n   */\n  private static BitmapSampled decodeSampledBitmapRegion(\n      Context context, Uri uri, Rect rect, int reqWidth, int reqHeight, int sampleMulti) {\n    InputStream stream = null;\n    BitmapRegionDecoder decoder = null;\n    try {\n      BitmapFactory.Options options = new BitmapFactory.Options();\n      options.inSampleSize =\n          sampleMulti\n              * calculateInSampleSizeByReqestedSize(\n                  rect.width(), rect.height(), reqWidth, reqHeight);\n\n      stream = context.getContentResolver().openInputStream(uri);\n      decoder = BitmapRegionDecoder.newInstance(stream, false);\n      do {\n        try {\n          return new BitmapSampled(decoder.decodeRegion(rect, options), options.inSampleSize);\n        } catch (OutOfMemoryError e) {\n          options.inSampleSize *= 2;\n        }\n      } while (options.inSampleSize <= 512);\n    } catch (Exception e) {\n      throw new RuntimeException(\n          \"Failed to load sampled bitmap: \" + uri + \"\\r\\n\" + e.getMessage(), e);\n    } finally {\n      closeSafe(stream);\n      if (decoder != null) {\n        decoder.recycle();\n      }\n    }\n    return new BitmapSampled(null, 1);\n  }\n\n  /**\n   * Special crop of bitmap rotated by not stright angle, in this case the original crop bitmap\n   * contains parts beyond the required crop area, this method crops the already cropped and rotated\n   * bitmap to the final rectangle.<br>\n   * Note: rotating by 0, 90, 180 or 270 degrees doesn't require extra cropping.\n   */\n  private static Bitmap cropForRotatedImage(\n      Bitmap bitmap,\n      float[] points,\n      Rect rect,\n      int degreesRotated,\n      boolean fixAspectRatio,\n      int aspectRatioX,\n      int aspectRatioY) {\n    if (degreesRotated % 90 != 0) {\n\n      int adjLeft = 0, adjTop = 0, width = 0, height = 0;\n      double rads = Math.toRadians(degreesRotated);\n      int compareTo =\n          degreesRotated < 90 || (degreesRotated > 180 && degreesRotated < 270)\n              ? rect.left\n              : rect.right;\n      for (int i = 0; i < points.length; i += 2) {\n        if (points[i] >= compareTo - 1 && points[i] <= compareTo + 1) {\n          adjLeft = (int) Math.abs(Math.sin(rads) * (rect.bottom - points[i + 1]));\n          adjTop = (int) Math.abs(Math.cos(rads) * (points[i + 1] - rect.top));\n          width = (int) Math.abs((points[i + 1] - rect.top) / Math.sin(rads));\n          height = (int) Math.abs((rect.bottom - points[i + 1]) / Math.cos(rads));\n          break;\n        }\n      }\n\n      rect.set(adjLeft, adjTop, adjLeft + width, adjTop + height);\n      if (fixAspectRatio) {\n        fixRectForAspectRatio(rect, aspectRatioX, aspectRatioY);\n      }\n\n      Bitmap bitmapTmp = bitmap;\n      bitmap = Bitmap.createBitmap(bitmap, rect.left, rect.top, rect.width(), rect.height());\n      if (bitmapTmp != bitmap) {\n        bitmapTmp.recycle();\n      }\n    }\n    return bitmap;\n  }\n\n  /**\n   * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width\n   * larger than the requested height and width.\n   */\n  private static int calculateInSampleSizeByReqestedSize(\n      int width, int height, int reqWidth, int reqHeight) {\n    int inSampleSize = 1;\n    if (height > reqHeight || width > reqWidth) {\n      while ((height / 2 / inSampleSize) > reqHeight && (width / 2 / inSampleSize) > reqWidth) {\n        inSampleSize *= 2;\n      }\n    }\n    return inSampleSize;\n  }\n\n  /**\n   * Calculate the largest inSampleSize value that is a power of 2 and keeps both height and width\n   * smaller than max texture size allowed for the device.\n   */\n  private static int calculateInSampleSizeByMaxTextureSize(int width, int height) {\n    int inSampleSize = 1;\n    if (mMaxTextureSize == 0) {\n      mMaxTextureSize = getMaxTextureSize();\n    }\n    if (mMaxTextureSize > 0) {\n      while ((height / inSampleSize) > mMaxTextureSize\n          || (width / inSampleSize) > mMaxTextureSize) {\n        inSampleSize *= 2;\n      }\n    }\n    return inSampleSize;\n  }\n\n  /**\n   * Rotate the given bitmap by the given degrees.<br>\n   * New bitmap is created and the old one is recycled.\n   */\n  private static Bitmap rotateAndFlipBitmapInt(\n      Bitmap bitmap, int degrees, boolean flipHorizontally, boolean flipVertically) {\n    if (degrees > 0 || flipHorizontally || flipVertically) {\n      Matrix matrix = new Matrix();\n      matrix.setRotate(degrees);\n      matrix.postScale(flipHorizontally ? -1 : 1, flipVertically ? -1 : 1);\n      Bitmap newBitmap =\n          Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);\n      if (newBitmap != bitmap) {\n        bitmap.recycle();\n      }\n      return newBitmap;\n    } else {\n      return bitmap;\n    }\n  }\n\n  /**\n   * Get the max size of bitmap allowed to be rendered on the device.<br>\n   * http://stackoverflow.com/questions/7428996/hw-accelerated-activity-how-to-get-opengl-texture-size-limit.\n   */\n  private static int getMaxTextureSize() {\n    // Safe minimum default size\n    final int IMAGE_MAX_BITMAP_DIMENSION = 2048;\n\n    try {\n      // Get EGL Display\n      EGL10 egl = (EGL10) EGLContext.getEGL();\n      EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);\n\n      // Initialise\n      int[] version = new int[2];\n      egl.eglInitialize(display, version);\n\n      // Query total number of configurations\n      int[] totalConfigurations = new int[1];\n      egl.eglGetConfigs(display, null, 0, totalConfigurations);\n\n      // Query actual list configurations\n      EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]];\n      egl.eglGetConfigs(display, configurationsList, totalConfigurations[0], totalConfigurations);\n\n      int[] textureSize = new int[1];\n      int maximumTextureSize = 0;\n\n      // Iterate through all the configurations to located the maximum texture size\n      for (int i = 0; i < totalConfigurations[0]; i++) {\n        // Only need to check for width since opengl textures are always squared\n        egl.eglGetConfigAttrib(\n            display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize);\n\n        // Keep track of the maximum texture size\n        if (maximumTextureSize < textureSize[0]) {\n          maximumTextureSize = textureSize[0];\n        }\n      }\n\n      // Release\n      egl.eglTerminate(display);\n\n      // Return largest texture size found, or default\n      return Math.max(maximumTextureSize, IMAGE_MAX_BITMAP_DIMENSION);\n    } catch (Exception e) {\n      return IMAGE_MAX_BITMAP_DIMENSION;\n    }\n  }\n\n  /**\n   * Close the given closeable object (Stream) in a safe way: check if it is null and catch-log\n   * exception thrown.\n   *\n   * @param closeable the closable object to close\n   */\n  private static void closeSafe(Closeable closeable) {\n    if (closeable != null) {\n      try {\n        closeable.close();\n      } catch (IOException ignored) {\n      }\n    }\n  }\n  // endregion\n\n  // region: Inner class: BitmapSampled\n\n  /** Holds bitmap instance and the sample size that the bitmap was loaded/cropped with. */\n  static final class BitmapSampled {\n\n    /** The bitmap instance */\n    public final Bitmap bitmap;\n\n    /** The sample size used to lower the size of the bitmap (1,2,4,8,...) */\n    final int sampleSize;\n\n    BitmapSampled(Bitmap bitmap, int sampleSize) {\n      this.bitmap = bitmap;\n      this.sampleSize = sampleSize;\n    }\n  }\n  // endregion\n\n  // region: Inner class: RotateBitmapResult\n\n  /** The result of {@link #rotateBitmapByExif(android.graphics.Bitmap, ExifInterface)}. */\n  static final class RotateBitmapResult {\n\n    /** The loaded bitmap */\n    public final Bitmap bitmap;\n\n    /** The degrees the image was rotated */\n    final int degrees;\n\n    RotateBitmapResult(Bitmap bitmap, int degrees) {\n      this.bitmap = bitmap;\n      this.degrees = degrees;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImage.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.Manifest;\nimport android.app.Activity;\nimport android.content.ComponentName;\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ResolveInfo;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.PorterDuff;\nimport android.graphics.PorterDuffXfermode;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.net.Uri;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.provider.MediaStore;\n\nimport java.io.File;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport androidx.annotation.DrawableRes;\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.annotation.RequiresApi;\nimport androidx.fragment.app.Fragment;\n\n/**\n * Helper to simplify crop image work like starting pick-image acitvity and handling camera/gallery\n * intents.<br>\n * The goal of the helper is to simplify the starting and most-common usage of image cropping and\n * not all porpose all possible scenario one-to-rule-them-all code base. So feel free to use it as\n * is and as a wiki to make your own.<br>\n * Added value you get out-of-the-box is some edge case handling that you may miss otherwise, like\n * the stupid-ass Android camera result URI that may differ from version to version and from device\n * to device.\n */\n@SuppressWarnings(\"WeakerAccess, unused\")\npublic final class CropImage {\n\n  // region: Fields and Consts\n\n  /** The key used to pass crop image source URI to {@link CropImageActivity}. */\n  public static final String CROP_IMAGE_EXTRA_SOURCE = \"CROP_IMAGE_EXTRA_SOURCE\";\n\n  /** The key used to pass crop image options to {@link CropImageActivity}. */\n  public static final String CROP_IMAGE_EXTRA_OPTIONS = \"CROP_IMAGE_EXTRA_OPTIONS\";\n\n  /** The key used to pass crop image bundle data to {@link CropImageActivity}. */\n  public static final String CROP_IMAGE_EXTRA_BUNDLE = \"CROP_IMAGE_EXTRA_BUNDLE\";\n\n  /** The key used to pass crop image result data back from {@link CropImageActivity}. */\n  public static final String CROP_IMAGE_EXTRA_RESULT = \"CROP_IMAGE_EXTRA_RESULT\";\n\n  /**\n   * The request code used to start pick image activity to be used on result to identify the this\n   * specific request.\n   */\n  public static final int PICK_IMAGE_CHOOSER_REQUEST_CODE = 200;\n\n  /** The request code used to request permission to pick image from external storage. */\n  public static final int PICK_IMAGE_PERMISSIONS_REQUEST_CODE = 201;\n\n  /** The request code used to request permission to capture image from camera. */\n  public static final int CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE = 2011;\n\n  /**\n   * The request code used to start {@link CropImageActivity} to be used on result to identify the\n   * this specific request.\n   */\n  public static final int CROP_IMAGE_ACTIVITY_REQUEST_CODE = 203;\n\n  /** The result code used to return error from {@link CropImageActivity}. */\n  public static final int CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE = 204;\n  // endregion\n\n  private CropImage() {}\n\n  /**\n   * Create a new bitmap that has all pixels beyond the oval shape transparent. Old bitmap is\n   * recycled.\n   */\n  public static Bitmap toOvalBitmap(@NonNull Bitmap bitmap) {\n    int width = bitmap.getWidth();\n    int height = bitmap.getHeight();\n    Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);\n\n    Canvas canvas = new Canvas(output);\n\n    int color = 0xff424242;\n    Paint paint = new Paint();\n\n    paint.setAntiAlias(true);\n    canvas.drawARGB(0, 0, 0, 0);\n    paint.setColor(color);\n\n    RectF rect = new RectF(0, 0, width, height);\n    canvas.drawOval(rect, paint);\n    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));\n    canvas.drawBitmap(bitmap, 0, 0, paint);\n\n    bitmap.recycle();\n\n    return output;\n  }\n\n  /**\n   * Start an activity to get image for cropping using chooser intent that will have all the\n   * available applications for the device like camera (MyCamera), galery (Photos), store apps\n   * (Dropbox), etc.<br>\n   * Use \"pick_image_intent_chooser_title\" string resource to override pick chooser title.\n   *\n   * @param activity the activity to be used to start activity from\n   */\n  public static void startPickImageActivity(@NonNull Activity activity) {\n    activity.startActivityForResult(\n        getPickImageChooserIntent(activity), PICK_IMAGE_CHOOSER_REQUEST_CODE);\n  }\n\n  /**\n   * Same as {@link #startPickImageActivity(Activity) startPickImageActivity} method but instead of\n   * being called and returning to an Activity, this method can be called and return to a Fragment.\n   *\n   * @param context The Fragments context. Use getContext()\n   * @param fragment The calling Fragment to start and return the image to\n   */\n  public static void startPickImageActivity(@NonNull Context context, @NonNull Fragment fragment) {\n    fragment.startActivityForResult(\n        getPickImageChooserIntent(context), PICK_IMAGE_CHOOSER_REQUEST_CODE);\n  }\n\n  /**\n   * Create a chooser intent to select the source to get image from.<br>\n   * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br>\n   * All possible sources are added to the intent chooser.<br>\n   * Use \"pick_image_intent_chooser_title\" string resource to override chooser title.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   */\n  public static Intent getPickImageChooserIntent(@NonNull Context context) {\n    return getPickImageChooserIntent(\n        context, context.getString(R.string.pick_image_intent_chooser_title), false, true);\n  }\n\n  /**\n   * Create a chooser intent to select the source to get image from.<br>\n   * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br>\n   * All possible sources are added to the intent chooser.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   * @param title the title to use for the chooser UI\n   * @param includeDocuments if to include KitKat documents activity containing all sources\n   * @param includeCamera if to include camera intents\n   */\n  public static Intent getPickImageChooserIntent(\n      @NonNull Context context,\n      CharSequence title,\n      boolean includeDocuments,\n      boolean includeCamera) {\n\n    List<Intent> allIntents = new ArrayList<>();\n    PackageManager packageManager = context.getPackageManager();\n\n    // collect all camera intents if Camera permission is available\n    if (!isExplicitCameraPermissionRequired(context) && includeCamera) {\n      allIntents.addAll(getCameraIntents(context, packageManager));\n    }\n\n    List<Intent> galleryIntents =\n        getGalleryIntents(packageManager, Intent.ACTION_GET_CONTENT, includeDocuments);\n    if (galleryIntents.size() == 0) {\n      // if no intents found for get-content try pick intent action (Huawei P9).\n      galleryIntents = getGalleryIntents(packageManager, Intent.ACTION_PICK, includeDocuments);\n    }\n    allIntents.addAll(galleryIntents);\n\n    Intent target;\n    if (allIntents.isEmpty()) {\n      target = new Intent();\n    } else {\n      target = allIntents.get(allIntents.size() - 1);\n      allIntents.remove(allIntents.size() - 1);\n    }\n\n    // Create a chooser from the main  intent\n    Intent chooserIntent = Intent.createChooser(target, title);\n\n    // Add all other intents\n    chooserIntent.putExtra(\n        Intent.EXTRA_INITIAL_INTENTS, allIntents.toArray(new Parcelable[allIntents.size()]));\n\n    return chooserIntent;\n  }\n\n  /**\n   * Get the main Camera intent for capturing image using device camera app. If the outputFileUri is\n   * null, a default Uri will be created with {@link #getCaptureImageOutputUri(Context)}, so then\n   * you will be able to get the pictureUri using {@link #getPickImageResultUri(Context, Intent)}.\n   * Otherwise, it is just you use the Uri passed to this method.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   * @param outputFileUri the Uri where the picture will be placed.\n   */\n  public static Intent getCameraIntent(@NonNull Context context, Uri outputFileUri) {\n    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n    if (outputFileUri == null) {\n      outputFileUri = getCaptureImageOutputUri(context);\n    }\n    intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);\n    return intent;\n  }\n\n  /** Get all Camera intents for capturing image using device camera apps. */\n  public static List<Intent> getCameraIntents(\n      @NonNull Context context, @NonNull PackageManager packageManager) {\n\n    List<Intent> allIntents = new ArrayList<>();\n\n    // Determine Uri of camera image to  save.\n    Uri outputFileUri = getCaptureImageOutputUri(context);\n\n    Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);\n    List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);\n    for (ResolveInfo res : listCam) {\n      Intent intent = new Intent(captureIntent);\n      intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));\n      intent.setPackage(res.activityInfo.packageName);\n      if (outputFileUri != null) {\n        intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);\n      }\n      allIntents.add(intent);\n    }\n\n    return allIntents;\n  }\n\n  /**\n   * Get all Gallery intents for getting image from one of the apps of the device that handle\n   * images.\n   */\n  public static List<Intent> getGalleryIntents(\n      @NonNull PackageManager packageManager, String action, boolean includeDocuments) {\n    List<Intent> intents = new ArrayList<>();\n    Intent galleryIntent =\n        action == Intent.ACTION_GET_CONTENT\n            ? new Intent(action)\n            : new Intent(action, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);\n    galleryIntent.setType(\"image/*\");\n    List<ResolveInfo> listGallery = packageManager.queryIntentActivities(galleryIntent, 0);\n    for (ResolveInfo res : listGallery) {\n      Intent intent = new Intent(galleryIntent);\n      intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));\n      intent.setPackage(res.activityInfo.packageName);\n      intents.add(intent);\n    }\n\n    // remove documents intent\n    if (!includeDocuments) {\n      for (Intent intent : intents) {\n        if (intent\n            .getComponent()\n            .getClassName()\n            .equals(\"com.android.documentsui.DocumentsActivity\")) {\n          intents.remove(intent);\n          break;\n        }\n      }\n    }\n    return intents;\n  }\n\n  /**\n   * Check if explicetly requesting camera permission is required.<br>\n   * It is required in Android Marshmellow and above if \"CAMERA\" permission is requested in the\n   * manifest.<br>\n   * See <a\n   * href=\"http://stackoverflow.com/questions/32789027/android-m-camera-intent-permission-bug\">StackOverflow\n   * question</a>.\n   */\n  public static boolean isExplicitCameraPermissionRequired(@NonNull Context context) {\n    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M\n        && hasPermissionInManifest(context, \"android.permission.CAMERA\")\n        && context.checkSelfPermission(Manifest.permission.CAMERA)\n            != PackageManager.PERMISSION_GRANTED;\n  }\n\n  /**\n   * Check if the app requests a specific permission in the manifest.\n   *\n   * @param permissionName the permission to check\n   * @return true - the permission in requested in manifest, false - not.\n   */\n  public static boolean hasPermissionInManifest(\n      @NonNull Context context, @NonNull String permissionName) {\n    String packageName = context.getPackageName();\n    try {\n      PackageInfo packageInfo =\n          context.getPackageManager().getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);\n      final String[] declaredPermisisons = packageInfo.requestedPermissions;\n      if (declaredPermisisons != null && declaredPermisisons.length > 0) {\n        for (String p : declaredPermisisons) {\n          if (p.equalsIgnoreCase(permissionName)) {\n            return true;\n          }\n        }\n      }\n    } catch (PackageManager.NameNotFoundException e) {\n    }\n    return false;\n  }\n\n  /**\n   * Get URI to image received from capture by camera.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   */\n  public static Uri getCaptureImageOutputUri(@NonNull Context context) {\n    Uri outputFileUri = null;\n    File getImage = context.getExternalCacheDir();\n    if (getImage != null) {\n      outputFileUri = Uri.fromFile(new File(getImage.getPath(), \"pickImageResult.jpeg\"));\n    }\n    return outputFileUri;\n  }\n\n  /**\n   * Get the URI of the selected image from {@link #getPickImageChooserIntent(Context)}.<br>\n   * Will return the correct URI for camera and gallery image.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   * @param data the returned data of the activity result\n   */\n  public static Uri getPickImageResultUri(@NonNull Context context, @Nullable Intent data) {\n    boolean isCamera = true;\n    if (data != null && data.getData() != null) {\n      String action = data.getAction();\n      isCamera = action != null && action.equals(MediaStore.ACTION_IMAGE_CAPTURE);\n    }\n    return isCamera || data.getData() == null ? getCaptureImageOutputUri(context) : data.getData();\n  }\n\n  /**\n   * Check if the given picked image URI requires READ_EXTERNAL_STORAGE permissions.<br>\n   * Only relevant for API version 23 and above and not required for all URI's depends on the\n   * implementation of the app that was used for picking the image. So we just test if we can open\n   * the stream or do we get an exception when we try, Android is awesome.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   * @param uri the result URI of image pick.\n   * @return true - required permission are not granted, false - either no need for permissions or\n   *     they are granted\n   */\n  public static boolean isReadExternalStoragePermissionsRequired(\n      @NonNull Context context, @NonNull Uri uri) {\n    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M\n        && context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)\n            != PackageManager.PERMISSION_GRANTED\n        && isUriRequiresPermissions(context, uri);\n  }\n\n  /**\n   * Test if we can open the given Android URI to test if permission required error is thrown.<br>\n   * Only relevant for API version 23 and above.\n   *\n   * @param context used to access Android APIs, like content resolve, it is your\n   *     activity/fragment/widget.\n   * @param uri the result URI of image pick.\n   */\n  public static boolean isUriRequiresPermissions(@NonNull Context context, @NonNull Uri uri) {\n    try {\n      ContentResolver resolver = context.getContentResolver();\n      InputStream stream = resolver.openInputStream(uri);\n      if (stream != null) {\n        stream.close();\n      }\n      return false;\n    } catch (Exception e) {\n      return true;\n    }\n  }\n\n  /**\n   * Create {@link ActivityBuilder} instance to open image picker for cropping and then start {@link\n   * CropImageActivity} to crop the selected image.<br>\n   * Result will be received in {@link Activity#onActivityResult(int, int, Intent)} and can be\n   * retrieved using {@link #getActivityResult(Intent)}.\n   *\n   * @return builder for Crop Image Activity\n   */\n  public static ActivityBuilder activity() {\n    return new ActivityBuilder(null);\n  }\n\n  /**\n   * Create {@link ActivityBuilder} instance to start {@link CropImageActivity} to crop the given\n   * image.<br>\n   * Result will be received in {@link Activity#onActivityResult(int, int, Intent)} and can be\n   * retrieved using {@link #getActivityResult(Intent)}.\n   *\n   * @param uri the image Android uri source to crop or null to start a picker\n   * @return builder for Crop Image Activity\n   */\n  public static ActivityBuilder activity(@Nullable Uri uri) {\n    return new ActivityBuilder(uri);\n  }\n\n  /**\n   * Get {@link CropImageActivity} result data object for crop image activity started using {@link\n   * #activity(Uri)}.\n   *\n   * @param data result data intent as received in {@link Activity#onActivityResult(int, int,\n   *     Intent)}.\n   * @return Crop Image Activity Result object or null if none exists\n   */\n  public static ActivityResult getActivityResult(@Nullable Intent data) {\n    return data != null ? (ActivityResult) data.getParcelableExtra(CROP_IMAGE_EXTRA_RESULT) : null;\n  }\n\n  // region: Inner class: ActivityBuilder\n\n  /** Builder used for creating Image Crop Activity by user request. */\n  public static final class ActivityBuilder {\n\n    /** The image to crop source Android uri. */\n    @Nullable private final Uri mSource;\n\n    /** Options for image crop UX */\n    private final CropImageOptions mOptions;\n\n    private ActivityBuilder(@Nullable Uri source) {\n      mSource = source;\n      mOptions = new CropImageOptions();\n    }\n\n    /** Get {@link CropImageActivity} intent to start the activity. */\n    public Intent getIntent(@NonNull Context context) {\n      return getIntent(context, CropImageActivity.class);\n    }\n\n    /** Get {@link CropImageActivity} intent to start the activity. */\n    public Intent getIntent(@NonNull Context context, @Nullable Class<?> cls) {\n      mOptions.validate();\n\n      Intent intent = new Intent();\n      intent.setClass(context, cls);\n      Bundle bundle = new Bundle();\n      bundle.putParcelable(CROP_IMAGE_EXTRA_SOURCE, mSource);\n      bundle.putParcelable(CROP_IMAGE_EXTRA_OPTIONS, mOptions);\n      intent.putExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE, bundle);\n      return intent;\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param activity activity to receive result\n     */\n    public void start(@NonNull Activity activity) {\n      mOptions.validate();\n      activity.startActivityForResult(getIntent(activity), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param activity activity to receive result\n     */\n    public void start(@NonNull Activity activity, @Nullable Class<?> cls) {\n      mOptions.validate();\n      activity.startActivityForResult(getIntent(activity, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param fragment fragment to receive result\n     */\n    public void start(@NonNull Context context, @NonNull Fragment fragment) {\n      fragment.startActivityForResult(getIntent(context), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param fragment fragment to receive result\n     */\n    @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)\n    public void start(@NonNull Context context, @NonNull android.app.Fragment fragment) {\n      fragment.startActivityForResult(getIntent(context), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param fragment fragment to receive result\n     */\n    public void start(\n        @NonNull Context context, @NonNull Fragment fragment, @Nullable Class<?> cls) {\n      fragment.startActivityForResult(getIntent(context, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * Start {@link CropImageActivity}.\n     *\n     * @param fragment fragment to receive result\n     */\n    @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)\n    public void start(\n        @NonNull Context context, @NonNull android.app.Fragment fragment, @Nullable Class<?> cls) {\n      fragment.startActivityForResult(getIntent(context, cls), CROP_IMAGE_ACTIVITY_REQUEST_CODE);\n    }\n\n    /**\n     * The shape of the cropping window.<br>\n     * To set square/circle crop shape set aspect ratio to 1:1.<br>\n     * <i>Default: RECTANGLE</i>\n     */\n    public ActivityBuilder setCropShape(@NonNull CropImageView.CropShape cropShape) {\n      mOptions.cropShape = cropShape;\n      return this;\n    }\n\n    /**\n     * An edge of the crop window will snap to the corresponding edge of a specified bounding box\n     * when the crop window edge is less than or equal to this distance (in pixels) away from the\n     * bounding box edge (in pixels).<br>\n     * <i>Default: 3dp</i>\n     */\n    public ActivityBuilder setSnapRadius(float snapRadius) {\n      mOptions.snapRadius = snapRadius;\n      return this;\n    }\n\n    /**\n     * The radius of the touchable area around the handle (in pixels).<br>\n     * We are basing this value off of the recommended 48dp Rhythm.<br>\n     * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm<br>\n     * <i>Default: 48dp</i>\n     */\n    public ActivityBuilder setTouchRadius(float touchRadius) {\n      mOptions.touchRadius = touchRadius;\n      return this;\n    }\n\n    /**\n     * whether the guidelines should be on, off, or only showing when resizing.<br>\n     * <i>Default: ON_TOUCH</i>\n     */\n    public ActivityBuilder setGuidelines(@NonNull CropImageView.Guidelines guidelines) {\n      mOptions.guidelines = guidelines;\n      return this;\n    }\n\n    /**\n     * The initial scale type of the image in the crop image view<br>\n     * <i>Default: FIT_CENTER</i>\n     */\n    public ActivityBuilder setScaleType(@NonNull CropImageView.ScaleType scaleType) {\n      mOptions.scaleType = scaleType;\n      return this;\n    }\n\n    /**\n     * if to show crop overlay UI what contains the crop window UI surrounded by background over the\n     * cropping image.<br>\n     * <i>default: true, may disable for animation or frame transition.</i>\n     */\n    public ActivityBuilder setShowCropOverlay(boolean showCropOverlay) {\n      mOptions.showCropOverlay = showCropOverlay;\n      return this;\n    }\n\n    /**\n     * if auto-zoom functionality is enabled.<br>\n     * default: true.\n     */\n    public ActivityBuilder setAutoZoomEnabled(boolean autoZoomEnabled) {\n      mOptions.autoZoomEnabled = autoZoomEnabled;\n      return this;\n    }\n\n    /**\n     * if multi touch functionality is enabled.<br>\n     * default: true.\n     */\n    public ActivityBuilder setMultiTouchEnabled(boolean multiTouchEnabled) {\n      mOptions.multiTouchEnabled = multiTouchEnabled;\n      return this;\n    }\n\n    /**\n     * The max zoom allowed during cropping.<br>\n     * <i>Default: 4</i>\n     */\n    public ActivityBuilder setMaxZoom(int maxZoom) {\n      mOptions.maxZoom = maxZoom;\n      return this;\n    }\n\n    /**\n     * The initial crop window padding from image borders in percentage of the cropping image\n     * dimensions.<br>\n     * <i>Default: 0.1</i>\n     */\n    public ActivityBuilder setInitialCropWindowPaddingRatio(float initialCropWindowPaddingRatio) {\n      mOptions.initialCropWindowPaddingRatio = initialCropWindowPaddingRatio;\n      return this;\n    }\n\n    /**\n     * whether the width to height aspect ratio should be maintained or free to change.<br>\n     * <i>Default: false</i>\n     */\n    public ActivityBuilder setFixAspectRatio(boolean fixAspectRatio) {\n      mOptions.fixAspectRatio = fixAspectRatio;\n      return this;\n    }\n\n    /**\n     * the X,Y value of the aspect ratio.<br>\n     * Also sets fixes aspect ratio to TRUE.<br>\n     * <i>Default: 1/1</i>\n     *\n     * @param aspectRatioX the width\n     * @param aspectRatioY the height\n     */\n    public ActivityBuilder setAspectRatio(int aspectRatioX, int aspectRatioY) {\n      mOptions.aspectRatioX = aspectRatioX;\n      mOptions.aspectRatioY = aspectRatioY;\n      mOptions.fixAspectRatio = true;\n      return this;\n    }\n\n    /**\n     * the thickness of the guidelines lines (in pixels).<br>\n     * <i>Default: 3dp</i>\n     */\n    public ActivityBuilder setBorderLineThickness(float borderLineThickness) {\n      mOptions.borderLineThickness = borderLineThickness;\n      return this;\n    }\n\n    /**\n     * the color of the guidelines lines.<br>\n     * <i>Default: Color.argb(170, 255, 255, 255)</i>\n     */\n    public ActivityBuilder setBorderLineColor(int borderLineColor) {\n      mOptions.borderLineColor = borderLineColor;\n      return this;\n    }\n\n    /**\n     * thickness of the corner line (in pixels).<br>\n     * <i>Default: 2dp</i>\n     */\n    public ActivityBuilder setBorderCornerThickness(float borderCornerThickness) {\n      mOptions.borderCornerThickness = borderCornerThickness;\n      return this;\n    }\n\n    /**\n     * the offset of corner line from crop window border (in pixels).<br>\n     * <i>Default: 5dp</i>\n     */\n    public ActivityBuilder setBorderCornerOffset(float borderCornerOffset) {\n      mOptions.borderCornerOffset = borderCornerOffset;\n      return this;\n    }\n\n    /**\n     * the length of the corner line away from the corner (in pixels).<br>\n     * <i>Default: 14dp</i>\n     */\n    public ActivityBuilder setBorderCornerLength(float borderCornerLength) {\n      mOptions.borderCornerLength = borderCornerLength;\n      return this;\n    }\n\n    /**\n     * the color of the corner line.<br>\n     * <i>Default: WHITE</i>\n     */\n    public ActivityBuilder setBorderCornerColor(int borderCornerColor) {\n      mOptions.borderCornerColor = borderCornerColor;\n      return this;\n    }\n\n    /**\n     * the thickness of the guidelines lines (in pixels).<br>\n     * <i>Default: 1dp</i>\n     */\n    public ActivityBuilder setGuidelinesThickness(float guidelinesThickness) {\n      mOptions.guidelinesThickness = guidelinesThickness;\n      return this;\n    }\n\n    /**\n     * the color of the guidelines lines.<br>\n     * <i>Default: Color.argb(170, 255, 255, 255)</i>\n     */\n    public ActivityBuilder setGuidelinesColor(int guidelinesColor) {\n      mOptions.guidelinesColor = guidelinesColor;\n      return this;\n    }\n\n    /**\n     * the color of the overlay background around the crop window cover the image parts not in the\n     * crop window.<br>\n     * <i>Default: Color.argb(119, 0, 0, 0)</i>\n     */\n    public ActivityBuilder setBackgroundColor(int backgroundColor) {\n      mOptions.backgroundColor = backgroundColor;\n      return this;\n    }\n\n    /**\n     * the min size the crop window is allowed to be (in pixels).<br>\n     * <i>Default: 42dp, 42dp</i>\n     */\n    public ActivityBuilder setMinCropWindowSize(int minCropWindowWidth, int minCropWindowHeight) {\n      mOptions.minCropWindowWidth = minCropWindowWidth;\n      mOptions.minCropWindowHeight = minCropWindowHeight;\n      return this;\n    }\n\n    /**\n     * the min size the resulting cropping image is allowed to be, affects the cropping window\n     * limits (in pixels).<br>\n     * <i>Default: 40px, 40px</i>\n     */\n    public ActivityBuilder setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) {\n      mOptions.minCropResultWidth = minCropResultWidth;\n      mOptions.minCropResultHeight = minCropResultHeight;\n      return this;\n    }\n\n    /**\n     * the max size the resulting cropping image is allowed to be, affects the cropping window\n     * limits (in pixels).<br>\n     * <i>Default: 99999, 99999</i>\n     */\n    public ActivityBuilder setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {\n      mOptions.maxCropResultWidth = maxCropResultWidth;\n      mOptions.maxCropResultHeight = maxCropResultHeight;\n      return this;\n    }\n\n    /**\n     * the title of the {@link CropImageActivity}.<br>\n     * <i>Default: \"\"</i>\n     */\n    public ActivityBuilder setActivityTitle(CharSequence activityTitle) {\n      mOptions.activityTitle = activityTitle;\n      return this;\n    }\n\n    /**\n     * the color to use for action bar items icons.<br>\n     * <i>Default: NONE</i>\n     */\n    public ActivityBuilder setActivityMenuIconColor(int activityMenuIconColor) {\n      mOptions.activityMenuIconColor = activityMenuIconColor;\n      return this;\n    }\n\n    /**\n     * the Android Uri to save the cropped image to.<br>\n     * <i>Default: NONE, will create a temp file</i>\n     */\n    public ActivityBuilder setOutputUri(Uri outputUri) {\n      mOptions.outputUri = outputUri;\n      return this;\n    }\n\n    /**\n     * the compression format to use when writting the image.<br>\n     * <i>Default: JPEG</i>\n     */\n    public ActivityBuilder setOutputCompressFormat(Bitmap.CompressFormat outputCompressFormat) {\n      mOptions.outputCompressFormat = outputCompressFormat;\n      return this;\n    }\n\n    /**\n     * the quility (if applicable) to use when writting the image (0 - 100).<br>\n     * <i>Default: 90</i>\n     */\n    public ActivityBuilder setOutputCompressQuality(int outputCompressQuality) {\n      mOptions.outputCompressQuality = outputCompressQuality;\n      return this;\n    }\n\n    /**\n     * the size to resize the cropped image to.<br>\n     * Uses {@link CropImageView.RequestSizeOptions#RESIZE_INSIDE} option.<br>\n     * <i>Default: 0, 0 - not set, will not resize</i>\n     */\n    public ActivityBuilder setRequestedSize(int reqWidth, int reqHeight) {\n      return setRequestedSize(reqWidth, reqHeight, CropImageView.RequestSizeOptions.RESIZE_INSIDE);\n    }\n\n    /**\n     * the size to resize the cropped image to.<br>\n     * <i>Default: 0, 0 - not set, will not resize</i>\n     */\n    public ActivityBuilder setRequestedSize(\n        int reqWidth, int reqHeight, CropImageView.RequestSizeOptions options) {\n      mOptions.outputRequestWidth = reqWidth;\n      mOptions.outputRequestHeight = reqHeight;\n      mOptions.outputRequestSizeOptions = options;\n      return this;\n    }\n\n    /**\n     * if the result of crop image activity should not save the cropped image bitmap.<br>\n     * Used if you want to crop the image manually and need only the crop rectangle and rotation\n     * data.<br>\n     * <i>Default: false</i>\n     */\n    public ActivityBuilder setNoOutputImage(boolean noOutputImage) {\n      mOptions.noOutputImage = noOutputImage;\n      return this;\n    }\n\n    /**\n     * the initial rectangle to set on the cropping image after loading.<br>\n     * <i>Default: NONE - will initialize using initial crop window padding ratio</i>\n     */\n    public ActivityBuilder setInitialCropWindowRectangle(Rect initialCropWindowRectangle) {\n      mOptions.initialCropWindowRectangle = initialCropWindowRectangle;\n      return this;\n    }\n\n    /**\n     * the initial rotation to set on the cropping image after loading (0-360 degrees clockwise).\n     * <br>\n     * <i>Default: NONE - will read image exif data</i>\n     */\n    public ActivityBuilder setInitialRotation(int initialRotation) {\n      mOptions.initialRotation = (initialRotation + 360) % 360;\n      return this;\n    }\n\n    /**\n     * if to allow rotation during cropping.<br>\n     * <i>Default: true</i>\n     */\n    public ActivityBuilder setAllowRotation(boolean allowRotation) {\n      mOptions.allowRotation = allowRotation;\n      return this;\n    }\n\n    /**\n     * if to allow flipping during cropping.<br>\n     * <i>Default: true</i>\n     */\n    public ActivityBuilder setAllowFlipping(boolean allowFlipping) {\n      mOptions.allowFlipping = allowFlipping;\n      return this;\n    }\n\n    /**\n     * if to allow counter-clockwise rotation during cropping.<br>\n     * Note: if rotation is disabled this option has no effect.<br>\n     * <i>Default: false</i>\n     */\n    public ActivityBuilder setAllowCounterRotation(boolean allowCounterRotation) {\n      mOptions.allowCounterRotation = allowCounterRotation;\n      return this;\n    }\n\n    /**\n     * The amount of degreees to rotate clockwise or counter-clockwise (0-360).<br>\n     * <i>Default: 90</i>\n     */\n    public ActivityBuilder setRotationDegrees(int rotationDegrees) {\n      mOptions.rotationDegrees = (rotationDegrees + 360) % 360;\n      return this;\n    }\n\n    /**\n     * whether the image should be flipped horizontally.<br>\n     * <i>Default: false</i>\n     */\n    public ActivityBuilder setFlipHorizontally(boolean flipHorizontally) {\n      mOptions.flipHorizontally = flipHorizontally;\n      return this;\n    }\n\n    /**\n     * whether the image should be flipped vertically.<br>\n     * <i>Default: false</i>\n     */\n    public ActivityBuilder setFlipVertically(boolean flipVertically) {\n      mOptions.flipVertically = flipVertically;\n      return this;\n    }\n\n    /**\n     * optional, set crop menu crop button title.<br>\n     * <i>Default: null, will use resource string: crop_image_menu_crop</i>\n     */\n    public ActivityBuilder setCropMenuCropButtonTitle(CharSequence title) {\n      mOptions.cropMenuCropButtonTitle = title;\n      return this;\n    }\n\n    /**\n     * Image resource id to use for crop icon instead of text.<br>\n     * <i>Default: 0</i>\n     */\n    public ActivityBuilder setCropMenuCropButtonIcon(@DrawableRes int drawableResource) {\n      mOptions.cropMenuCropButtonIcon = drawableResource;\n      return this;\n    }\n  }\n  // endregion\n\n  // region: Inner class: ActivityResult\n\n  /** Result data of Crop Image Activity. */\n  public static final class ActivityResult extends CropImageView.CropResult implements Parcelable {\n\n    public static final Creator<ActivityResult> CREATOR =\n        new Creator<ActivityResult>() {\n          @Override\n          public ActivityResult createFromParcel(Parcel in) {\n            return new ActivityResult(in);\n          }\n\n          @Override\n          public ActivityResult[] newArray(int size) {\n            return new ActivityResult[size];\n          }\n        };\n\n    public ActivityResult(\n        Uri originalUri,\n        Uri uri,\n        Exception error,\n        float[] cropPoints,\n        Rect cropRect,\n        int rotation,\n        Rect wholeImageRect,\n        int sampleSize) {\n      super(\n          null,\n          originalUri,\n          null,\n          uri,\n          error,\n          cropPoints,\n          cropRect,\n          wholeImageRect,\n          rotation,\n          sampleSize);\n    }\n\n    protected ActivityResult(Parcel in) {\n      super(\n          null,\n          (Uri) in.readParcelable(Uri.class.getClassLoader()),\n          null,\n          (Uri) in.readParcelable(Uri.class.getClassLoader()),\n          (Exception) in.readSerializable(),\n          in.createFloatArray(),\n          (Rect) in.readParcelable(Rect.class.getClassLoader()),\n          (Rect) in.readParcelable(Rect.class.getClassLoader()),\n          in.readInt(),\n          in.readInt());\n    }\n\n    @Override\n    public void writeToParcel(Parcel dest, int flags) {\n      dest.writeParcelable(getOriginalUri(), flags);\n      dest.writeParcelable(getUri(), flags);\n      dest.writeSerializable(getError());\n      dest.writeFloatArray(getCropPoints());\n      dest.writeParcelable(getCropRect(), flags);\n      dest.writeParcelable(getWholeImageRect(), flags);\n      dest.writeInt(getRotation());\n      dest.writeInt(getSampleSize());\n    }\n\n    @Override\n    public int describeContents() {\n      return 0;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageActivity.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.graphics.Bitmap;\nimport android.graphics.PorterDuff;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport androidx.annotation.NonNull;\nimport androidx.core.content.ContextCompat;\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Log;\nimport android.view.Menu;\nimport android.view.MenuItem;\nimport android.widget.Toast;\n\nimport java.io.File;\nimport java.io.IOException;\n\n/**\n * Built-in activity for image cropping.<br>\n * Use {@link CropImage#activity(Uri)} to create a builder to start this activity.\n */\npublic class CropImageActivity extends AppCompatActivity\n    implements CropImageView.OnSetImageUriCompleteListener,\n        CropImageView.OnCropImageCompleteListener {\n\n  /** The crop image view library widget used in the activity */\n  private CropImageView mCropImageView;\n\n  /** Persist URI image to crop URI if specific permissions are required */\n  private Uri mCropImageUri;\n\n  /** the options that were set for the crop image */\n  private CropImageOptions mOptions;\n\n  @Override\n  @SuppressLint(\"NewApi\")\n  public void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.crop_image_activity);\n\n    mCropImageView = findViewById(R.id.cropImageView);\n\n    Bundle bundle = getIntent().getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE);\n    mCropImageUri = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE);\n    mOptions = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS);\n\n    if (savedInstanceState == null) {\n      if (mCropImageUri == null || mCropImageUri.equals(Uri.EMPTY)) {\n        if (CropImage.isExplicitCameraPermissionRequired(this)) {\n          // request permissions and handle the result in onRequestPermissionsResult()\n          requestPermissions(\n              new String[] {Manifest.permission.CAMERA},\n              CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE);\n        } else {\n          CropImage.startPickImageActivity(this);\n        }\n      } else if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) {\n        // request permissions and handle the result in onRequestPermissionsResult()\n        requestPermissions(\n            new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},\n            CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE);\n      } else {\n        // no permissions required or already grunted, can start crop image activity\n        mCropImageView.setImageUriAsync(mCropImageUri);\n      }\n    }\n\n    ActionBar actionBar = getSupportActionBar();\n    if (actionBar != null) {\n      CharSequence title = mOptions != null &&\n          mOptions.activityTitle != null && mOptions.activityTitle.length() > 0\n              ? mOptions.activityTitle\n              : getResources().getString(R.string.crop_image_activity_title);\n      actionBar.setTitle(title);\n      actionBar.setDisplayHomeAsUpEnabled(true);\n    }\n  }\n\n  @Override\n  protected void onStart() {\n    super.onStart();\n    mCropImageView.setOnSetImageUriCompleteListener(this);\n    mCropImageView.setOnCropImageCompleteListener(this);\n  }\n\n  @Override\n  protected void onStop() {\n    super.onStop();\n    mCropImageView.setOnSetImageUriCompleteListener(null);\n    mCropImageView.setOnCropImageCompleteListener(null);\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    getMenuInflater().inflate(R.menu.crop_image_menu, menu);\n\n    if (!mOptions.allowRotation) {\n      menu.removeItem(R.id.crop_image_menu_rotate_left);\n      menu.removeItem(R.id.crop_image_menu_rotate_right);\n    } else if (mOptions.allowCounterRotation) {\n      menu.findItem(R.id.crop_image_menu_rotate_left).setVisible(true);\n    }\n\n    if (!mOptions.allowFlipping) {\n      menu.removeItem(R.id.crop_image_menu_flip);\n    }\n\n    if (mOptions.cropMenuCropButtonTitle != null) {\n      menu.findItem(R.id.crop_image_menu_crop).setTitle(mOptions.cropMenuCropButtonTitle);\n    }\n\n    Drawable cropIcon = null;\n    try {\n      if (mOptions.cropMenuCropButtonIcon != 0) {\n        cropIcon = ContextCompat.getDrawable(this, mOptions.cropMenuCropButtonIcon);\n        menu.findItem(R.id.crop_image_menu_crop).setIcon(cropIcon);\n      }\n    } catch (Exception e) {\n      Log.w(\"AIC\", \"Failed to read menu crop drawable\", e);\n    }\n\n    if (mOptions.activityMenuIconColor != 0) {\n      updateMenuItemIconColor(\n          menu, R.id.crop_image_menu_rotate_left, mOptions.activityMenuIconColor);\n      updateMenuItemIconColor(\n          menu, R.id.crop_image_menu_rotate_right, mOptions.activityMenuIconColor);\n      updateMenuItemIconColor(menu, R.id.crop_image_menu_flip, mOptions.activityMenuIconColor);\n      if (cropIcon != null) {\n        updateMenuItemIconColor(menu, R.id.crop_image_menu_crop, mOptions.activityMenuIconColor);\n      }\n    }\n    return true;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    if (item.getItemId() == R.id.crop_image_menu_crop) {\n      cropImage();\n      return true;\n    }\n    if (item.getItemId() == R.id.crop_image_menu_rotate_left) {\n      rotateImage(-mOptions.rotationDegrees);\n      return true;\n    }\n    if (item.getItemId() == R.id.crop_image_menu_rotate_right) {\n      rotateImage(mOptions.rotationDegrees);\n      return true;\n    }\n    if (item.getItemId() == R.id.crop_image_menu_flip_horizontally) {\n      mCropImageView.flipImageHorizontally();\n      return true;\n    }\n    if (item.getItemId() == R.id.crop_image_menu_flip_vertically) {\n      mCropImageView.flipImageVertically();\n      return true;\n    }\n    if (item.getItemId() == android.R.id.home) {\n      setResultCancel();\n      return true;\n    }\n    return super.onOptionsItemSelected(item);\n  }\n\n  @Override\n  public void onBackPressed() {\n    super.onBackPressed();\n    setResultCancel();\n  }\n\n  @Override\n  @SuppressLint(\"NewApi\")\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n\n    // handle result of pick image chooser\n    if (requestCode == CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE) {\n      if (resultCode == Activity.RESULT_CANCELED) {\n        // User cancelled the picker. We don't have anything to crop\n        setResultCancel();\n      }\n\n      if (resultCode == Activity.RESULT_OK) {\n        mCropImageUri = CropImage.getPickImageResultUri(this, data);\n\n        // For API >= 23 we need to check specifically that we have permissions to read external\n        // storage.\n        if (CropImage.isReadExternalStoragePermissionsRequired(this, mCropImageUri)) {\n          // request permissions and handle the result in onRequestPermissionsResult()\n          requestPermissions(\n              new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},\n              CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE);\n        } else {\n          // no permissions required or already grunted, can start crop image activity\n          mCropImageView.setImageUriAsync(mCropImageUri);\n        }\n      }\n    }\n  }\n\n  @Override\n  public void onRequestPermissionsResult(\n      int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {\n    if (requestCode == CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE) {\n      if (mCropImageUri != null\n          && grantResults.length > 0\n          && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n        // required permissions granted, start crop image activity\n        mCropImageView.setImageUriAsync(mCropImageUri);\n      } else {\n        Toast.makeText(this, R.string.crop_image_activity_no_permissions, Toast.LENGTH_LONG).show();\n        setResultCancel();\n      }\n    }\n\n    if (requestCode == CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE) {\n      // Irrespective of whether camera permission was given or not, we show the picker\n      // The picker will not add the camera intent if permission is not available\n      CropImage.startPickImageActivity(this);\n    }\n  }\n\n  @Override\n  public void onSetImageUriComplete(CropImageView view, Uri uri, Exception error) {\n    if (error == null) {\n      if (mOptions.initialCropWindowRectangle != null) {\n        mCropImageView.setCropRect(mOptions.initialCropWindowRectangle);\n      }\n      if (mOptions.initialRotation > -1) {\n        mCropImageView.setRotatedDegrees(mOptions.initialRotation);\n      }\n    } else {\n      setResult(null, error, 1);\n    }\n  }\n\n  @Override\n  public void onCropImageComplete(CropImageView view, CropImageView.CropResult result) {\n    setResult(result.getUri(), result.getError(), result.getSampleSize());\n  }\n\n  // region: Private methods\n\n  /** Execute crop image and save the result tou output uri. */\n  protected void cropImage() {\n    if (mOptions.noOutputImage) {\n      setResult(null, null, 1);\n    } else {\n      Uri outputUri = getOutputUri();\n      mCropImageView.saveCroppedImageAsync(\n          outputUri,\n          mOptions.outputCompressFormat,\n          mOptions.outputCompressQuality,\n          mOptions.outputRequestWidth,\n          mOptions.outputRequestHeight,\n          mOptions.outputRequestSizeOptions);\n    }\n  }\n\n  /** Rotate the image in the crop image view. */\n  protected void rotateImage(int degrees) {\n    mCropImageView.rotateImage(degrees);\n  }\n\n  /**\n   * Get Android uri to save the cropped image into.<br>\n   * Use the given in options or create a temp file.\n   */\n  protected Uri getOutputUri() {\n    Uri outputUri = mOptions.outputUri;\n    if (outputUri == null || outputUri.equals(Uri.EMPTY)) {\n      try {\n        String ext =\n            mOptions.outputCompressFormat == Bitmap.CompressFormat.JPEG\n                ? \".jpg\"\n                : mOptions.outputCompressFormat == Bitmap.CompressFormat.PNG ? \".png\" : \".webp\";\n        outputUri = Uri.fromFile(File.createTempFile(\"cropped\", ext, getCacheDir()));\n      } catch (IOException e) {\n        throw new RuntimeException(\"Failed to create temp file for output image\", e);\n      }\n    }\n    return outputUri;\n  }\n\n  /** Result with cropped image data or error if failed. */\n  protected void setResult(Uri uri, Exception error, int sampleSize) {\n    int resultCode = error == null ? RESULT_OK : CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE;\n    setResult(resultCode, getResultIntent(uri, error, sampleSize));\n    finish();\n  }\n\n  /** Cancel of cropping activity. */\n  protected void setResultCancel() {\n    setResult(RESULT_CANCELED);\n    finish();\n  }\n\n  /** Get intent instance to be used for the result of this activity. */\n  protected Intent getResultIntent(Uri uri, Exception error, int sampleSize) {\n    CropImage.ActivityResult result =\n        new CropImage.ActivityResult(\n            mCropImageView.getImageUri(),\n            uri,\n            error,\n            mCropImageView.getCropPoints(),\n            mCropImageView.getCropRect(),\n            mCropImageView.getRotatedDegrees(),\n            mCropImageView.getWholeImageRect(),\n            sampleSize);\n    Intent intent = new Intent();\n    intent.putExtras(getIntent());\n    intent.putExtra(CropImage.CROP_IMAGE_EXTRA_RESULT, result);\n    return intent;\n  }\n\n  /** Update the color of a specific menu item to the given color. */\n  private void updateMenuItemIconColor(Menu menu, int itemId, int color) {\n    MenuItem menuItem = menu.findItem(itemId);\n    if (menuItem != null) {\n      Drawable menuItemIcon = menuItem.getIcon();\n      if (menuItemIcon != null) {\n        try {\n          menuItemIcon.mutate();\n          menuItemIcon.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);\n          menuItem.setIcon(menuItemIcon);\n        } catch (Exception e) {\n          Log.w(\"AIC\", \"Failed to update menu item color\", e);\n        }\n      }\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageAnimation.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.graphics.Matrix;\nimport android.graphics.RectF;\nimport android.view.animation.AccelerateDecelerateInterpolator;\nimport android.view.animation.Animation;\nimport android.view.animation.Transformation;\nimport android.widget.ImageView;\n\n/**\n * Animation to handle smooth cropping image matrix transformation change, specifically for\n * zoom-in/out.\n */\nfinal class CropImageAnimation extends Animation implements Animation.AnimationListener {\n\n  // region: Fields and Consts\n\n  private final ImageView mImageView;\n\n  private final CropOverlayView mCropOverlayView;\n\n  private final float[] mStartBoundPoints = new float[8];\n\n  private final float[] mEndBoundPoints = new float[8];\n\n  private final RectF mStartCropWindowRect = new RectF();\n\n  private final RectF mEndCropWindowRect = new RectF();\n\n  private final float[] mStartImageMatrix = new float[9];\n\n  private final float[] mEndImageMatrix = new float[9];\n\n  private final RectF mAnimRect = new RectF();\n\n  private final float[] mAnimPoints = new float[8];\n\n  private final float[] mAnimMatrix = new float[9];\n  // endregion\n\n  public CropImageAnimation(ImageView cropImageView, CropOverlayView cropOverlayView) {\n    mImageView = cropImageView;\n    mCropOverlayView = cropOverlayView;\n\n    setDuration(300);\n    setFillAfter(true);\n    setInterpolator(new AccelerateDecelerateInterpolator());\n    setAnimationListener(this);\n  }\n\n  public void setStartState(float[] boundPoints, Matrix imageMatrix) {\n    reset();\n    System.arraycopy(boundPoints, 0, mStartBoundPoints, 0, 8);\n    mStartCropWindowRect.set(mCropOverlayView.getCropWindowRect());\n    imageMatrix.getValues(mStartImageMatrix);\n  }\n\n  public void setEndState(float[] boundPoints, Matrix imageMatrix) {\n    System.arraycopy(boundPoints, 0, mEndBoundPoints, 0, 8);\n    mEndCropWindowRect.set(mCropOverlayView.getCropWindowRect());\n    imageMatrix.getValues(mEndImageMatrix);\n  }\n\n  @Override\n  protected void applyTransformation(float interpolatedTime, Transformation t) {\n\n    mAnimRect.left =\n        mStartCropWindowRect.left\n            + (mEndCropWindowRect.left - mStartCropWindowRect.left) * interpolatedTime;\n    mAnimRect.top =\n        mStartCropWindowRect.top\n            + (mEndCropWindowRect.top - mStartCropWindowRect.top) * interpolatedTime;\n    mAnimRect.right =\n        mStartCropWindowRect.right\n            + (mEndCropWindowRect.right - mStartCropWindowRect.right) * interpolatedTime;\n    mAnimRect.bottom =\n        mStartCropWindowRect.bottom\n            + (mEndCropWindowRect.bottom - mStartCropWindowRect.bottom) * interpolatedTime;\n    mCropOverlayView.setCropWindowRect(mAnimRect);\n\n    for (int i = 0; i < mAnimPoints.length; i++) {\n      mAnimPoints[i] =\n          mStartBoundPoints[i] + (mEndBoundPoints[i] - mStartBoundPoints[i]) * interpolatedTime;\n    }\n    mCropOverlayView.setBounds(mAnimPoints, mImageView.getWidth(), mImageView.getHeight());\n\n    for (int i = 0; i < mAnimMatrix.length; i++) {\n      mAnimMatrix[i] =\n          mStartImageMatrix[i] + (mEndImageMatrix[i] - mStartImageMatrix[i]) * interpolatedTime;\n    }\n    Matrix m = mImageView.getImageMatrix();\n    m.setValues(mAnimMatrix);\n    mImageView.setImageMatrix(m);\n\n    mImageView.invalidate();\n    mCropOverlayView.invalidate();\n  }\n\n  @Override\n  public void onAnimationStart(Animation animation) {}\n\n  @Override\n  public void onAnimationEnd(Animation animation) {\n    mImageView.clearAnimation();\n  }\n\n  @Override\n  public void onAnimationRepeat(Animation animation) {}\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageOptions.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth;\n// inexhaustible as the great rivers.\n// When they come to an end;\n// they begin again;\n// like the days and months;\n// they die and are reborn;\n// like the four seasons.\"\n//\n// - Sun Tsu;\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.content.res.Resources;\nimport android.graphics.Bitmap;\nimport android.graphics.Color;\nimport android.graphics.Rect;\nimport android.net.Uri;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.text.TextUtils;\nimport android.util.DisplayMetrics;\nimport android.util.TypedValue;\n\n/**\n * All the possible options that can be set to customize crop image.<br>\n * Initialized with default values.\n */\npublic class CropImageOptions implements Parcelable {\n\n  public static final Creator<CropImageOptions> CREATOR =\n      new Creator<CropImageOptions>() {\n        @Override\n        public CropImageOptions createFromParcel(Parcel in) {\n          return new CropImageOptions(in);\n        }\n\n        @Override\n        public CropImageOptions[] newArray(int size) {\n          return new CropImageOptions[size];\n        }\n      };\n\n  /** The shape of the cropping window. */\n  public CropImageView.CropShape cropShape;\n\n  /**\n   * An edge of the crop window will snap to the corresponding edge of a specified bounding box when\n   * the crop window edge is less than or equal to this distance (in pixels) away from the bounding\n   * box edge. (in pixels)\n   */\n  public float snapRadius;\n\n  /**\n   * The radius of the touchable area around the handle. (in pixels)<br>\n   * We are basing this value off of the recommended 48dp Rhythm.<br>\n   * See: http://developer.android.com/design/style/metrics-grids.html#48dp-rhythm\n   */\n  public float touchRadius;\n\n  /** whether the guidelines should be on, off, or only showing when resizing. */\n  public CropImageView.Guidelines guidelines;\n\n  /** The initial scale type of the image in the crop image view */\n  public CropImageView.ScaleType scaleType;\n\n  /**\n   * if to show crop overlay UI what contains the crop window UI surrounded by background over the\n   * cropping image.<br>\n   * default: true, may disable for animation or frame transition.\n   */\n  public boolean showCropOverlay;\n\n  /**\n   * if to show progress bar when image async loading/cropping is in progress.<br>\n   * default: true, disable to provide custom progress bar UI.\n   */\n  public boolean showProgressBar;\n\n  /**\n   * if auto-zoom functionality is enabled.<br>\n   * default: true.\n   */\n  public boolean autoZoomEnabled;\n\n  /** if multi-touch should be enabled on the crop box default: false */\n  public boolean multiTouchEnabled;\n\n  /** The max zoom allowed during cropping. */\n  public int maxZoom;\n\n  /**\n   * The initial crop window padding from image borders in percentage of the cropping image\n   * dimensions.\n   */\n  public float initialCropWindowPaddingRatio;\n\n  /** whether the width to height aspect ratio should be maintained or free to change. */\n  public boolean fixAspectRatio;\n\n  /** the X value of the aspect ratio. */\n  public int aspectRatioX;\n\n  /** the Y value of the aspect ratio. */\n  public int aspectRatioY;\n\n  /** the thickness of the guidelines lines in pixels. (in pixels) */\n  public float borderLineThickness;\n\n  /** the color of the guidelines lines */\n  public int borderLineColor;\n\n  /** thickness of the corner line. (in pixels) */\n  public float borderCornerThickness;\n\n  /** the offset of corner line from crop window border. (in pixels) */\n  public float borderCornerOffset;\n\n  /** the length of the corner line away from the corner. (in pixels) */\n  public float borderCornerLength;\n\n  /** the color of the corner line */\n  public int borderCornerColor;\n\n  /** the thickness of the guidelines lines. (in pixels) */\n  public float guidelinesThickness;\n\n  /** the color of the guidelines lines */\n  public int guidelinesColor;\n\n  /**\n   * the color of the overlay background around the crop window cover the image parts not in the\n   * crop window.\n   */\n  public int backgroundColor;\n\n  /** the min width the crop window is allowed to be. (in pixels) */\n  public int minCropWindowWidth;\n\n  /** the min height the crop window is allowed to be. (in pixels) */\n  public int minCropWindowHeight;\n\n  /**\n   * the min width the resulting cropping image is allowed to be, affects the cropping window\n   * limits. (in pixels)\n   */\n  public int minCropResultWidth;\n\n  /**\n   * the min height the resulting cropping image is allowed to be, affects the cropping window\n   * limits. (in pixels)\n   */\n  public int minCropResultHeight;\n\n  /**\n   * the max width the resulting cropping image is allowed to be, affects the cropping window\n   * limits. (in pixels)\n   */\n  public int maxCropResultWidth;\n\n  /**\n   * the max height the resulting cropping image is allowed to be, affects the cropping window\n   * limits. (in pixels)\n   */\n  public int maxCropResultHeight;\n\n  /** the title of the {@link CropImageActivity} */\n  public CharSequence activityTitle;\n\n  /** the color to use for action bar items icons */\n  public int activityMenuIconColor;\n\n  /** the Android Uri to save the cropped image to */\n  public Uri outputUri;\n\n  /** the compression format to use when writing the image */\n  public Bitmap.CompressFormat outputCompressFormat;\n\n  /** the quality (if applicable) to use when writing the image (0 - 100) */\n  public int outputCompressQuality;\n\n  /** the width to resize the cropped image to (see options) */\n  public int outputRequestWidth;\n\n  /** the height to resize the cropped image to (see options) */\n  public int outputRequestHeight;\n\n  /** the resize method to use on the cropped bitmap (see options documentation) */\n  public CropImageView.RequestSizeOptions outputRequestSizeOptions;\n\n  /** if the result of crop image activity should not save the cropped image bitmap */\n  public boolean noOutputImage;\n\n  /** the initial rectangle to set on the cropping image after loading */\n  public Rect initialCropWindowRectangle;\n\n  /** the initial rotation to set on the cropping image after loading (0-360 degrees clockwise) */\n  public int initialRotation;\n\n  /** if to allow (all) rotation during cropping (activity) */\n  public boolean allowRotation;\n\n  /** if to allow (all) flipping during cropping (activity) */\n  public boolean allowFlipping;\n\n  /** if to allow counter-clockwise rotation during cropping (activity) */\n  public boolean allowCounterRotation;\n\n  /** the amount of degrees to rotate clockwise or counter-clockwise */\n  public int rotationDegrees;\n\n  /** whether the image should be flipped horizontally */\n  public boolean flipHorizontally;\n\n  /** whether the image should be flipped vertically */\n  public boolean flipVertically;\n\n  /** optional, the text of the crop menu crop button */\n  public CharSequence cropMenuCropButtonTitle;\n\n  /** optional image resource to be used for crop menu crop icon instead of text */\n  public int cropMenuCropButtonIcon;\n\n  /** Init options with defaults. */\n  public CropImageOptions() {\n\n    DisplayMetrics dm = Resources.getSystem().getDisplayMetrics();\n\n    cropShape = CropImageView.CropShape.RECTANGLE;\n    snapRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);\n    touchRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, dm);\n    guidelines = CropImageView.Guidelines.ON_TOUCH;\n    scaleType = CropImageView.ScaleType.FIT_CENTER;\n    showCropOverlay = true;\n    showProgressBar = true;\n    autoZoomEnabled = true;\n    multiTouchEnabled = false;\n    maxZoom = 4;\n    initialCropWindowPaddingRatio = 0.1f;\n\n    fixAspectRatio = false;\n    aspectRatioX = 1;\n    aspectRatioY = 1;\n\n    borderLineThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, dm);\n    borderLineColor = Color.argb(170, 255, 255, 255);\n    borderCornerThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, dm);\n    borderCornerOffset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, dm);\n    borderCornerLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 14, dm);\n    borderCornerColor = Color.WHITE;\n\n    guidelinesThickness = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, dm);\n    guidelinesColor = Color.argb(170, 255, 255, 255);\n    backgroundColor = Color.argb(119, 0, 0, 0);\n\n    minCropWindowWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);\n    minCropWindowHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 42, dm);\n    minCropResultWidth = 40;\n    minCropResultHeight = 40;\n    maxCropResultWidth = 99999;\n    maxCropResultHeight = 99999;\n\n    activityTitle = \"\";\n    activityMenuIconColor = 0;\n\n    outputUri = Uri.EMPTY;\n    outputCompressFormat = Bitmap.CompressFormat.JPEG;\n    outputCompressQuality = 90;\n    outputRequestWidth = 0;\n    outputRequestHeight = 0;\n    outputRequestSizeOptions = CropImageView.RequestSizeOptions.NONE;\n    noOutputImage = false;\n\n    initialCropWindowRectangle = null;\n    initialRotation = -1;\n    allowRotation = true;\n    allowFlipping = true;\n    allowCounterRotation = false;\n    rotationDegrees = 90;\n    flipHorizontally = false;\n    flipVertically = false;\n    cropMenuCropButtonTitle = null;\n\n    cropMenuCropButtonIcon = 0;\n  }\n\n  /** Create object from parcel. */\n  protected CropImageOptions(Parcel in) {\n    cropShape = CropImageView.CropShape.values()[in.readInt()];\n    snapRadius = in.readFloat();\n    touchRadius = in.readFloat();\n    guidelines = CropImageView.Guidelines.values()[in.readInt()];\n    scaleType = CropImageView.ScaleType.values()[in.readInt()];\n    showCropOverlay = in.readByte() != 0;\n    showProgressBar = in.readByte() != 0;\n    autoZoomEnabled = in.readByte() != 0;\n    multiTouchEnabled = in.readByte() != 0;\n    maxZoom = in.readInt();\n    initialCropWindowPaddingRatio = in.readFloat();\n    fixAspectRatio = in.readByte() != 0;\n    aspectRatioX = in.readInt();\n    aspectRatioY = in.readInt();\n    borderLineThickness = in.readFloat();\n    borderLineColor = in.readInt();\n    borderCornerThickness = in.readFloat();\n    borderCornerOffset = in.readFloat();\n    borderCornerLength = in.readFloat();\n    borderCornerColor = in.readInt();\n    guidelinesThickness = in.readFloat();\n    guidelinesColor = in.readInt();\n    backgroundColor = in.readInt();\n    minCropWindowWidth = in.readInt();\n    minCropWindowHeight = in.readInt();\n    minCropResultWidth = in.readInt();\n    minCropResultHeight = in.readInt();\n    maxCropResultWidth = in.readInt();\n    maxCropResultHeight = in.readInt();\n    activityTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);\n    activityMenuIconColor = in.readInt();\n    outputUri = in.readParcelable(Uri.class.getClassLoader());\n    outputCompressFormat = Bitmap.CompressFormat.valueOf(in.readString());\n    outputCompressQuality = in.readInt();\n    outputRequestWidth = in.readInt();\n    outputRequestHeight = in.readInt();\n    outputRequestSizeOptions = CropImageView.RequestSizeOptions.values()[in.readInt()];\n    noOutputImage = in.readByte() != 0;\n    initialCropWindowRectangle = in.readParcelable(Rect.class.getClassLoader());\n    initialRotation = in.readInt();\n    allowRotation = in.readByte() != 0;\n    allowFlipping = in.readByte() != 0;\n    allowCounterRotation = in.readByte() != 0;\n    rotationDegrees = in.readInt();\n    flipHorizontally = in.readByte() != 0;\n    flipVertically = in.readByte() != 0;\n    cropMenuCropButtonTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);\n    cropMenuCropButtonIcon = in.readInt();\n  }\n\n  @Override\n  public void writeToParcel(Parcel dest, int flags) {\n    dest.writeInt(cropShape.ordinal());\n    dest.writeFloat(snapRadius);\n    dest.writeFloat(touchRadius);\n    dest.writeInt(guidelines.ordinal());\n    dest.writeInt(scaleType.ordinal());\n    dest.writeByte((byte) (showCropOverlay ? 1 : 0));\n    dest.writeByte((byte) (showProgressBar ? 1 : 0));\n    dest.writeByte((byte) (autoZoomEnabled ? 1 : 0));\n    dest.writeByte((byte) (multiTouchEnabled ? 1 : 0));\n    dest.writeInt(maxZoom);\n    dest.writeFloat(initialCropWindowPaddingRatio);\n    dest.writeByte((byte) (fixAspectRatio ? 1 : 0));\n    dest.writeInt(aspectRatioX);\n    dest.writeInt(aspectRatioY);\n    dest.writeFloat(borderLineThickness);\n    dest.writeInt(borderLineColor);\n    dest.writeFloat(borderCornerThickness);\n    dest.writeFloat(borderCornerOffset);\n    dest.writeFloat(borderCornerLength);\n    dest.writeInt(borderCornerColor);\n    dest.writeFloat(guidelinesThickness);\n    dest.writeInt(guidelinesColor);\n    dest.writeInt(backgroundColor);\n    dest.writeInt(minCropWindowWidth);\n    dest.writeInt(minCropWindowHeight);\n    dest.writeInt(minCropResultWidth);\n    dest.writeInt(minCropResultHeight);\n    dest.writeInt(maxCropResultWidth);\n    dest.writeInt(maxCropResultHeight);\n    TextUtils.writeToParcel(activityTitle, dest, flags);\n    dest.writeInt(activityMenuIconColor);\n    dest.writeParcelable(outputUri, flags);\n    dest.writeString(outputCompressFormat.name());\n    dest.writeInt(outputCompressQuality);\n    dest.writeInt(outputRequestWidth);\n    dest.writeInt(outputRequestHeight);\n    dest.writeInt(outputRequestSizeOptions.ordinal());\n    dest.writeInt(noOutputImage ? 1 : 0);\n    dest.writeParcelable(initialCropWindowRectangle, flags);\n    dest.writeInt(initialRotation);\n    dest.writeByte((byte) (allowRotation ? 1 : 0));\n    dest.writeByte((byte) (allowFlipping ? 1 : 0));\n    dest.writeByte((byte) (allowCounterRotation ? 1 : 0));\n    dest.writeInt(rotationDegrees);\n    dest.writeByte((byte) (flipHorizontally ? 1 : 0));\n    dest.writeByte((byte) (flipVertically ? 1 : 0));\n    TextUtils.writeToParcel(cropMenuCropButtonTitle, dest, flags);\n    dest.writeInt(cropMenuCropButtonIcon);\n  }\n\n  @Override\n  public int describeContents() {\n    return 0;\n  }\n\n  /**\n   * Validate all the options are withing valid range.\n   *\n   * @throws IllegalArgumentException if any of the options is not valid\n   */\n  public void validate() {\n    if (maxZoom < 0) {\n      throw new IllegalArgumentException(\"Cannot set max zoom to a number < 1\");\n    }\n    if (touchRadius < 0) {\n      throw new IllegalArgumentException(\"Cannot set touch radius value to a number <= 0 \");\n    }\n    if (initialCropWindowPaddingRatio < 0 || initialCropWindowPaddingRatio >= 0.5) {\n      throw new IllegalArgumentException(\n          \"Cannot set initial crop window padding value to a number < 0 or >= 0.5\");\n    }\n    if (aspectRatioX <= 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set aspect ratio value to a number less than or equal to 0.\");\n    }\n    if (aspectRatioY <= 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set aspect ratio value to a number less than or equal to 0.\");\n    }\n    if (borderLineThickness < 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set line thickness value to a number less than 0.\");\n    }\n    if (borderCornerThickness < 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set corner thickness value to a number less than 0.\");\n    }\n    if (guidelinesThickness < 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set guidelines thickness value to a number less than 0.\");\n    }\n    if (minCropWindowHeight < 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set min crop window height value to a number < 0 \");\n    }\n    if (minCropResultWidth < 0) {\n      throw new IllegalArgumentException(\"Cannot set min crop result width value to a number < 0 \");\n    }\n    if (minCropResultHeight < 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set min crop result height value to a number < 0 \");\n    }\n    if (maxCropResultWidth < minCropResultWidth) {\n      throw new IllegalArgumentException(\n          \"Cannot set max crop result width to smaller value than min crop result width\");\n    }\n    if (maxCropResultHeight < minCropResultHeight) {\n      throw new IllegalArgumentException(\n          \"Cannot set max crop result height to smaller value than min crop result height\");\n    }\n    if (outputRequestWidth < 0) {\n      throw new IllegalArgumentException(\"Cannot set request width value to a number < 0 \");\n    }\n    if (outputRequestHeight < 0) {\n      throw new IllegalArgumentException(\"Cannot set request height value to a number < 0 \");\n    }\n    if (rotationDegrees < 0 || rotationDegrees > 360) {\n      throw new IllegalArgumentException(\n          \"Cannot set rotation degrees value to a number < 0 or > 360\");\n    }\n  }\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropImageView.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.TypedArray;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.Matrix;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.net.Uri;\nimport android.os.AsyncTask;\nimport android.os.Bundle;\nimport android.os.Parcelable;\nimport androidx.exifinterface.media.ExifInterface;\nimport android.util.AttributeSet;\nimport android.util.Pair;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.FrameLayout;\nimport android.widget.ImageView;\nimport android.widget.ProgressBar;\n\nimport java.lang.ref.WeakReference;\nimport java.util.UUID;\n\n/** Custom view that provides cropping capabilities to an image. */\npublic class CropImageView extends FrameLayout {\n\n  // region: Fields and Consts\n\n  /** Image view widget used to show the image for cropping. */\n  private final ImageView mImageView;\n\n  /** Overlay over the image view to show cropping UI. */\n  private final CropOverlayView mCropOverlayView;\n\n  /** The matrix used to transform the cropping image in the image view */\n  private final Matrix mImageMatrix = new Matrix();\n\n  /** Reusing matrix instance for reverse matrix calculations. */\n  private final Matrix mImageInverseMatrix = new Matrix();\n\n  /** Progress bar widget to show progress bar on async image loading and cropping. */\n  private final ProgressBar mProgressBar;\n\n  /** Rectangle used in image matrix transformation calculation (reusing rect instance) */\n  private final float[] mImagePoints = new float[8];\n\n  /** Rectangle used in image matrix transformation for scale calculation (reusing rect instance) */\n  private final float[] mScaleImagePoints = new float[8];\n\n  /** Animation class to smooth animate zoom-in/out */\n  private CropImageAnimation mAnimation;\n\n  private Bitmap mBitmap;\n\n  /** The image rotation value used during loading of the image so we can reset to it */\n  private int mInitialDegreesRotated;\n\n  /** How much the image is rotated from original clockwise */\n  private int mDegreesRotated;\n\n  /** if the image flipped horizontally */\n  private boolean mFlipHorizontally;\n\n  /** if the image flipped vertically */\n  private boolean mFlipVertically;\n\n  private int mLayoutWidth;\n\n  private int mLayoutHeight;\n\n  private int mImageResource;\n\n  /** The initial scale type of the image in the crop image view */\n  private ScaleType mScaleType;\n\n  /**\n   * if to save bitmap on save instance state.<br>\n   * It is best to avoid it by using URI in setting image for cropping.<br>\n   * If false the bitmap is not saved and if restore is required to view will be empty, storing the\n   * bitmap requires saving it to file which can be expensive. default: false.\n   */\n  private boolean mSaveBitmapToInstanceState = false;\n\n  /**\n   * if to show crop overlay UI what contains the crop window UI surrounded by background over the\n   * cropping image.<br>\n   * default: true, may disable for animation or frame transition.\n   */\n  private boolean mShowCropOverlay = true;\n\n  /**\n   * if to show progress bar when image async loading/cropping is in progress.<br>\n   * default: true, disable to provide custom progress bar UI.\n   */\n  private boolean mShowProgressBar = true;\n\n  /**\n   * if auto-zoom functionality is enabled.<br>\n   * default: true.\n   */\n  private boolean mAutoZoomEnabled = true;\n\n  /** The max zoom allowed during cropping */\n  private int mMaxZoom;\n\n  /** callback to be invoked when crop overlay is released. */\n  private OnSetCropOverlayReleasedListener mOnCropOverlayReleasedListener;\n\n  /** callback to be invoked when crop overlay is moved. */\n  private OnSetCropOverlayMovedListener mOnSetCropOverlayMovedListener;\n\n  /** callback to be invoked when crop window is changed. */\n  private OnSetCropWindowChangeListener mOnSetCropWindowChangeListener;\n\n  /** callback to be invoked when image async loading is complete. */\n  private OnSetImageUriCompleteListener mOnSetImageUriCompleteListener;\n\n  /** callback to be invoked when image async cropping is complete. */\n  private OnCropImageCompleteListener mOnCropImageCompleteListener;\n\n  /** The URI that the image was loaded from (if loaded from URI) */\n  private Uri mLoadedImageUri;\n\n  /** The sample size the image was loaded by if was loaded by URI */\n  private int mLoadedSampleSize = 1;\n\n  /** The current zoom level to to scale the cropping image */\n  private float mZoom = 1;\n\n  /** The X offset that the cropping image was translated after zooming */\n  private float mZoomOffsetX;\n\n  /** The Y offset that the cropping image was translated after zooming */\n  private float mZoomOffsetY;\n\n  /** Used to restore the cropping windows rectangle after state restore */\n  private RectF mRestoreCropWindowRect;\n\n  /** Used to restore image rotation after state restore */\n  private int mRestoreDegreesRotated;\n\n  /**\n   * Used to detect size change to handle auto-zoom using {@link #handleCropWindowChanged(boolean,\n   * boolean)} in {@link #layout(int, int, int, int)}.\n   */\n  private boolean mSizeChanged;\n\n  /**\n   * Temp URI used to save bitmap image to disk to preserve for instance state in case cropped was\n   * set with bitmap\n   */\n  private Uri mSaveInstanceStateBitmapUri;\n\n  /** Task used to load bitmap async from UI thread */\n  private WeakReference<BitmapLoadingWorkerTask> mBitmapLoadingWorkerTask;\n\n  /** Task used to crop bitmap async from UI thread */\n  private WeakReference<BitmapCroppingWorkerTask> mBitmapCroppingWorkerTask;\n  // endregion\n\n  public CropImageView(Context context) {\n    this(context, null);\n  }\n\n  public CropImageView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n\n    CropImageOptions options = null;\n    Intent intent = context instanceof Activity ? ((Activity) context).getIntent() : null;\n    if (intent != null) {\n      Bundle bundle = intent.getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE);\n      if (bundle != null) {\n        options = bundle.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS);\n      }\n    }\n\n    if (options == null) {\n\n      options = new CropImageOptions();\n\n      if (attrs != null) {\n        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0);\n        try {\n          options.fixAspectRatio =\n              ta.getBoolean(R.styleable.CropImageView_cropFixAspectRatio, options.fixAspectRatio);\n          options.aspectRatioX =\n              ta.getInteger(R.styleable.CropImageView_cropAspectRatioX, options.aspectRatioX);\n          options.aspectRatioY =\n              ta.getInteger(R.styleable.CropImageView_cropAspectRatioY, options.aspectRatioY);\n          options.scaleType =\n              ScaleType.values()[\n                  ta.getInt(R.styleable.CropImageView_cropScaleType, options.scaleType.ordinal())];\n          options.autoZoomEnabled =\n              ta.getBoolean(R.styleable.CropImageView_cropAutoZoomEnabled, options.autoZoomEnabled);\n          options.multiTouchEnabled =\n              ta.getBoolean(\n                  R.styleable.CropImageView_cropMultiTouchEnabled, options.multiTouchEnabled);\n          options.maxZoom = ta.getInteger(R.styleable.CropImageView_cropMaxZoom, options.maxZoom);\n          options.cropShape =\n              CropShape.values()[\n                  ta.getInt(R.styleable.CropImageView_cropShape, options.cropShape.ordinal())];\n          options.guidelines =\n              Guidelines.values()[\n                  ta.getInt(\n                      R.styleable.CropImageView_cropGuidelines, options.guidelines.ordinal())];\n          options.snapRadius =\n              ta.getDimension(R.styleable.CropImageView_cropSnapRadius, options.snapRadius);\n          options.touchRadius =\n              ta.getDimension(R.styleable.CropImageView_cropTouchRadius, options.touchRadius);\n          options.initialCropWindowPaddingRatio =\n              ta.getFloat(\n                  R.styleable.CropImageView_cropInitialCropWindowPaddingRatio,\n                  options.initialCropWindowPaddingRatio);\n          options.borderLineThickness =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropBorderLineThickness, options.borderLineThickness);\n          options.borderLineColor =\n              ta.getInteger(R.styleable.CropImageView_cropBorderLineColor, options.borderLineColor);\n          options.borderCornerThickness =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropBorderCornerThickness,\n                  options.borderCornerThickness);\n          options.borderCornerOffset =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropBorderCornerOffset, options.borderCornerOffset);\n          options.borderCornerLength =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropBorderCornerLength, options.borderCornerLength);\n          options.borderCornerColor =\n              ta.getInteger(\n                  R.styleable.CropImageView_cropBorderCornerColor, options.borderCornerColor);\n          options.guidelinesThickness =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropGuidelinesThickness, options.guidelinesThickness);\n          options.guidelinesColor =\n              ta.getInteger(R.styleable.CropImageView_cropGuidelinesColor, options.guidelinesColor);\n          options.backgroundColor =\n              ta.getInteger(R.styleable.CropImageView_cropBackgroundColor, options.backgroundColor);\n          options.showCropOverlay =\n              ta.getBoolean(R.styleable.CropImageView_cropShowCropOverlay, mShowCropOverlay);\n          options.showProgressBar =\n              ta.getBoolean(R.styleable.CropImageView_cropShowProgressBar, mShowProgressBar);\n          options.borderCornerThickness =\n              ta.getDimension(\n                  R.styleable.CropImageView_cropBorderCornerThickness,\n                  options.borderCornerThickness);\n          options.minCropWindowWidth =\n              (int)\n                  ta.getDimension(\n                      R.styleable.CropImageView_cropMinCropWindowWidth, options.minCropWindowWidth);\n          options.minCropWindowHeight =\n              (int)\n                  ta.getDimension(\n                      R.styleable.CropImageView_cropMinCropWindowHeight,\n                      options.minCropWindowHeight);\n          options.minCropResultWidth =\n              (int)\n                  ta.getFloat(\n                      R.styleable.CropImageView_cropMinCropResultWidthPX,\n                      options.minCropResultWidth);\n          options.minCropResultHeight =\n              (int)\n                  ta.getFloat(\n                      R.styleable.CropImageView_cropMinCropResultHeightPX,\n                      options.minCropResultHeight);\n          options.maxCropResultWidth =\n              (int)\n                  ta.getFloat(\n                      R.styleable.CropImageView_cropMaxCropResultWidthPX,\n                      options.maxCropResultWidth);\n          options.maxCropResultHeight =\n              (int)\n                  ta.getFloat(\n                      R.styleable.CropImageView_cropMaxCropResultHeightPX,\n                      options.maxCropResultHeight);\n          options.flipHorizontally =\n              ta.getBoolean(\n                  R.styleable.CropImageView_cropFlipHorizontally, options.flipHorizontally);\n          options.flipVertically =\n              ta.getBoolean(R.styleable.CropImageView_cropFlipHorizontally, options.flipVertically);\n\n          mSaveBitmapToInstanceState =\n              ta.getBoolean(\n                  R.styleable.CropImageView_cropSaveBitmapToInstanceState,\n                  mSaveBitmapToInstanceState);\n\n          // if aspect ratio is set then set fixed to true\n          if (ta.hasValue(R.styleable.CropImageView_cropAspectRatioX)\n              && ta.hasValue(R.styleable.CropImageView_cropAspectRatioX)\n              && !ta.hasValue(R.styleable.CropImageView_cropFixAspectRatio)) {\n            options.fixAspectRatio = true;\n          }\n        } finally {\n          ta.recycle();\n        }\n      }\n    }\n\n    options.validate();\n\n    mScaleType = options.scaleType;\n    mAutoZoomEnabled = options.autoZoomEnabled;\n    mMaxZoom = options.maxZoom;\n    mShowCropOverlay = options.showCropOverlay;\n    mShowProgressBar = options.showProgressBar;\n    mFlipHorizontally = options.flipHorizontally;\n    mFlipVertically = options.flipVertically;\n\n    LayoutInflater inflater = LayoutInflater.from(context);\n    View v = inflater.inflate(R.layout.crop_image_view, this, true);\n\n    mImageView = v.findViewById(R.id.ImageView_image);\n    mImageView.setScaleType(ImageView.ScaleType.MATRIX);\n\n    mCropOverlayView = v.findViewById(R.id.CropOverlayView);\n    mCropOverlayView.setCropWindowChangeListener(\n        new CropOverlayView.CropWindowChangeListener() {\n          @Override\n          public void onCropWindowChanged(boolean inProgress) {\n            handleCropWindowChanged(inProgress, true);\n            OnSetCropOverlayReleasedListener listener = mOnCropOverlayReleasedListener;\n            if (listener != null && !inProgress) {\n              listener.onCropOverlayReleased(getCropRect());\n            }\n            OnSetCropOverlayMovedListener movedListener = mOnSetCropOverlayMovedListener;\n            if (movedListener != null && inProgress) {\n              movedListener.onCropOverlayMoved(getCropRect());\n            }\n          }\n        });\n    mCropOverlayView.setInitialAttributeValues(options);\n\n    mProgressBar = v.findViewById(R.id.CropProgressBar);\n    setProgressBarVisibility();\n  }\n\n  /** Get the scale type of the image in the crop view. */\n  public ScaleType getScaleType() {\n    return mScaleType;\n  }\n\n  /** Set the scale type of the image in the crop view */\n  public void setScaleType(ScaleType scaleType) {\n    if (scaleType != mScaleType) {\n      mScaleType = scaleType;\n      mZoom = 1;\n      mZoomOffsetX = mZoomOffsetY = 0;\n      mCropOverlayView.resetCropOverlayView();\n      requestLayout();\n    }\n  }\n\n  /** The shape of the cropping area - rectangle/circular. */\n  public CropShape getCropShape() {\n    return mCropOverlayView.getCropShape();\n  }\n\n  /**\n   * The shape of the cropping area - rectangle/circular.<br>\n   * To set square/circle crop shape set aspect ratio to 1:1.\n   */\n  public void setCropShape(CropShape cropShape) {\n    mCropOverlayView.setCropShape(cropShape);\n  }\n\n  /** if auto-zoom functionality is enabled. default: true. */\n  public boolean isAutoZoomEnabled() {\n    return mAutoZoomEnabled;\n  }\n\n  /** Set auto-zoom functionality to enabled/disabled. */\n  public void setAutoZoomEnabled(boolean autoZoomEnabled) {\n    if (mAutoZoomEnabled != autoZoomEnabled) {\n      mAutoZoomEnabled = autoZoomEnabled;\n      handleCropWindowChanged(false, false);\n      mCropOverlayView.invalidate();\n    }\n  }\n\n  /** Set multi touch functionality to enabled/disabled. */\n  public void setMultiTouchEnabled(boolean multiTouchEnabled) {\n    if (mCropOverlayView.setMultiTouchEnabled(multiTouchEnabled)) {\n      handleCropWindowChanged(false, false);\n      mCropOverlayView.invalidate();\n    }\n  }\n\n  /** The max zoom allowed during cropping. */\n  public int getMaxZoom() {\n    return mMaxZoom;\n  }\n\n  /** The max zoom allowed during cropping. */\n  public void setMaxZoom(int maxZoom) {\n    if (mMaxZoom != maxZoom && maxZoom > 0) {\n      mMaxZoom = maxZoom;\n      handleCropWindowChanged(false, false);\n      mCropOverlayView.invalidate();\n    }\n  }\n\n  /**\n   * the min size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) {\n    mCropOverlayView.setMinCropResultSize(minCropResultWidth, minCropResultHeight);\n  }\n\n  /**\n   * the max size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {\n    mCropOverlayView.setMaxCropResultSize(maxCropResultWidth, maxCropResultHeight);\n  }\n\n  /**\n   * Get the amount of degrees the cropping image is rotated cloackwise.<br>\n   *\n   * @return 0-360\n   */\n  public int getRotatedDegrees() {\n    return mDegreesRotated;\n  }\n\n  /**\n   * Set the amount of degrees the cropping image is rotated cloackwise.<br>\n   *\n   * @param degrees 0-360\n   */\n  public void setRotatedDegrees(int degrees) {\n    if (mDegreesRotated != degrees) {\n      rotateImage(degrees - mDegreesRotated);\n    }\n  }\n\n  /**\n   * whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to\n   * be changed.\n   */\n  public boolean isFixAspectRatio() {\n    return mCropOverlayView.isFixAspectRatio();\n  }\n\n  /**\n   * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows\n   * it to be changed.\n   */\n  public void setFixedAspectRatio(boolean fixAspectRatio) {\n    mCropOverlayView.setFixedAspectRatio(fixAspectRatio);\n  }\n\n  /** whether the image should be flipped horizontally */\n  public boolean isFlippedHorizontally() {\n    return mFlipHorizontally;\n  }\n\n  /** Sets whether the image should be flipped horizontally */\n  public void setFlippedHorizontally(boolean flipHorizontally) {\n    if (mFlipHorizontally != flipHorizontally) {\n      mFlipHorizontally = flipHorizontally;\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n    }\n  }\n\n  /** whether the image should be flipped vertically */\n  public boolean isFlippedVertically() {\n    return mFlipVertically;\n  }\n\n  /** Sets whether the image should be flipped vertically */\n  public void setFlippedVertically(boolean flipVertically) {\n    if (mFlipVertically != flipVertically) {\n      mFlipVertically = flipVertically;\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n    }\n  }\n\n  /** Get the current guidelines option set. */\n  public Guidelines getGuidelines() {\n    return mCropOverlayView.getGuidelines();\n  }\n\n  /**\n   * Sets the guidelines for the CropOverlayView to be either on, off, or to show when resizing the\n   * application.\n   */\n  public void setGuidelines(Guidelines guidelines) {\n    mCropOverlayView.setGuidelines(guidelines);\n  }\n\n  /** both the X and Y values of the aspectRatio. */\n  public Pair<Integer, Integer> getAspectRatio() {\n    return new Pair<>(mCropOverlayView.getAspectRatioX(), mCropOverlayView.getAspectRatioY());\n  }\n\n  /**\n   * Sets the both the X and Y values of the aspectRatio.<br>\n   * Sets fixed aspect ratio to TRUE.\n   *\n   * @param aspectRatioX int that specifies the new X value of the aspect ratio\n   * @param aspectRatioY int that specifies the new Y value of the aspect ratio\n   */\n  public void setAspectRatio(int aspectRatioX, int aspectRatioY) {\n    mCropOverlayView.setAspectRatioX(aspectRatioX);\n    mCropOverlayView.setAspectRatioY(aspectRatioY);\n    setFixedAspectRatio(true);\n  }\n\n  /** Clears set aspect ratio values and sets fixed aspect ratio to FALSE. */\n  public void clearAspectRatio() {\n    mCropOverlayView.setAspectRatioX(1);\n    mCropOverlayView.setAspectRatioY(1);\n    setFixedAspectRatio(false);\n  }\n\n  /**\n   * An edge of the crop window will snap to the corresponding edge of a specified bounding box when\n   * the crop window edge is less than or equal to this distance (in pixels) away from the bounding\n   * box edge. (default: 3dp)\n   */\n  public void setSnapRadius(float snapRadius) {\n    if (snapRadius >= 0) {\n      mCropOverlayView.setSnapRadius(snapRadius);\n    }\n  }\n\n  /**\n   * if to show progress bar when image async loading/cropping is in progress.<br>\n   * default: true, disable to provide custom progress bar UI.\n   */\n  public boolean isShowProgressBar() {\n    return mShowProgressBar;\n  }\n\n  /**\n   * if to show progress bar when image async loading/cropping is in progress.<br>\n   * default: true, disable to provide custom progress bar UI.\n   */\n  public void setShowProgressBar(boolean showProgressBar) {\n    if (mShowProgressBar != showProgressBar) {\n      mShowProgressBar = showProgressBar;\n      setProgressBarVisibility();\n    }\n  }\n\n  /**\n   * if to show crop overlay UI what contains the crop window UI surrounded by background over the\n   * cropping image.<br>\n   * default: true, may disable for animation or frame transition.\n   */\n  public boolean isShowCropOverlay() {\n    return mShowCropOverlay;\n  }\n\n  /**\n   * if to show crop overlay UI what contains the crop window UI surrounded by background over the\n   * cropping image.<br>\n   * default: true, may disable for animation or frame transition.\n   */\n  public void setShowCropOverlay(boolean showCropOverlay) {\n    if (mShowCropOverlay != showCropOverlay) {\n      mShowCropOverlay = showCropOverlay;\n      setCropOverlayVisibility();\n    }\n  }\n\n  /**\n   * if to save bitmap on save instance state.<br>\n   * It is best to avoid it by using URI in setting image for cropping.<br>\n   * If false the bitmap is not saved and if restore is required to view will be empty, storing the\n   * bitmap requires saving it to file which can be expensive. default: false.\n   */\n  public boolean isSaveBitmapToInstanceState() {\n    return mSaveBitmapToInstanceState;\n  }\n\n  /**\n   * if to save bitmap on save instance state.<br>\n   * It is best to avoid it by using URI in setting image for cropping.<br>\n   * If false the bitmap is not saved and if restore is required to view will be empty, storing the\n   * bitmap requires saving it to file which can be expensive. default: false.\n   */\n  public void setSaveBitmapToInstanceState(boolean saveBitmapToInstanceState) {\n    mSaveBitmapToInstanceState = saveBitmapToInstanceState;\n  }\n\n  /** Returns the integer of the imageResource */\n  public int getImageResource() {\n    return mImageResource;\n  }\n\n  /** Get the URI of an image that was set by URI, null otherwise. */\n  public Uri getImageUri() {\n    return mLoadedImageUri;\n  }\n\n  /**\n   * Gets the source Bitmap's dimensions. This represents the largest possible crop rectangle.\n   *\n   * @return a Rect instance dimensions of the source Bitmap\n   */\n  public Rect getWholeImageRect() {\n    int loadedSampleSize = mLoadedSampleSize;\n    Bitmap bitmap = mBitmap;\n    if (bitmap == null) {\n      return null;\n    }\n\n    int orgWidth = bitmap.getWidth() * loadedSampleSize;\n    int orgHeight = bitmap.getHeight() * loadedSampleSize;\n    return new Rect(0, 0, orgWidth, orgHeight);\n  }\n\n  /**\n   * Gets the crop window's position relative to the source Bitmap (not the image displayed in the\n   * CropImageView) using the original image rotation.\n   *\n   * @return a Rect instance containing cropped area boundaries of the source Bitmap\n   */\n  public Rect getCropRect() {\n    int loadedSampleSize = mLoadedSampleSize;\n    Bitmap bitmap = mBitmap;\n    if (bitmap == null) {\n      return null;\n    }\n\n    // get the points of the crop rectangle adjusted to source bitmap\n    float[] points = getCropPoints();\n\n    int orgWidth = bitmap.getWidth() * loadedSampleSize;\n    int orgHeight = bitmap.getHeight() * loadedSampleSize;\n\n    // get the rectangle for the points (it may be larger than original if rotation is not stright)\n    return BitmapUtils.getRectFromPoints(\n        points,\n        orgWidth,\n        orgHeight,\n        mCropOverlayView.isFixAspectRatio(),\n        mCropOverlayView.getAspectRatioX(),\n        mCropOverlayView.getAspectRatioY());\n  }\n\n  /**\n   * Gets the crop window's position relative to the parent's view at screen.\n   *\n   * @return a Rect instance containing cropped area boundaries of the source Bitmap\n   */\n  public RectF getCropWindowRect() {\n    if (mCropOverlayView == null) {\n      return null;\n    }\n    return mCropOverlayView.getCropWindowRect();\n  }\n\n  /**\n   * Gets the 4 points of crop window's position relative to the source Bitmap (not the image\n   * displayed in the CropImageView) using the original image rotation.<br>\n   * Note: the 4 points may not be a rectangle if the image was rotates to NOT stright angle (!=\n   * 90/180/270).\n   *\n   * @return 4 points (x0,y0,x1,y1,x2,y2,x3,y3) of cropped area boundaries\n   */\n  public float[] getCropPoints() {\n\n    // Get crop window position relative to the displayed image.\n    RectF cropWindowRect = mCropOverlayView.getCropWindowRect();\n\n    float[] points =\n        new float[] {\n          cropWindowRect.left,\n          cropWindowRect.top,\n          cropWindowRect.right,\n          cropWindowRect.top,\n          cropWindowRect.right,\n          cropWindowRect.bottom,\n          cropWindowRect.left,\n          cropWindowRect.bottom\n        };\n\n    mImageMatrix.invert(mImageInverseMatrix);\n    mImageInverseMatrix.mapPoints(points);\n\n    for (int i = 0; i < points.length; i++) {\n      points[i] *= mLoadedSampleSize;\n    }\n\n    return points;\n  }\n\n  /**\n   * Set the crop window position and size to the given rectangle.<br>\n   * Image to crop must be first set before invoking this, for async - after complete callback.\n   *\n   * @param rect window rectangle (position and size) relative to source bitmap\n   */\n  public void setCropRect(Rect rect) {\n    mCropOverlayView.setInitialCropWindowRect(rect);\n  }\n\n  /** Reset crop window to initial rectangle. */\n  public void resetCropRect() {\n    mZoom = 1;\n    mZoomOffsetX = 0;\n    mZoomOffsetY = 0;\n    mDegreesRotated = mInitialDegreesRotated;\n    mFlipHorizontally = false;\n    mFlipVertically = false;\n    applyImageMatrix(getWidth(), getHeight(), false, false);\n    mCropOverlayView.resetCropWindowRect();\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.\n   *\n   * @return a new Bitmap representing the cropped image\n   */\n  public Bitmap getCroppedImage() {\n    return getCroppedImage(0, 0, RequestSizeOptions.NONE);\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.\n   *\n   * @param reqWidth the width to resize the cropped image to\n   * @param reqHeight the height to resize the cropped image to\n   * @return a new Bitmap representing the cropped image\n   */\n  public Bitmap getCroppedImage(int reqWidth, int reqHeight) {\n    return getCroppedImage(reqWidth, reqHeight, RequestSizeOptions.RESIZE_INSIDE);\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   *\n   * @param reqWidth the width to resize the cropped image to (see options)\n   * @param reqHeight the height to resize the cropped image to (see options)\n   * @param options the resize method to use, see its documentation\n   * @return a new Bitmap representing the cropped image\n   */\n  public Bitmap getCroppedImage(int reqWidth, int reqHeight, RequestSizeOptions options) {\n    Bitmap croppedBitmap = null;\n    if (mBitmap != null) {\n      mImageView.clearAnimation();\n\n      reqWidth = options != RequestSizeOptions.NONE ? reqWidth : 0;\n      reqHeight = options != RequestSizeOptions.NONE ? reqHeight : 0;\n\n      if (mLoadedImageUri != null\n          && (mLoadedSampleSize > 1 || options == RequestSizeOptions.SAMPLING)) {\n        int orgWidth = mBitmap.getWidth() * mLoadedSampleSize;\n        int orgHeight = mBitmap.getHeight() * mLoadedSampleSize;\n        BitmapUtils.BitmapSampled bitmapSampled =\n            BitmapUtils.cropBitmap(\n                getContext(),\n                mLoadedImageUri,\n                getCropPoints(),\n                mDegreesRotated,\n                orgWidth,\n                orgHeight,\n                mCropOverlayView.isFixAspectRatio(),\n                mCropOverlayView.getAspectRatioX(),\n                mCropOverlayView.getAspectRatioY(),\n                reqWidth,\n                reqHeight,\n                mFlipHorizontally,\n                mFlipVertically);\n        croppedBitmap = bitmapSampled.bitmap;\n      } else {\n        croppedBitmap =\n            BitmapUtils.cropBitmapObjectHandleOOM(\n                    mBitmap,\n                    getCropPoints(),\n                    mDegreesRotated,\n                    mCropOverlayView.isFixAspectRatio(),\n                    mCropOverlayView.getAspectRatioX(),\n                    mCropOverlayView.getAspectRatioY(),\n                    mFlipHorizontally,\n                    mFlipVertically)\n                .bitmap;\n      }\n\n      croppedBitmap = BitmapUtils.resizeBitmap(croppedBitmap, reqWidth, reqHeight, options);\n    }\n\n    return croppedBitmap;\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   */\n  public void getCroppedImageAsync() {\n    getCroppedImageAsync(0, 0, RequestSizeOptions.NONE);\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param reqWidth the width to resize the cropped image to\n   * @param reqHeight the height to resize the cropped image to\n   */\n  public void getCroppedImageAsync(int reqWidth, int reqHeight) {\n    getCroppedImageAsync(reqWidth, reqHeight, RequestSizeOptions.RESIZE_INSIDE);\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param reqWidth the width to resize the cropped image to (see options)\n   * @param reqHeight the height to resize the cropped image to (see options)\n   * @param options the resize method to use, see its documentation\n   */\n  public void getCroppedImageAsync(int reqWidth, int reqHeight, RequestSizeOptions options) {\n    if (mOnCropImageCompleteListener == null) {\n      throw new IllegalArgumentException(\"mOnCropImageCompleteListener is not set\");\n    }\n    startCropWorkerTask(reqWidth, reqHeight, options, null, null, 0);\n  }\n\n  /**\n   * Save the cropped image based on the current crop window to the given uri.<br>\n   * Uses JPEG image compression with 90 compression quality.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param saveUri the Android Uri to save the cropped image to\n   */\n  public void saveCroppedImageAsync(Uri saveUri) {\n    saveCroppedImageAsync(saveUri, Bitmap.CompressFormat.JPEG, 90, 0, 0, RequestSizeOptions.NONE);\n  }\n\n  /**\n   * Save the cropped image based on the current crop window to the given uri.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param saveUri the Android Uri to save the cropped image to\n   * @param saveCompressFormat the compression format to use when writing the image\n   * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100)\n   */\n  public void saveCroppedImageAsync(\n      Uri saveUri, Bitmap.CompressFormat saveCompressFormat, int saveCompressQuality) {\n    saveCroppedImageAsync(\n        saveUri, saveCompressFormat, saveCompressQuality, 0, 0, RequestSizeOptions.NONE);\n  }\n\n  /**\n   * Save the cropped image based on the current crop window to the given uri.<br>\n   * Uses {@link RequestSizeOptions#RESIZE_INSIDE} option.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param saveUri the Android Uri to save the cropped image to\n   * @param saveCompressFormat the compression format to use when writing the image\n   * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100)\n   * @param reqWidth the width to resize the cropped image to\n   * @param reqHeight the height to resize the cropped image to\n   */\n  public void saveCroppedImageAsync(\n      Uri saveUri,\n      Bitmap.CompressFormat saveCompressFormat,\n      int saveCompressQuality,\n      int reqWidth,\n      int reqHeight) {\n    saveCroppedImageAsync(\n        saveUri,\n        saveCompressFormat,\n        saveCompressQuality,\n        reqWidth,\n        reqHeight,\n        RequestSizeOptions.RESIZE_INSIDE);\n  }\n\n  /**\n   * Save the cropped image based on the current crop window to the given uri.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param saveUri the Android Uri to save the cropped image to\n   * @param saveCompressFormat the compression format to use when writing the image\n   * @param saveCompressQuality the quality (if applicable) to use when writing the image (0 - 100)\n   * @param reqWidth the width to resize the cropped image to (see options)\n   * @param reqHeight the height to resize the cropped image to (see options)\n   * @param options the resize method to use, see its documentation\n   */\n  public void saveCroppedImageAsync(\n      Uri saveUri,\n      Bitmap.CompressFormat saveCompressFormat,\n      int saveCompressQuality,\n      int reqWidth,\n      int reqHeight,\n      RequestSizeOptions options) {\n    if (mOnCropImageCompleteListener == null) {\n      throw new IllegalArgumentException(\"mOnCropImageCompleteListener is not set\");\n    }\n    startCropWorkerTask(\n        reqWidth, reqHeight, options, saveUri, saveCompressFormat, saveCompressQuality);\n  }\n\n  /** Set the callback t */\n  public void setOnSetCropOverlayReleasedListener(OnSetCropOverlayReleasedListener listener) {\n    mOnCropOverlayReleasedListener = listener;\n  }\n\n  /** Set the callback when the cropping is moved */\n  public void setOnSetCropOverlayMovedListener(OnSetCropOverlayMovedListener listener) {\n    mOnSetCropOverlayMovedListener = listener;\n  }\n\n  /** Set the callback when the crop window is changed */\n  public void setOnCropWindowChangedListener(OnSetCropWindowChangeListener listener) {\n    mOnSetCropWindowChangeListener = listener;\n  }\n\n  /**\n   * Set the callback to be invoked when image async loading ({@link #setImageUriAsync(Uri)}) is\n   * complete (successful or failed).\n   */\n  public void setOnSetImageUriCompleteListener(OnSetImageUriCompleteListener listener) {\n    mOnSetImageUriCompleteListener = listener;\n  }\n\n  /**\n   * Set the callback to be invoked when image async cropping image ({@link #getCroppedImageAsync()}\n   * or {@link #saveCroppedImageAsync(Uri)}) is complete (successful or failed).\n   */\n  public void setOnCropImageCompleteListener(OnCropImageCompleteListener listener) {\n    mOnCropImageCompleteListener = listener;\n  }\n\n  /**\n   * Sets a Bitmap as the content of the CropImageView.\n   *\n   * @param bitmap the Bitmap to set\n   */\n  public void setImageBitmap(Bitmap bitmap) {\n    mCropOverlayView.setInitialCropWindowRect(null);\n    setBitmap(bitmap, 0, null, 1, 0);\n  }\n\n  /**\n   * Sets a Bitmap and initializes the image rotation according to the EXIT data.<br>\n   * <br>\n   * The EXIF can be retrieved by doing the following: <code>\n   * ExifInterface exif = new ExifInterface(path);</code>\n   *\n   * @param bitmap the original bitmap to set; if null, this\n   * @param exif the EXIF information about this bitmap; may be null\n   */\n  public void setImageBitmap(Bitmap bitmap, ExifInterface exif) {\n    Bitmap setBitmap;\n    int degreesRotated = 0;\n    if (bitmap != null && exif != null) {\n      BitmapUtils.RotateBitmapResult result = BitmapUtils.rotateBitmapByExif(bitmap, exif);\n      setBitmap = result.bitmap;\n      degreesRotated = result.degrees;\n      mInitialDegreesRotated = result.degrees;\n    } else {\n      setBitmap = bitmap;\n    }\n    mCropOverlayView.setInitialCropWindowRect(null);\n    setBitmap(setBitmap, 0, null, 1, degreesRotated);\n  }\n\n  /**\n   * Sets a Drawable as the content of the CropImageView.\n   *\n   * @param resId the drawable resource ID to set\n   */\n  public void setImageResource(int resId) {\n    if (resId != 0) {\n      mCropOverlayView.setInitialCropWindowRect(null);\n      Bitmap bitmap = BitmapFactory.decodeResource(getResources(), resId);\n      setBitmap(bitmap, resId, null, 1, 0);\n    }\n  }\n\n  /**\n   * Sets a bitmap loaded from the given Android URI as the content of the CropImageView.<br>\n   * Can be used with URI from gallery or camera source.<br>\n   * Will rotate the image by exif data.<br>\n   *\n   * @param uri the URI to load the image from\n   */\n  public void setImageUriAsync(Uri uri) {\n    if (uri != null) {\n      BitmapLoadingWorkerTask currentTask =\n          mBitmapLoadingWorkerTask != null ? mBitmapLoadingWorkerTask.get() : null;\n      if (currentTask != null) {\n        // cancel previous loading (no check if the same URI because camera URI can be the same for\n        // different images)\n        currentTask.cancel(true);\n      }\n\n      // either no existing task is working or we canceled it, need to load new URI\n      clearImageInt();\n      mRestoreCropWindowRect = null;\n      mRestoreDegreesRotated = 0;\n      mCropOverlayView.setInitialCropWindowRect(null);\n      mBitmapLoadingWorkerTask = new WeakReference<>(new BitmapLoadingWorkerTask(this, uri));\n      mBitmapLoadingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\n      setProgressBarVisibility();\n    }\n  }\n\n  /** Clear the current image set for cropping. */\n  public void clearImage() {\n    clearImageInt();\n    mCropOverlayView.setInitialCropWindowRect(null);\n  }\n\n  /**\n   * Rotates image by the specified number of degrees clockwise.<br>\n   * Negative values represent counter-clockwise rotations.\n   *\n   * @param degrees Integer specifying the number of degrees to rotate.\n   */\n  public void rotateImage(int degrees) {\n    if (mBitmap != null) {\n      // Force degrees to be a non-zero value between 0 and 360 (inclusive)\n      if (degrees < 0) {\n        degrees = (degrees % 360) + 360;\n      } else {\n        degrees = degrees % 360;\n      }\n\n      boolean flipAxes =\n          !mCropOverlayView.isFixAspectRatio()\n              && ((degrees > 45 && degrees < 135) || (degrees > 215 && degrees < 305));\n      BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect());\n      float halfWidth = (flipAxes ? BitmapUtils.RECT.height() : BitmapUtils.RECT.width()) / 2f;\n      float halfHeight = (flipAxes ? BitmapUtils.RECT.width() : BitmapUtils.RECT.height()) / 2f;\n      if (flipAxes) {\n        boolean isFlippedHorizontally = mFlipHorizontally;\n        mFlipHorizontally = mFlipVertically;\n        mFlipVertically = isFlippedHorizontally;\n      }\n\n      mImageMatrix.invert(mImageInverseMatrix);\n\n      BitmapUtils.POINTS[0] = BitmapUtils.RECT.centerX();\n      BitmapUtils.POINTS[1] = BitmapUtils.RECT.centerY();\n      BitmapUtils.POINTS[2] = 0;\n      BitmapUtils.POINTS[3] = 0;\n      BitmapUtils.POINTS[4] = 1;\n      BitmapUtils.POINTS[5] = 0;\n      mImageInverseMatrix.mapPoints(BitmapUtils.POINTS);\n\n      // This is valid because degrees is not negative.\n      mDegreesRotated = (mDegreesRotated + degrees) % 360;\n\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n\n      // adjust the zoom so the crop window size remains the same even after image scale change\n      mImageMatrix.mapPoints(BitmapUtils.POINTS2, BitmapUtils.POINTS);\n      mZoom /=\n          Math.sqrt(\n              Math.pow(BitmapUtils.POINTS2[4] - BitmapUtils.POINTS2[2], 2)\n                  + Math.pow(BitmapUtils.POINTS2[5] - BitmapUtils.POINTS2[3], 2));\n      mZoom = Math.max(mZoom, 1);\n\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n\n      mImageMatrix.mapPoints(BitmapUtils.POINTS2, BitmapUtils.POINTS);\n\n      // adjust the width/height by the changes in scaling to the image\n      double change =\n          Math.sqrt(\n              Math.pow(BitmapUtils.POINTS2[4] - BitmapUtils.POINTS2[2], 2)\n                  + Math.pow(BitmapUtils.POINTS2[5] - BitmapUtils.POINTS2[3], 2));\n      halfWidth *= change;\n      halfHeight *= change;\n\n      // calculate the new crop window rectangle to center in the same location and have proper\n      // width/height\n      BitmapUtils.RECT.set(\n          BitmapUtils.POINTS2[0] - halfWidth,\n          BitmapUtils.POINTS2[1] - halfHeight,\n          BitmapUtils.POINTS2[0] + halfWidth,\n          BitmapUtils.POINTS2[1] + halfHeight);\n\n      mCropOverlayView.resetCropOverlayView();\n      mCropOverlayView.setCropWindowRect(BitmapUtils.RECT);\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n      handleCropWindowChanged(false, false);\n\n      // make sure the crop window rectangle is within the cropping image bounds after all the\n      // changes\n      mCropOverlayView.fixCurrentCropWindowRect();\n    }\n  }\n\n  /** Flips the image horizontally. */\n  public void flipImageHorizontally() {\n    mFlipHorizontally = !mFlipHorizontally;\n    applyImageMatrix(getWidth(), getHeight(), true, false);\n  }\n\n  /** Flips the image vertically. */\n  public void flipImageVertically() {\n    mFlipVertically = !mFlipVertically;\n    applyImageMatrix(getWidth(), getHeight(), true, false);\n  }\n\n  // region: Private methods\n\n  /**\n   * On complete of the async bitmap loading by {@link #setImageUriAsync(Uri)} set the result to the\n   * widget if still relevant and call listener if set.\n   *\n   * @param result the result of bitmap loading\n   */\n  void onSetImageUriAsyncComplete(BitmapLoadingWorkerTask.Result result) {\n\n    mBitmapLoadingWorkerTask = null;\n    setProgressBarVisibility();\n\n    if (result.error == null) {\n      mInitialDegreesRotated = result.degreesRotated;\n      setBitmap(result.bitmap, 0, result.uri, result.loadSampleSize, result.degreesRotated);\n    }\n\n    OnSetImageUriCompleteListener listener = mOnSetImageUriCompleteListener;\n    if (listener != null) {\n      listener.onSetImageUriComplete(this, result.uri, result.error);\n    }\n  }\n\n  /**\n   * On complete of the async bitmap cropping by {@link #getCroppedImageAsync()} call listener if\n   * set.\n   *\n   * @param result the result of bitmap cropping\n   */\n  void onImageCroppingAsyncComplete(BitmapCroppingWorkerTask.Result result) {\n\n    mBitmapCroppingWorkerTask = null;\n    setProgressBarVisibility();\n\n    OnCropImageCompleteListener listener = mOnCropImageCompleteListener;\n    if (listener != null) {\n      CropResult cropResult =\n          new CropResult(\n              mBitmap,\n              mLoadedImageUri,\n              result.bitmap,\n              result.uri,\n              result.error,\n              getCropPoints(),\n              getCropRect(),\n              getWholeImageRect(),\n              getRotatedDegrees(),\n              result.sampleSize);\n      listener.onCropImageComplete(this, cropResult);\n    }\n  }\n\n  /**\n   * Set the given bitmap to be used in for cropping<br>\n   * Optionally clear full if the bitmap is new, or partial clear if the bitmap has been\n   * manipulated.\n   */\n  private void setBitmap(\n      Bitmap bitmap, int imageResource, Uri imageUri, int loadSampleSize, int degreesRotated) {\n    if (mBitmap == null || !mBitmap.equals(bitmap)) {\n\n      mImageView.clearAnimation();\n\n      clearImageInt();\n\n      mBitmap = bitmap;\n      mImageView.setImageBitmap(mBitmap);\n\n      mLoadedImageUri = imageUri;\n      mImageResource = imageResource;\n      mLoadedSampleSize = loadSampleSize;\n      mDegreesRotated = degreesRotated;\n\n      applyImageMatrix(getWidth(), getHeight(), true, false);\n\n      if (mCropOverlayView != null) {\n        mCropOverlayView.resetCropOverlayView();\n        setCropOverlayVisibility();\n      }\n    }\n  }\n\n  /**\n   * Clear the current image set for cropping.<br>\n   * Full clear will also clear the data of the set image like Uri or Resource id while partial\n   * clear will only clear the bitmap and recycle if required.\n   */\n  private void clearImageInt() {\n\n    // if we allocated the bitmap, release it as fast as possible\n    if (mBitmap != null && (mImageResource > 0 || mLoadedImageUri != null)) {\n      mBitmap.recycle();\n    }\n    mBitmap = null;\n\n    // clean the loaded image flags for new image\n    mImageResource = 0;\n    mLoadedImageUri = null;\n    mLoadedSampleSize = 1;\n    mDegreesRotated = 0;\n    mZoom = 1;\n    mZoomOffsetX = 0;\n    mZoomOffsetY = 0;\n    mImageMatrix.reset();\n    mSaveInstanceStateBitmapUri = null;\n\n    mImageView.setImageBitmap(null);\n\n    setCropOverlayVisibility();\n  }\n\n  /**\n   * Gets the cropped image based on the current crop window.<br>\n   * If (reqWidth,reqHeight) is given AND image is loaded from URI cropping will try to use sample\n   * size to fit in the requested width and height down-sampling if possible - optimization to get\n   * best size to quality.<br>\n   * The result will be invoked to listener set by {@link\n   * #setOnCropImageCompleteListener(OnCropImageCompleteListener)}.\n   *\n   * @param reqWidth the width to resize the cropped image to (see options)\n   * @param reqHeight the height to resize the cropped image to (see options)\n   * @param options the resize method to use on the cropped bitmap\n   * @param saveUri optional: to save the cropped image to\n   * @param saveCompressFormat if saveUri is given, the given compression will be used for saving\n   *     the image\n   * @param saveCompressQuality if saveUri is given, the given quality will be used for the\n   *     compression.\n   */\n  public void startCropWorkerTask(\n      int reqWidth,\n      int reqHeight,\n      RequestSizeOptions options,\n      Uri saveUri,\n      Bitmap.CompressFormat saveCompressFormat,\n      int saveCompressQuality) {\n    Bitmap bitmap = mBitmap;\n    if (bitmap != null) {\n      mImageView.clearAnimation();\n\n      BitmapCroppingWorkerTask currentTask =\n          mBitmapCroppingWorkerTask != null ? mBitmapCroppingWorkerTask.get() : null;\n      if (currentTask != null) {\n        // cancel previous cropping\n        currentTask.cancel(true);\n      }\n\n      reqWidth = options != RequestSizeOptions.NONE ? reqWidth : 0;\n      reqHeight = options != RequestSizeOptions.NONE ? reqHeight : 0;\n\n      int orgWidth = bitmap.getWidth() * mLoadedSampleSize;\n      int orgHeight = bitmap.getHeight() * mLoadedSampleSize;\n      if (mLoadedImageUri != null\n          && (mLoadedSampleSize > 1 || options == RequestSizeOptions.SAMPLING)) {\n        mBitmapCroppingWorkerTask =\n            new WeakReference<>(\n                new BitmapCroppingWorkerTask(\n                    this,\n                    mLoadedImageUri,\n                    getCropPoints(),\n                    mDegreesRotated,\n                    orgWidth,\n                    orgHeight,\n                    mCropOverlayView.isFixAspectRatio(),\n                    mCropOverlayView.getAspectRatioX(),\n                    mCropOverlayView.getAspectRatioY(),\n                    reqWidth,\n                    reqHeight,\n                    mFlipHorizontally,\n                    mFlipVertically,\n                    options,\n                    saveUri,\n                    saveCompressFormat,\n                    saveCompressQuality));\n      } else {\n        mBitmapCroppingWorkerTask =\n            new WeakReference<>(\n                new BitmapCroppingWorkerTask(\n                    this,\n                    bitmap,\n                    getCropPoints(),\n                    mDegreesRotated,\n                    mCropOverlayView.isFixAspectRatio(),\n                    mCropOverlayView.getAspectRatioX(),\n                    mCropOverlayView.getAspectRatioY(),\n                    reqWidth,\n                    reqHeight,\n                    mFlipHorizontally,\n                    mFlipVertically,\n                    options,\n                    saveUri,\n                    saveCompressFormat,\n                    saveCompressQuality));\n      }\n      mBitmapCroppingWorkerTask.get().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\n      setProgressBarVisibility();\n    }\n  }\n\n  @Override\n  public Parcelable onSaveInstanceState() {\n    if (mLoadedImageUri == null && mBitmap == null && mImageResource < 1) {\n      return super.onSaveInstanceState();\n    }\n\n    Bundle bundle = new Bundle();\n    Uri imageUri = mLoadedImageUri;\n    if (mSaveBitmapToInstanceState && imageUri == null && mImageResource < 1) {\n      mSaveInstanceStateBitmapUri =\n          imageUri =\n              BitmapUtils.writeTempStateStoreBitmap(\n                  getContext(), mBitmap, mSaveInstanceStateBitmapUri);\n    }\n    if (imageUri != null && mBitmap != null) {\n      String key = UUID.randomUUID().toString();\n      BitmapUtils.mStateBitmap = new Pair<>(key, new WeakReference<>(mBitmap));\n      bundle.putString(\"LOADED_IMAGE_STATE_BITMAP_KEY\", key);\n    }\n    if (mBitmapLoadingWorkerTask != null) {\n      BitmapLoadingWorkerTask task = mBitmapLoadingWorkerTask.get();\n      if (task != null) {\n        bundle.putParcelable(\"LOADING_IMAGE_URI\", task.getUri());\n      }\n    }\n    bundle.putParcelable(\"instanceState\", super.onSaveInstanceState());\n    bundle.putParcelable(\"LOADED_IMAGE_URI\", imageUri);\n    bundle.putInt(\"LOADED_IMAGE_RESOURCE\", mImageResource);\n    bundle.putInt(\"LOADED_SAMPLE_SIZE\", mLoadedSampleSize);\n    bundle.putInt(\"DEGREES_ROTATED\", mDegreesRotated);\n    bundle.putParcelable(\"INITIAL_CROP_RECT\", mCropOverlayView.getInitialCropWindowRect());\n\n    BitmapUtils.RECT.set(mCropOverlayView.getCropWindowRect());\n\n    mImageMatrix.invert(mImageInverseMatrix);\n    mImageInverseMatrix.mapRect(BitmapUtils.RECT);\n\n    bundle.putParcelable(\"CROP_WINDOW_RECT\", BitmapUtils.RECT);\n    bundle.putString(\"CROP_SHAPE\", mCropOverlayView.getCropShape().name());\n    bundle.putBoolean(\"CROP_AUTO_ZOOM_ENABLED\", mAutoZoomEnabled);\n    bundle.putInt(\"CROP_MAX_ZOOM\", mMaxZoom);\n    bundle.putBoolean(\"CROP_FLIP_HORIZONTALLY\", mFlipHorizontally);\n    bundle.putBoolean(\"CROP_FLIP_VERTICALLY\", mFlipVertically);\n\n    return bundle;\n  }\n\n  @Override\n  public void onRestoreInstanceState(Parcelable state) {\n\n    if (state instanceof Bundle) {\n      Bundle bundle = (Bundle) state;\n\n      // prevent restoring state if already set by outside code\n      if (mBitmapLoadingWorkerTask == null\n          && mLoadedImageUri == null\n          && mBitmap == null\n          && mImageResource == 0) {\n\n        Uri uri = bundle.getParcelable(\"LOADED_IMAGE_URI\");\n        if (uri != null) {\n          String key = bundle.getString(\"LOADED_IMAGE_STATE_BITMAP_KEY\");\n          if (key != null) {\n            Bitmap stateBitmap =\n                BitmapUtils.mStateBitmap != null && BitmapUtils.mStateBitmap.first.equals(key)\n                    ? BitmapUtils.mStateBitmap.second.get()\n                    : null;\n            BitmapUtils.mStateBitmap = null;\n            if (stateBitmap != null && !stateBitmap.isRecycled()) {\n              setBitmap(stateBitmap, 0, uri, bundle.getInt(\"LOADED_SAMPLE_SIZE\"), 0);\n            }\n          }\n          if (mLoadedImageUri == null) {\n            setImageUriAsync(uri);\n          }\n        } else {\n          int resId = bundle.getInt(\"LOADED_IMAGE_RESOURCE\");\n          if (resId > 0) {\n            setImageResource(resId);\n          } else {\n            uri = bundle.getParcelable(\"LOADING_IMAGE_URI\");\n            if (uri != null) {\n              setImageUriAsync(uri);\n            }\n          }\n        }\n\n        mDegreesRotated = mRestoreDegreesRotated = bundle.getInt(\"DEGREES_ROTATED\");\n\n        Rect initialCropRect = bundle.getParcelable(\"INITIAL_CROP_RECT\");\n        if (initialCropRect != null\n            && (initialCropRect.width() > 0 || initialCropRect.height() > 0)) {\n          mCropOverlayView.setInitialCropWindowRect(initialCropRect);\n        }\n\n        RectF cropWindowRect = bundle.getParcelable(\"CROP_WINDOW_RECT\");\n        if (cropWindowRect != null && (cropWindowRect.width() > 0 || cropWindowRect.height() > 0)) {\n          mRestoreCropWindowRect = cropWindowRect;\n        }\n\n        mCropOverlayView.setCropShape(CropShape.valueOf(bundle.getString(\"CROP_SHAPE\")));\n\n        mAutoZoomEnabled = bundle.getBoolean(\"CROP_AUTO_ZOOM_ENABLED\");\n        mMaxZoom = bundle.getInt(\"CROP_MAX_ZOOM\");\n\n        mFlipHorizontally = bundle.getBoolean(\"CROP_FLIP_HORIZONTALLY\");\n        mFlipVertically = bundle.getBoolean(\"CROP_FLIP_VERTICALLY\");\n      }\n\n      super.onRestoreInstanceState(bundle.getParcelable(\"instanceState\"));\n    } else {\n      super.onRestoreInstanceState(state);\n    }\n  }\n\n  @Override\n  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n    super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n\n    int widthMode = MeasureSpec.getMode(widthMeasureSpec);\n    int widthSize = MeasureSpec.getSize(widthMeasureSpec);\n    int heightMode = MeasureSpec.getMode(heightMeasureSpec);\n    int heightSize = MeasureSpec.getSize(heightMeasureSpec);\n\n    if (mBitmap != null) {\n\n      // Bypasses a baffling bug when used within a ScrollView, where heightSize is set to 0.\n      if (heightSize == 0) {\n        heightSize = mBitmap.getHeight();\n      }\n\n      int desiredWidth;\n      int desiredHeight;\n\n      double viewToBitmapWidthRatio = Double.POSITIVE_INFINITY;\n      double viewToBitmapHeightRatio = Double.POSITIVE_INFINITY;\n\n      // Checks if either width or height needs to be fixed\n      if (widthSize < mBitmap.getWidth()) {\n        viewToBitmapWidthRatio = (double) widthSize / (double) mBitmap.getWidth();\n      }\n      if (heightSize < mBitmap.getHeight()) {\n        viewToBitmapHeightRatio = (double) heightSize / (double) mBitmap.getHeight();\n      }\n\n      // If either needs to be fixed, choose smallest ratio and calculate from there\n      if (viewToBitmapWidthRatio != Double.POSITIVE_INFINITY\n          || viewToBitmapHeightRatio != Double.POSITIVE_INFINITY) {\n        if (viewToBitmapWidthRatio <= viewToBitmapHeightRatio) {\n          desiredWidth = widthSize;\n          desiredHeight = (int) (mBitmap.getHeight() * viewToBitmapWidthRatio);\n        } else {\n          desiredHeight = heightSize;\n          desiredWidth = (int) (mBitmap.getWidth() * viewToBitmapHeightRatio);\n        }\n      } else {\n        // Otherwise, the picture is within frame layout bounds. Desired width is simply picture\n        // size\n        desiredWidth = mBitmap.getWidth();\n        desiredHeight = mBitmap.getHeight();\n      }\n\n      int width = getOnMeasureSpec(widthMode, widthSize, desiredWidth);\n      int height = getOnMeasureSpec(heightMode, heightSize, desiredHeight);\n\n      mLayoutWidth = width;\n      mLayoutHeight = height;\n\n      setMeasuredDimension(mLayoutWidth, mLayoutHeight);\n\n    } else {\n      setMeasuredDimension(widthSize, heightSize);\n    }\n  }\n\n  @Override\n  protected void onLayout(boolean changed, int l, int t, int r, int b) {\n\n    super.onLayout(changed, l, t, r, b);\n\n    if (mLayoutWidth > 0 && mLayoutHeight > 0) {\n      // Gets original parameters, and creates the new parameters\n      ViewGroup.LayoutParams origParams = this.getLayoutParams();\n      origParams.width = mLayoutWidth;\n      origParams.height = mLayoutHeight;\n      setLayoutParams(origParams);\n\n      if (mBitmap != null) {\n        applyImageMatrix(r - l, b - t, true, false);\n\n        // after state restore we want to restore the window crop, possible only after widget size\n        // is known\n        if (mRestoreCropWindowRect != null) {\n          if (mRestoreDegreesRotated != mInitialDegreesRotated) {\n            mDegreesRotated = mRestoreDegreesRotated;\n            applyImageMatrix(r - l, b - t, true, false);\n          }\n          mImageMatrix.mapRect(mRestoreCropWindowRect);\n          mCropOverlayView.setCropWindowRect(mRestoreCropWindowRect);\n          handleCropWindowChanged(false, false);\n          mCropOverlayView.fixCurrentCropWindowRect();\n          mRestoreCropWindowRect = null;\n        } else if (mSizeChanged) {\n          mSizeChanged = false;\n          handleCropWindowChanged(false, false);\n        }\n      } else {\n        updateImageBounds(true);\n      }\n    } else {\n      updateImageBounds(true);\n    }\n  }\n\n  /**\n   * Detect size change to handle auto-zoom using {@link #handleCropWindowChanged(boolean, boolean)}\n   * in {@link #layout(int, int, int, int)}.\n   */\n  @Override\n  protected void onSizeChanged(int w, int h, int oldw, int oldh) {\n    super.onSizeChanged(w, h, oldw, oldh);\n    mSizeChanged = oldw > 0 && oldh > 0;\n  }\n\n  /**\n   * Handle crop window change to:<br>\n   * 1. Execute auto-zoom-in/out depending on the area covered of cropping window relative to the\n   * available view area.<br>\n   * 2. Slide the zoomed sub-area if the cropping window is outside of the visible view sub-area.\n   * <br>\n   *\n   * @param inProgress is the crop window change is still in progress by the user\n   * @param animate if to animate the change to the image matrix, or set it directly\n   */\n  private void handleCropWindowChanged(boolean inProgress, boolean animate) {\n    int width = getWidth();\n    int height = getHeight();\n    if (mBitmap != null && width > 0 && height > 0) {\n\n      RectF cropRect = mCropOverlayView.getCropWindowRect();\n      if (inProgress) {\n        if (cropRect.left < 0\n            || cropRect.top < 0\n            || cropRect.right > width\n            || cropRect.bottom > height) {\n          applyImageMatrix(width, height, false, false);\n        }\n      } else if (mAutoZoomEnabled || mZoom > 1) {\n        float newZoom = 0;\n        // keep the cropping window covered area to 50%-65% of zoomed sub-area\n        if (mZoom < mMaxZoom\n            && cropRect.width() < width * 0.5f\n            && cropRect.height() < height * 0.5f) {\n          newZoom =\n              Math.min(\n                  mMaxZoom,\n                  Math.min(\n                      width / (cropRect.width() / mZoom / 0.64f),\n                      height / (cropRect.height() / mZoom / 0.64f)));\n        }\n        if (mZoom > 1 && (cropRect.width() > width * 0.65f || cropRect.height() > height * 0.65f)) {\n          newZoom =\n              Math.max(\n                  1,\n                  Math.min(\n                      width / (cropRect.width() / mZoom / 0.51f),\n                      height / (cropRect.height() / mZoom / 0.51f)));\n        }\n        if (!mAutoZoomEnabled) {\n          newZoom = 1;\n        }\n\n        if (newZoom > 0 && newZoom != mZoom) {\n          if (animate) {\n            if (mAnimation == null) {\n              // lazy create animation single instance\n              mAnimation = new CropImageAnimation(mImageView, mCropOverlayView);\n            }\n            // set the state for animation to start from\n            mAnimation.setStartState(mImagePoints, mImageMatrix);\n          }\n\n          mZoom = newZoom;\n\n          applyImageMatrix(width, height, true, animate);\n        }\n      }\n      if (mOnSetCropWindowChangeListener != null && !inProgress) {\n        mOnSetCropWindowChangeListener.onCropWindowChanged();\n      }\n    }\n  }\n\n  /**\n   * Apply matrix to handle the image inside the image view.\n   *\n   * @param width the width of the image view\n   * @param height the height of the image view\n   */\n  private void applyImageMatrix(float width, float height, boolean center, boolean animate) {\n    if (mBitmap != null && width > 0 && height > 0) {\n\n      mImageMatrix.invert(mImageInverseMatrix);\n      RectF cropRect = mCropOverlayView.getCropWindowRect();\n      mImageInverseMatrix.mapRect(cropRect);\n\n      mImageMatrix.reset();\n\n      // move the image to the center of the image view first so we can manipulate it from there\n      mImageMatrix.postTranslate(\n          (width - mBitmap.getWidth()) / 2, (height - mBitmap.getHeight()) / 2);\n      mapImagePointsByImageMatrix();\n\n      // rotate the image the required degrees from center of image\n      if (mDegreesRotated > 0) {\n        mImageMatrix.postRotate(\n            mDegreesRotated,\n            BitmapUtils.getRectCenterX(mImagePoints),\n            BitmapUtils.getRectCenterY(mImagePoints));\n        mapImagePointsByImageMatrix();\n      }\n\n      // scale the image to the image view, image rect transformed to know new width/height\n      float scale =\n          Math.min(\n              width / BitmapUtils.getRectWidth(mImagePoints),\n              height / BitmapUtils.getRectHeight(mImagePoints));\n      if (mScaleType == ScaleType.FIT_CENTER\n          || (mScaleType == ScaleType.CENTER_INSIDE && scale < 1)\n          || (scale > 1 && mAutoZoomEnabled)) {\n        mImageMatrix.postScale(\n            scale,\n            scale,\n            BitmapUtils.getRectCenterX(mImagePoints),\n            BitmapUtils.getRectCenterY(mImagePoints));\n        mapImagePointsByImageMatrix();\n      }\n\n      // scale by the current zoom level\n      float scaleX = mFlipHorizontally ? -mZoom : mZoom;\n      float scaleY = mFlipVertically ? -mZoom : mZoom;\n      mImageMatrix.postScale(\n          scaleX,\n          scaleY,\n          BitmapUtils.getRectCenterX(mImagePoints),\n          BitmapUtils.getRectCenterY(mImagePoints));\n      mapImagePointsByImageMatrix();\n\n      mImageMatrix.mapRect(cropRect);\n\n      if (center) {\n        // set the zoomed area to be as to the center of cropping window as possible\n        mZoomOffsetX =\n            width > BitmapUtils.getRectWidth(mImagePoints)\n                ? 0\n                : Math.max(\n                        Math.min(\n                            width / 2 - cropRect.centerX(), -BitmapUtils.getRectLeft(mImagePoints)),\n                        getWidth() - BitmapUtils.getRectRight(mImagePoints))\n                    / scaleX;\n        mZoomOffsetY =\n            height > BitmapUtils.getRectHeight(mImagePoints)\n                ? 0\n                : Math.max(\n                        Math.min(\n                            height / 2 - cropRect.centerY(), -BitmapUtils.getRectTop(mImagePoints)),\n                        getHeight() - BitmapUtils.getRectBottom(mImagePoints))\n                    / scaleY;\n      } else {\n        // adjust the zoomed area so the crop window rectangle will be inside the area in case it\n        // was moved outside\n        mZoomOffsetX =\n            Math.min(Math.max(mZoomOffsetX * scaleX, -cropRect.left), -cropRect.right + width)\n                / scaleX;\n        mZoomOffsetY =\n            Math.min(Math.max(mZoomOffsetY * scaleY, -cropRect.top), -cropRect.bottom + height)\n                / scaleY;\n      }\n\n      // apply to zoom offset translate and update the crop rectangle to offset correctly\n      mImageMatrix.postTranslate(mZoomOffsetX * scaleX, mZoomOffsetY * scaleY);\n      cropRect.offset(mZoomOffsetX * scaleX, mZoomOffsetY * scaleY);\n      mCropOverlayView.setCropWindowRect(cropRect);\n      mapImagePointsByImageMatrix();\n      mCropOverlayView.invalidate();\n\n      // set matrix to apply\n      if (animate) {\n        // set the state for animation to end in, start animation now\n        mAnimation.setEndState(mImagePoints, mImageMatrix);\n        mImageView.startAnimation(mAnimation);\n      } else {\n        mImageView.setImageMatrix(mImageMatrix);\n      }\n\n      // update the image rectangle in the crop overlay\n      updateImageBounds(false);\n    }\n  }\n\n  /**\n   * Adjust the given image rectangle by image transformation matrix to know the final rectangle of\n   * the image.<br>\n   * To get the proper rectangle it must be first reset to original image rectangle.\n   */\n  private void mapImagePointsByImageMatrix() {\n    mImagePoints[0] = 0;\n    mImagePoints[1] = 0;\n    mImagePoints[2] = mBitmap.getWidth();\n    mImagePoints[3] = 0;\n    mImagePoints[4] = mBitmap.getWidth();\n    mImagePoints[5] = mBitmap.getHeight();\n    mImagePoints[6] = 0;\n    mImagePoints[7] = mBitmap.getHeight();\n    mImageMatrix.mapPoints(mImagePoints);\n    mScaleImagePoints[0] = 0;\n    mScaleImagePoints[1] = 0;\n    mScaleImagePoints[2] = 100;\n    mScaleImagePoints[3] = 0;\n    mScaleImagePoints[4] = 100;\n    mScaleImagePoints[5] = 100;\n    mScaleImagePoints[6] = 0;\n    mScaleImagePoints[7] = 100;\n    mImageMatrix.mapPoints(mScaleImagePoints);\n  }\n\n  /**\n   * Determines the specs for the onMeasure function. Calculates the width or height depending on\n   * the mode.\n   *\n   * @param measureSpecMode The mode of the measured width or height.\n   * @param measureSpecSize The size of the measured width or height.\n   * @param desiredSize The desired size of the measured width or height.\n   * @return The final size of the width or height.\n   */\n  private static int getOnMeasureSpec(int measureSpecMode, int measureSpecSize, int desiredSize) {\n\n    // Measure Width\n    int spec;\n    if (measureSpecMode == MeasureSpec.EXACTLY) {\n      // Must be this size\n      spec = measureSpecSize;\n    } else if (measureSpecMode == MeasureSpec.AT_MOST) {\n      // Can't be bigger than...; match_parent value\n      spec = Math.min(desiredSize, measureSpecSize);\n    } else {\n      // Be whatever you want; wrap_content\n      spec = desiredSize;\n    }\n\n    return spec;\n  }\n\n  /**\n   * Set visibility of crop overlay to hide it when there is no image or specificly set by client.\n   */\n  private void setCropOverlayVisibility() {\n    if (mCropOverlayView != null) {\n      mCropOverlayView.setVisibility(mShowCropOverlay && mBitmap != null ? VISIBLE : INVISIBLE);\n    }\n  }\n\n  /**\n   * Set visibility of progress bar when async loading/cropping is in process and show is enabled.\n   */\n  private void setProgressBarVisibility() {\n    boolean visible =\n        mShowProgressBar\n            && (mBitmap == null && mBitmapLoadingWorkerTask != null\n                || mBitmapCroppingWorkerTask != null);\n    mProgressBar.setVisibility(visible ? VISIBLE : INVISIBLE);\n  }\n\n  /** Update the scale factor between the actual image bitmap and the shown image.<br> */\n  private void updateImageBounds(boolean clear) {\n    if (mBitmap != null && !clear) {\n\n      // Get the scale factor between the actual Bitmap dimensions and the displayed dimensions for\n      // width/height.\n      float scaleFactorWidth =\n          100f * mLoadedSampleSize / BitmapUtils.getRectWidth(mScaleImagePoints);\n      float scaleFactorHeight =\n          100f * mLoadedSampleSize / BitmapUtils.getRectHeight(mScaleImagePoints);\n      mCropOverlayView.setCropWindowLimits(\n          getWidth(), getHeight(), scaleFactorWidth, scaleFactorHeight);\n    }\n\n    // set the bitmap rectangle and update the crop window after scale factor is set\n    mCropOverlayView.setBounds(clear ? null : mImagePoints, getWidth(), getHeight());\n  }\n  // endregion\n\n  // region: Inner class: CropShape\n\n  /**\n   * The possible cropping area shape.<br>\n   * To set square/circle crop shape set aspect ratio to 1:1.\n   */\n  public enum CropShape {\n    RECTANGLE,\n    OVAL\n  }\n  // endregion\n\n  // region: Inner class: ScaleType\n\n  /**\n   * Options for scaling the bounds of cropping image to the bounds of Crop Image View.<br>\n   * Note: Some options are affected by auto-zoom, if enabled.\n   */\n  public enum ScaleType {\n\n    /**\n     * Scale the image uniformly (maintain the image's aspect ratio) to fit in crop image view.<br>\n     * The largest dimension will be equals to crop image view and the second dimension will be\n     * smaller.\n     */\n    FIT_CENTER,\n\n    /**\n     * Center the image in the view, but perform no scaling.<br>\n     * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it\n     * will be scaled uniformly to fit the crop image view.\n     */\n    CENTER,\n\n    /**\n     * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width\n     * and height) of the image will be equal to or <b>larger</b> than the corresponding dimension\n     * of the view (minus padding).<br>\n     * The image is then centered in the view.\n     */\n    CENTER_CROP,\n\n    /**\n     * Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width\n     * and height) of the image will be equal to or <b>less</b> than the corresponding dimension of\n     * the view (minus padding).<br>\n     * The image is then centered in the view.<br>\n     * Note: If auto-zoom is enabled and the source image is smaller than crop image view then it\n     * will be scaled uniformly to fit the crop image view.\n     */\n    CENTER_INSIDE\n  }\n  // endregion\n\n  // region: Inner class: Guidelines\n\n  /** The possible guidelines showing types. */\n  public enum Guidelines {\n    /** Never show */\n    OFF,\n\n    /** Show when crop move action is live */\n    ON_TOUCH,\n\n    /** Always show */\n    ON\n  }\n  // endregion\n\n  // region: Inner class: RequestSizeOptions\n\n  /** Possible options for handling requested width/height for cropping. */\n  public enum RequestSizeOptions {\n\n    /** No resize/sampling is done unless required for memory management (OOM). */\n    NONE,\n\n    /**\n     * Only sample the image during loading (if image set using URI) so the smallest of the image\n     * dimensions will be between the requested size and x2 requested size.<br>\n     * NOTE: resulting image will not be exactly requested width/height see: <a\n     * href=\"http://developer.android.com/training/displaying-bitmaps/load-bitmap.html\">Loading\n     * Large Bitmaps Efficiently</a>.\n     */\n    SAMPLING,\n\n    /**\n     * Resize the image uniformly (maintain the image's aspect ratio) so that both dimensions (width\n     * and height) of the image will be equal to or <b>less</b> than the corresponding requested\n     * dimension.<br>\n     * If the image is smaller than the requested size it will NOT change.\n     */\n    RESIZE_INSIDE,\n\n    /**\n     * Resize the image uniformly (maintain the image's aspect ratio) to fit in the given\n     * width/height.<br>\n     * The largest dimension will be equals to the requested and the second dimension will be\n     * smaller.<br>\n     * If the image is smaller than the requested size it will enlarge it.\n     */\n    RESIZE_FIT,\n\n    /**\n     * Resize the image to fit exactly in the given width/height.<br>\n     * This resize method does NOT preserve aspect ratio.<br>\n     * If the image is smaller than the requested size it will enlarge it.\n     */\n    RESIZE_EXACT\n  }\n  // endregion\n\n  // region: Inner class: OnSetImageUriCompleteListener\n\n  /** Interface definition for a callback to be invoked when the crop overlay is released. */\n  public interface OnSetCropOverlayReleasedListener {\n\n    /**\n     * Called when the crop overlay changed listener is called and inProgress is false.\n     *\n     * @param rect The rect coordinates of the cropped overlay\n     */\n    void onCropOverlayReleased(Rect rect);\n  }\n\n  /** Interface definition for a callback to be invoked when the crop overlay is released. */\n  public interface OnSetCropOverlayMovedListener {\n\n    /**\n     * Called when the crop overlay is moved\n     *\n     * @param rect The rect coordinates of the cropped overlay\n     */\n    void onCropOverlayMoved(Rect rect);\n  }\n\n  /** Interface definition for a callback to be invoked when the crop overlay is released. */\n  public interface OnSetCropWindowChangeListener {\n\n    /** Called when the crop window is changed */\n    void onCropWindowChanged();\n  }\n\n  /** Interface definition for a callback to be invoked when image async loading is complete. */\n  public interface OnSetImageUriCompleteListener {\n\n    /**\n     * Called when a crop image view has completed loading image for cropping.<br>\n     * If loading failed error parameter will contain the error.\n     *\n     * @param view The crop image view that loading of image was complete.\n     * @param uri the URI of the image that was loading\n     * @param error if error occurred during loading will contain the error, otherwise null.\n     */\n    void onSetImageUriComplete(CropImageView view, Uri uri, Exception error);\n  }\n  // endregion\n\n  // region: Inner class: OnGetCroppedImageCompleteListener\n\n  /** Interface definition for a callback to be invoked when image async crop is complete. */\n  public interface OnCropImageCompleteListener {\n\n    /**\n     * Called when a crop image view has completed cropping image.<br>\n     * Result object contains the cropped bitmap, saved cropped image uri, crop points data or the\n     * error occured during cropping.\n     *\n     * @param view The crop image view that cropping of image was complete.\n     * @param result the crop image result data (with cropped image or error)\n     */\n    void onCropImageComplete(CropImageView view, CropResult result);\n  }\n  // endregion\n\n  // region: Inner class: ActivityResult\n\n  /** Result data of crop image. */\n  public static class CropResult {\n\n    /**\n     * The image bitmap of the original image loaded for cropping.<br>\n     * Null if uri used to load image or activity result is used.\n     */\n    private final Bitmap mOriginalBitmap;\n\n    /**\n     * The Android uri of the original image loaded for cropping.<br>\n     * Null if bitmap was used to load image.\n     */\n    private final Uri mOriginalUri;\n\n    /**\n     * The cropped image bitmap result.<br>\n     * Null if save cropped image was executed, no output requested or failure.\n     */\n    private final Bitmap mBitmap;\n\n    /**\n     * The Android uri of the saved cropped image result.<br>\n     * Null if get cropped image was executed, no output requested or failure.\n     */\n    private final Uri mUri;\n\n    /** The error that failed the loading/cropping (null if successful) */\n    private final Exception mError;\n\n    /** The 4 points of the cropping window in the source image */\n    private final float[] mCropPoints;\n\n    /** The rectangle of the cropping window in the source image */\n    private final Rect mCropRect;\n\n    /** The rectangle of the source image dimensions */\n    private final Rect mWholeImageRect;\n\n    /** The final rotation of the cropped image relative to source */\n    private final int mRotation;\n\n    /** sample size used creating the crop bitmap to lower its size */\n    private final int mSampleSize;\n\n    CropResult(\n        Bitmap originalBitmap,\n        Uri originalUri,\n        Bitmap bitmap,\n        Uri uri,\n        Exception error,\n        float[] cropPoints,\n        Rect cropRect,\n        Rect wholeImageRect,\n        int rotation,\n        int sampleSize) {\n      mOriginalBitmap = originalBitmap;\n      mOriginalUri = originalUri;\n      mBitmap = bitmap;\n      mUri = uri;\n      mError = error;\n      mCropPoints = cropPoints;\n      mCropRect = cropRect;\n      mWholeImageRect = wholeImageRect;\n      mRotation = rotation;\n      mSampleSize = sampleSize;\n    }\n\n    /**\n     * The image bitmap of the original image loaded for cropping.<br>\n     * Null if uri used to load image or activity result is used.\n     */\n    public Bitmap getOriginalBitmap() {\n      return mOriginalBitmap;\n    }\n\n    /**\n     * The Android uri of the original image loaded for cropping.<br>\n     * Null if bitmap was used to load image.\n     */\n    public Uri getOriginalUri() {\n      return mOriginalUri;\n    }\n\n    /** Is the result is success or error. */\n    public boolean isSuccessful() {\n      return mError == null;\n    }\n\n    /**\n     * The cropped image bitmap result.<br>\n     * Null if save cropped image was executed, no output requested or failure.\n     */\n    public Bitmap getBitmap() {\n      return mBitmap;\n    }\n\n    /**\n     * The Android uri of the saved cropped image result Null if get cropped image was executed, no\n     * output requested or failure.\n     */\n    public Uri getUri() {\n      return mUri;\n    }\n\n    /** The error that failed the loading/cropping (null if successful) */\n    public Exception getError() {\n      return mError;\n    }\n\n    /** The 4 points of the cropping window in the source image */\n    public float[] getCropPoints() {\n      return mCropPoints;\n    }\n\n    /** The rectangle of the cropping window in the source image */\n    public Rect getCropRect() {\n      return mCropRect;\n    }\n\n    /** The rectangle of the source image dimensions */\n    public Rect getWholeImageRect() {\n      return mWholeImageRect;\n    }\n\n    /** The final rotation of the cropped image relative to source */\n    public int getRotation() {\n      return mRotation;\n    }\n\n    /** sample size used creating the crop bitmap to lower its size */\n    public int getSampleSize() {\n      return mSampleSize;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropOverlayView.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Paint;\nimport android.graphics.Path;\nimport android.graphics.Rect;\nimport android.graphics.RectF;\nimport android.graphics.Region;\nimport android.os.Build;\nimport android.util.AttributeSet;\nimport android.util.Log;\nimport android.view.MotionEvent;\nimport android.view.ScaleGestureDetector;\nimport android.view.View;\n\nimport java.util.Arrays;\n\n/** A custom View representing the crop window and the shaded background outside the crop window. */\npublic class CropOverlayView extends View {\n\n  // region: Fields and Consts\n\n  /** Gesture detector used for multi touch box scaling */\n  private ScaleGestureDetector mScaleDetector;\n\n  /** Boolean to see if multi touch is enabled for the crop rectangle */\n  private boolean mMultiTouchEnabled;\n\n  /** Handler from crop window stuff, moving and knowing possition. */\n  private final CropWindowHandler mCropWindowHandler = new CropWindowHandler();\n\n  /** Listener to publicj crop window changes */\n  private CropWindowChangeListener mCropWindowChangeListener;\n\n  /** Rectangle used for drawing */\n  private final RectF mDrawRect = new RectF();\n\n  /** The Paint used to draw the white rectangle around the crop area. */\n  private Paint mBorderPaint;\n\n  /** The Paint used to draw the corners of the Border */\n  private Paint mBorderCornerPaint;\n\n  /** The Paint used to draw the guidelines within the crop area when pressed. */\n  private Paint mGuidelinePaint;\n\n  /** The Paint used to darken the surrounding areas outside the crop area. */\n  private Paint mBackgroundPaint;\n\n  /** Used for oval crop window shape or non-straight rotation drawing. */\n  private Path mPath = new Path();\n\n  /** The bounding box around the Bitmap that we are cropping. */\n  private final float[] mBoundsPoints = new float[8];\n\n  /** The bounding box around the Bitmap that we are cropping. */\n  private final RectF mCalcBounds = new RectF();\n\n  /** The bounding image view width used to know the crop overlay is at view edges. */\n  private int mViewWidth;\n\n  /** The bounding image view height used to know the crop overlay is at view edges. */\n  private int mViewHeight;\n\n  /** The offset to draw the border corener from the border */\n  private float mBorderCornerOffset;\n\n  /** the length of the border corner to draw */\n  private float mBorderCornerLength;\n\n  /** The initial crop window padding from image borders */\n  private float mInitialCropWindowPaddingRatio;\n\n  /** The radius of the touch zone (in pixels) around a given Handle. */\n  private float mTouchRadius;\n\n  /**\n   * An edge of the crop window will snap to the corresponding edge of a specified bounding box when\n   * the crop window edge is less than or equal to this distance (in pixels) away from the bounding\n   * box edge.\n   */\n  private float mSnapRadius;\n\n  /** The Handle that is currently pressed; null if no Handle is pressed. */\n  private CropWindowMoveHandler mMoveHandler;\n\n  /**\n   * Flag indicating if the crop area should always be a certain aspect ratio (indicated by\n   * mTargetAspectRatio).\n   */\n  private boolean mFixAspectRatio;\n\n  /** save the current aspect ratio of the image */\n  private int mAspectRatioX;\n\n  /** save the current aspect ratio of the image */\n  private int mAspectRatioY;\n\n  /**\n   * The aspect ratio that the crop area should maintain; this variable is only used when\n   * mMaintainAspectRatio is true.\n   */\n  private float mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;\n\n  /** Instance variables for customizable attributes */\n  private CropImageView.Guidelines mGuidelines;\n\n  /** The shape of the cropping area - rectangle/circular. */\n  private CropImageView.CropShape mCropShape;\n\n  /** the initial crop window rectangle to set */\n  private final Rect mInitialCropWindowRect = new Rect();\n\n  /** Whether the Crop View has been initialized for the first time */\n  private boolean initializedCropWindow;\n\n  /** Used to set back LayerType after changing to software. */\n  private Integer mOriginalLayerType;\n  // endregion\n\n  public CropOverlayView(Context context) {\n    this(context, null);\n  }\n\n  public CropOverlayView(Context context, AttributeSet attrs) {\n    super(context, attrs);\n  }\n\n  /** Set the crop window change listener. */\n  public void setCropWindowChangeListener(CropWindowChangeListener listener) {\n    mCropWindowChangeListener = listener;\n  }\n\n  /** Get the left/top/right/bottom coordinates of the crop window. */\n  public RectF getCropWindowRect() {\n    return mCropWindowHandler.getRect();\n  }\n\n  /** Set the left/top/right/bottom coordinates of the crop window. */\n  public void setCropWindowRect(RectF rect) {\n    mCropWindowHandler.setRect(rect);\n  }\n\n  /** Fix the current crop window rectangle if it is outside of cropping image or view bounds. */\n  public void fixCurrentCropWindowRect() {\n    RectF rect = getCropWindowRect();\n    fixCropWindowRectByRules(rect);\n    mCropWindowHandler.setRect(rect);\n  }\n\n  /**\n   * Informs the CropOverlayView of the image's position relative to the ImageView. This is\n   * necessary to call in order to draw the crop window.\n   *\n   * @param boundsPoints the image's bounding points\n   * @param viewWidth The bounding image view width.\n   * @param viewHeight The bounding image view height.\n   */\n  public void setBounds(float[] boundsPoints, int viewWidth, int viewHeight) {\n    if (boundsPoints == null || !Arrays.equals(mBoundsPoints, boundsPoints)) {\n      if (boundsPoints == null) {\n        Arrays.fill(mBoundsPoints, 0);\n      } else {\n        System.arraycopy(boundsPoints, 0, mBoundsPoints, 0, boundsPoints.length);\n      }\n      mViewWidth = viewWidth;\n      mViewHeight = viewHeight;\n      RectF cropRect = mCropWindowHandler.getRect();\n      if (cropRect.width() == 0 || cropRect.height() == 0) {\n        initCropWindow();\n      }\n    }\n  }\n\n  /** Resets the crop overlay view. */\n  public void resetCropOverlayView() {\n    if (initializedCropWindow) {\n      setCropWindowRect(BitmapUtils.EMPTY_RECT_F);\n      initCropWindow();\n      invalidate();\n    }\n  }\n\n  /** The shape of the cropping area - rectangle/circular. */\n  public CropImageView.CropShape getCropShape() {\n    return mCropShape;\n  }\n\n  /** The shape of the cropping area - rectangle/circular. */\n  public void setCropShape(CropImageView.CropShape cropShape) {\n    if (mCropShape != cropShape) {\n      mCropShape = cropShape;\n        if (Build.VERSION.SDK_INT <= 17) {\n        if (mCropShape == CropImageView.CropShape.OVAL) {\n          mOriginalLayerType = getLayerType();\n          if (mOriginalLayerType != View.LAYER_TYPE_SOFTWARE) {\n            // TURN off hardware acceleration\n            setLayerType(View.LAYER_TYPE_SOFTWARE, null);\n          } else {\n            mOriginalLayerType = null;\n          }\n        } else if (mOriginalLayerType != null) {\n          // return hardware acceleration back\n          setLayerType(mOriginalLayerType, null);\n          mOriginalLayerType = null;\n        }\n      }\n      invalidate();\n    }\n  }\n\n  /** Get the current guidelines option set. */\n  public CropImageView.Guidelines getGuidelines() {\n    return mGuidelines;\n  }\n\n  /**\n   * Sets the guidelines for the CropOverlayView to be either on, off, or to show when resizing the\n   * application.\n   */\n  public void setGuidelines(CropImageView.Guidelines guidelines) {\n    if (mGuidelines != guidelines) {\n      mGuidelines = guidelines;\n      if (initializedCropWindow) {\n        invalidate();\n      }\n    }\n  }\n\n  /**\n   * whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows it to\n   * be changed.\n   */\n  public boolean isFixAspectRatio() {\n    return mFixAspectRatio;\n  }\n\n  /**\n   * Sets whether the aspect ratio is fixed or not; true fixes the aspect ratio, while false allows\n   * it to be changed.\n   */\n  public void setFixedAspectRatio(boolean fixAspectRatio) {\n    if (mFixAspectRatio != fixAspectRatio) {\n      mFixAspectRatio = fixAspectRatio;\n      if (initializedCropWindow) {\n        initCropWindow();\n        invalidate();\n      }\n    }\n  }\n\n  /** the X value of the aspect ratio; */\n  public int getAspectRatioX() {\n    return mAspectRatioX;\n  }\n\n  /** Sets the X value of the aspect ratio; is defaulted to 1. */\n  public void setAspectRatioX(int aspectRatioX) {\n    if (aspectRatioX <= 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set aspect ratio value to a number less than or equal to 0.\");\n    } else if (mAspectRatioX != aspectRatioX) {\n      mAspectRatioX = aspectRatioX;\n      mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;\n\n      if (initializedCropWindow) {\n        initCropWindow();\n        invalidate();\n      }\n    }\n  }\n\n  /** the Y value of the aspect ratio; */\n  public int getAspectRatioY() {\n    return mAspectRatioY;\n  }\n\n  /**\n   * Sets the Y value of the aspect ratio; is defaulted to 1.\n   *\n   * @param aspectRatioY int that specifies the new Y value of the aspect ratio\n   */\n  public void setAspectRatioY(int aspectRatioY) {\n    if (aspectRatioY <= 0) {\n      throw new IllegalArgumentException(\n          \"Cannot set aspect ratio value to a number less than or equal to 0.\");\n    } else if (mAspectRatioY != aspectRatioY) {\n      mAspectRatioY = aspectRatioY;\n      mTargetAspectRatio = ((float) mAspectRatioX) / mAspectRatioY;\n\n      if (initializedCropWindow) {\n        initCropWindow();\n        invalidate();\n      }\n    }\n  }\n\n  /**\n   * An edge of the crop window will snap to the corresponding edge of a specified bounding box when\n   * the crop window edge is less than or equal to this distance (in pixels) away from the bounding\n   * box edge. (default: 3)\n   */\n  public void setSnapRadius(float snapRadius) {\n    mSnapRadius = snapRadius;\n  }\n\n  /** Set multi touch functionality to enabled/disabled. */\n  public boolean setMultiTouchEnabled(boolean multiTouchEnabled) {\n      if (mMultiTouchEnabled != multiTouchEnabled) {\n      mMultiTouchEnabled = multiTouchEnabled;\n      if (mMultiTouchEnabled && mScaleDetector == null) {\n        mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());\n      }\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * the min size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) {\n    mCropWindowHandler.setMinCropResultSize(minCropResultWidth, minCropResultHeight);\n  }\n\n  /**\n   * the max size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {\n    mCropWindowHandler.setMaxCropResultSize(maxCropResultWidth, maxCropResultHeight);\n  }\n\n  /**\n   * set the max width/height and scale factor of the shown image to original image to scale the\n   * limits appropriately.\n   */\n  public void setCropWindowLimits(\n      float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) {\n    mCropWindowHandler.setCropWindowLimits(\n        maxWidth, maxHeight, scaleFactorWidth, scaleFactorHeight);\n  }\n\n  /** Get crop window initial rectangle. */\n  public Rect getInitialCropWindowRect() {\n    return mInitialCropWindowRect;\n  }\n\n  /** Set crop window initial rectangle to be used instead of default. */\n  public void setInitialCropWindowRect(Rect rect) {\n    mInitialCropWindowRect.set(rect != null ? rect : BitmapUtils.EMPTY_RECT);\n    if (initializedCropWindow) {\n      initCropWindow();\n      invalidate();\n      callOnCropWindowChanged(false);\n    }\n  }\n\n  /** Reset crop window to initial rectangle. */\n  public void resetCropWindowRect() {\n    if (initializedCropWindow) {\n      initCropWindow();\n      invalidate();\n      callOnCropWindowChanged(false);\n    }\n  }\n\n  /**\n   * Sets all initial values, but does not call initCropWindow to reset the views.<br>\n   * Used once at the very start to initialize the attributes.\n   */\n  public void setInitialAttributeValues(CropImageOptions options) {\n\n    mCropWindowHandler.setInitialAttributeValues(options);\n\n    setCropShape(options.cropShape);\n\n    setSnapRadius(options.snapRadius);\n\n    setGuidelines(options.guidelines);\n\n    setFixedAspectRatio(options.fixAspectRatio);\n\n    setAspectRatioX(options.aspectRatioX);\n\n    setAspectRatioY(options.aspectRatioY);\n\n    setMultiTouchEnabled(options.multiTouchEnabled);\n\n    mTouchRadius = options.touchRadius;\n\n    mInitialCropWindowPaddingRatio = options.initialCropWindowPaddingRatio;\n\n    mBorderPaint = getNewPaintOrNull(options.borderLineThickness, options.borderLineColor);\n\n    mBorderCornerOffset = options.borderCornerOffset;\n    mBorderCornerLength = options.borderCornerLength;\n    mBorderCornerPaint =\n        getNewPaintOrNull(options.borderCornerThickness, options.borderCornerColor);\n\n    mGuidelinePaint = getNewPaintOrNull(options.guidelinesThickness, options.guidelinesColor);\n\n    mBackgroundPaint = getNewPaint(options.backgroundColor);\n  }\n\n  // region: Private methods\n\n  /**\n   * Set the initial crop window size and position. This is dependent on the size and position of\n   * the image being cropped.\n   */\n  private void initCropWindow() {\n\n    float leftLimit = Math.max(BitmapUtils.getRectLeft(mBoundsPoints), 0);\n    float topLimit = Math.max(BitmapUtils.getRectTop(mBoundsPoints), 0);\n    float rightLimit = Math.min(BitmapUtils.getRectRight(mBoundsPoints), getWidth());\n    float bottomLimit = Math.min(BitmapUtils.getRectBottom(mBoundsPoints), getHeight());\n\n    if (rightLimit <= leftLimit || bottomLimit <= topLimit) {\n      return;\n    }\n\n    RectF rect = new RectF();\n\n    // Tells the attribute functions the crop window has already been initialized\n    initializedCropWindow = true;\n\n    float horizontalPadding = mInitialCropWindowPaddingRatio * (rightLimit - leftLimit);\n    float verticalPadding = mInitialCropWindowPaddingRatio * (bottomLimit - topLimit);\n\n    if (mInitialCropWindowRect.width() > 0 && mInitialCropWindowRect.height() > 0) {\n      // Get crop window position relative to the displayed image.\n      rect.left =\n          leftLimit + mInitialCropWindowRect.left / mCropWindowHandler.getScaleFactorWidth();\n      rect.top = topLimit + mInitialCropWindowRect.top / mCropWindowHandler.getScaleFactorHeight();\n      rect.right =\n          rect.left + mInitialCropWindowRect.width() / mCropWindowHandler.getScaleFactorWidth();\n      rect.bottom =\n          rect.top + mInitialCropWindowRect.height() / mCropWindowHandler.getScaleFactorHeight();\n\n      // Correct for floating point errors. Crop rect boundaries should not exceed the source Bitmap\n      // bounds.\n      rect.left = Math.max(leftLimit, rect.left);\n      rect.top = Math.max(topLimit, rect.top);\n      rect.right = Math.min(rightLimit, rect.right);\n      rect.bottom = Math.min(bottomLimit, rect.bottom);\n\n    } else if (mFixAspectRatio && rightLimit > leftLimit && bottomLimit > topLimit) {\n\n      // If the image aspect ratio is wider than the crop aspect ratio,\n      // then the image height is the determining initial length. Else, vice-versa.\n      float bitmapAspectRatio = (rightLimit - leftLimit) / (bottomLimit - topLimit);\n      if (bitmapAspectRatio > mTargetAspectRatio) {\n\n        rect.top = topLimit + verticalPadding;\n        rect.bottom = bottomLimit - verticalPadding;\n\n        float centerX = getWidth() / 2f;\n\n        // dirty fix for wrong crop overlay aspect ratio when using fixed aspect ratio\n        mTargetAspectRatio = (float) mAspectRatioX / mAspectRatioY;\n\n        // Limits the aspect ratio to no less than 40 wide or 40 tall\n        float cropWidth =\n            Math.max(mCropWindowHandler.getMinCropWidth(), rect.height() * mTargetAspectRatio);\n\n        float halfCropWidth = cropWidth / 2f;\n        rect.left = centerX - halfCropWidth;\n        rect.right = centerX + halfCropWidth;\n\n      } else {\n\n        rect.left = leftLimit + horizontalPadding;\n        rect.right = rightLimit - horizontalPadding;\n\n        float centerY = getHeight() / 2f;\n\n        // Limits the aspect ratio to no less than 40 wide or 40 tall\n        float cropHeight =\n            Math.max(mCropWindowHandler.getMinCropHeight(), rect.width() / mTargetAspectRatio);\n\n        float halfCropHeight = cropHeight / 2f;\n        rect.top = centerY - halfCropHeight;\n        rect.bottom = centerY + halfCropHeight;\n      }\n    } else {\n      // Initialize crop window to have 10% padding w/ respect to image.\n      rect.left = leftLimit + horizontalPadding;\n      rect.top = topLimit + verticalPadding;\n      rect.right = rightLimit - horizontalPadding;\n      rect.bottom = bottomLimit - verticalPadding;\n    }\n\n    fixCropWindowRectByRules(rect);\n\n    mCropWindowHandler.setRect(rect);\n  }\n\n  /** Fix the given rect to fit into bitmap rect and follow min, max and aspect ratio rules. */\n  private void fixCropWindowRectByRules(RectF rect) {\n    if (rect.width() < mCropWindowHandler.getMinCropWidth()) {\n      float adj = (mCropWindowHandler.getMinCropWidth() - rect.width()) / 2;\n      rect.left -= adj;\n      rect.right += adj;\n    }\n    if (rect.height() < mCropWindowHandler.getMinCropHeight()) {\n      float adj = (mCropWindowHandler.getMinCropHeight() - rect.height()) / 2;\n      rect.top -= adj;\n      rect.bottom += adj;\n    }\n    if (rect.width() > mCropWindowHandler.getMaxCropWidth()) {\n      float adj = (rect.width() - mCropWindowHandler.getMaxCropWidth()) / 2;\n      rect.left += adj;\n      rect.right -= adj;\n    }\n    if (rect.height() > mCropWindowHandler.getMaxCropHeight()) {\n      float adj = (rect.height() - mCropWindowHandler.getMaxCropHeight()) / 2;\n      rect.top += adj;\n      rect.bottom -= adj;\n    }\n\n    calculateBounds(rect);\n    if (mCalcBounds.width() > 0 && mCalcBounds.height() > 0) {\n      float leftLimit = Math.max(mCalcBounds.left, 0);\n      float topLimit = Math.max(mCalcBounds.top, 0);\n      float rightLimit = Math.min(mCalcBounds.right, getWidth());\n      float bottomLimit = Math.min(mCalcBounds.bottom, getHeight());\n      if (rect.left < leftLimit) {\n        rect.left = leftLimit;\n      }\n      if (rect.top < topLimit) {\n        rect.top = topLimit;\n      }\n      if (rect.right > rightLimit) {\n        rect.right = rightLimit;\n      }\n      if (rect.bottom > bottomLimit) {\n        rect.bottom = bottomLimit;\n      }\n    }\n    if (mFixAspectRatio && Math.abs(rect.width() - rect.height() * mTargetAspectRatio) > 0.1) {\n      if (rect.width() > rect.height() * mTargetAspectRatio) {\n        float adj = Math.abs(rect.height() * mTargetAspectRatio - rect.width()) / 2;\n        rect.left += adj;\n        rect.right -= adj;\n      } else {\n        float adj = Math.abs(rect.width() / mTargetAspectRatio - rect.height()) / 2;\n        rect.top += adj;\n        rect.bottom -= adj;\n      }\n    }\n  }\n\n  /**\n   * Draw crop overview by drawing background over image not in the cripping area, then borders and\n   * guidelines.\n   */\n  @Override\n  protected void onDraw(Canvas canvas) {\n\n    super.onDraw(canvas);\n\n    // Draw translucent background for the cropped area.\n    drawBackground(canvas);\n\n    if (mCropWindowHandler.showGuidelines()) {\n      // Determines whether guidelines should be drawn or not\n      if (mGuidelines == CropImageView.Guidelines.ON) {\n        drawGuidelines(canvas);\n      } else if (mGuidelines == CropImageView.Guidelines.ON_TOUCH && mMoveHandler != null) {\n        // Draw only when resizing\n        drawGuidelines(canvas);\n      }\n    }\n\n    drawBorders(canvas);\n\n    drawCorners(canvas);\n  }\n\n  /** Draw shadow background over the image not including the crop area. */\n  private void drawBackground(Canvas canvas) {\n\n    RectF rect = mCropWindowHandler.getRect();\n\n    float left = Math.max(BitmapUtils.getRectLeft(mBoundsPoints), 0);\n    float top = Math.max(BitmapUtils.getRectTop(mBoundsPoints), 0);\n    float right = Math.min(BitmapUtils.getRectRight(mBoundsPoints), getWidth());\n    float bottom = Math.min(BitmapUtils.getRectBottom(mBoundsPoints), getHeight());\n\n    if (mCropShape == CropImageView.CropShape.RECTANGLE) {\n      if (!isNonStraightAngleRotated() || Build.VERSION.SDK_INT <= 17) {\n        canvas.drawRect(left, top, right, rect.top, mBackgroundPaint);\n        canvas.drawRect(left, rect.bottom, right, bottom, mBackgroundPaint);\n        canvas.drawRect(left, rect.top, rect.left, rect.bottom, mBackgroundPaint);\n        canvas.drawRect(rect.right, rect.top, right, rect.bottom, mBackgroundPaint);\n      } else {\n        mPath.reset();\n        mPath.moveTo(mBoundsPoints[0], mBoundsPoints[1]);\n        mPath.lineTo(mBoundsPoints[2], mBoundsPoints[3]);\n        mPath.lineTo(mBoundsPoints[4], mBoundsPoints[5]);\n        mPath.lineTo(mBoundsPoints[6], mBoundsPoints[7]);\n        mPath.close();\n\n        canvas.save();\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n          canvas.clipOutPath(mPath);\n        } else {\n          canvas.clipPath(mPath, Region.Op.INTERSECT);\n        }\n        canvas.clipRect(rect, Region.Op.XOR);\n        canvas.drawRect(left, top, right, bottom, mBackgroundPaint);\n        canvas.restore();\n      }\n    } else {\n      mPath.reset();\n        if (Build.VERSION.SDK_INT <= 17 && mCropShape == CropImageView.CropShape.OVAL) {\n        mDrawRect.set(rect.left + 2, rect.top + 2, rect.right - 2, rect.bottom - 2);\n      } else {\n        mDrawRect.set(rect.left, rect.top, rect.right, rect.bottom);\n      }\n      mPath.addOval(mDrawRect, Path.Direction.CW);\n      canvas.save();\n      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        canvas.clipOutPath(mPath);\n      } else {\n        canvas.clipPath(mPath, Region.Op.XOR);\n      }\n      canvas.drawRect(left, top, right, bottom, mBackgroundPaint);\n      canvas.restore();\n    }\n  }\n\n  /**\n   * Draw 2 veritcal and 2 horizontal guidelines inside the cropping area to split it into 9 equal\n   * parts.\n   */\n  private void drawGuidelines(Canvas canvas) {\n    if (mGuidelinePaint != null) {\n      float sw = mBorderPaint != null ? mBorderPaint.getStrokeWidth() : 0;\n      RectF rect = mCropWindowHandler.getRect();\n      rect.inset(sw, sw);\n\n      float oneThirdCropWidth = rect.width() / 3;\n      float oneThirdCropHeight = rect.height() / 3;\n\n      if (mCropShape == CropImageView.CropShape.OVAL) {\n\n        float w = rect.width() / 2 - sw;\n        float h = rect.height() / 2 - sw;\n\n        // Draw vertical guidelines.\n        float x1 = rect.left + oneThirdCropWidth;\n        float x2 = rect.right - oneThirdCropWidth;\n        float yv = (float) (h * Math.sin(Math.acos((w - oneThirdCropWidth) / w)));\n        canvas.drawLine(x1, rect.top + h - yv, x1, rect.bottom - h + yv, mGuidelinePaint);\n        canvas.drawLine(x2, rect.top + h - yv, x2, rect.bottom - h + yv, mGuidelinePaint);\n\n        // Draw horizontal guidelines.\n        float y1 = rect.top + oneThirdCropHeight;\n        float y2 = rect.bottom - oneThirdCropHeight;\n        float xv = (float) (w * Math.cos(Math.asin((h - oneThirdCropHeight) / h)));\n        canvas.drawLine(rect.left + w - xv, y1, rect.right - w + xv, y1, mGuidelinePaint);\n        canvas.drawLine(rect.left + w - xv, y2, rect.right - w + xv, y2, mGuidelinePaint);\n      } else {\n\n        // Draw vertical guidelines.\n        float x1 = rect.left + oneThirdCropWidth;\n        float x2 = rect.right - oneThirdCropWidth;\n        canvas.drawLine(x1, rect.top, x1, rect.bottom, mGuidelinePaint);\n        canvas.drawLine(x2, rect.top, x2, rect.bottom, mGuidelinePaint);\n\n        // Draw horizontal guidelines.\n        float y1 = rect.top + oneThirdCropHeight;\n        float y2 = rect.bottom - oneThirdCropHeight;\n        canvas.drawLine(rect.left, y1, rect.right, y1, mGuidelinePaint);\n        canvas.drawLine(rect.left, y2, rect.right, y2, mGuidelinePaint);\n      }\n    }\n  }\n\n  /** Draw borders of the crop area. */\n  private void drawBorders(Canvas canvas) {\n    if (mBorderPaint != null) {\n      float w = mBorderPaint.getStrokeWidth();\n      RectF rect = mCropWindowHandler.getRect();\n      rect.inset(w / 2, w / 2);\n\n      if (mCropShape == CropImageView.CropShape.RECTANGLE) {\n        // Draw rectangle crop window border.\n        canvas.drawRect(rect, mBorderPaint);\n      } else {\n        // Draw circular crop window border\n        canvas.drawOval(rect, mBorderPaint);\n      }\n    }\n  }\n\n  /** Draw the corner of crop overlay. */\n  private void drawCorners(Canvas canvas) {\n    if (mBorderCornerPaint != null) {\n\n      float lineWidth = mBorderPaint != null ? mBorderPaint.getStrokeWidth() : 0;\n      float cornerWidth = mBorderCornerPaint.getStrokeWidth();\n\n      // for rectangle crop shape we allow the corners to be offset from the borders\n      float w =\n          cornerWidth / 2\n              + (mCropShape == CropImageView.CropShape.RECTANGLE ? mBorderCornerOffset : 0);\n\n      RectF rect = mCropWindowHandler.getRect();\n      rect.inset(w, w);\n\n      float cornerOffset = (cornerWidth - lineWidth) / 2;\n      float cornerExtension = cornerWidth / 2 + cornerOffset;\n\n      // Top left\n      canvas.drawLine(\n          rect.left - cornerOffset,\n          rect.top - cornerExtension,\n          rect.left - cornerOffset,\n          rect.top + mBorderCornerLength,\n          mBorderCornerPaint);\n      canvas.drawLine(\n          rect.left - cornerExtension,\n          rect.top - cornerOffset,\n          rect.left + mBorderCornerLength,\n          rect.top - cornerOffset,\n          mBorderCornerPaint);\n\n      // Top right\n      canvas.drawLine(\n          rect.right + cornerOffset,\n          rect.top - cornerExtension,\n          rect.right + cornerOffset,\n          rect.top + mBorderCornerLength,\n          mBorderCornerPaint);\n      canvas.drawLine(\n          rect.right + cornerExtension,\n          rect.top - cornerOffset,\n          rect.right - mBorderCornerLength,\n          rect.top - cornerOffset,\n          mBorderCornerPaint);\n\n      // Bottom left\n      canvas.drawLine(\n          rect.left - cornerOffset,\n          rect.bottom + cornerExtension,\n          rect.left - cornerOffset,\n          rect.bottom - mBorderCornerLength,\n          mBorderCornerPaint);\n      canvas.drawLine(\n          rect.left - cornerExtension,\n          rect.bottom + cornerOffset,\n          rect.left + mBorderCornerLength,\n          rect.bottom + cornerOffset,\n          mBorderCornerPaint);\n\n      // Bottom left\n      canvas.drawLine(\n          rect.right + cornerOffset,\n          rect.bottom + cornerExtension,\n          rect.right + cornerOffset,\n          rect.bottom - mBorderCornerLength,\n          mBorderCornerPaint);\n      canvas.drawLine(\n          rect.right + cornerExtension,\n          rect.bottom + cornerOffset,\n          rect.right - mBorderCornerLength,\n          rect.bottom + cornerOffset,\n          mBorderCornerPaint);\n    }\n  }\n\n  /** Creates the Paint object for drawing. */\n  private static Paint getNewPaint(int color) {\n    Paint paint = new Paint();\n    paint.setColor(color);\n    return paint;\n  }\n\n  /** Creates the Paint object for given thickness and color, if thickness < 0 return null. */\n  private static Paint getNewPaintOrNull(float thickness, int color) {\n    if (thickness > 0) {\n      Paint borderPaint = new Paint();\n      borderPaint.setColor(color);\n      borderPaint.setStrokeWidth(thickness);\n      borderPaint.setStyle(Paint.Style.STROKE);\n      borderPaint.setAntiAlias(true);\n      return borderPaint;\n    } else {\n      return null;\n    }\n  }\n\n  @Override\n  public boolean onTouchEvent(MotionEvent event) {\n    // If this View is not enabled, don't allow for touch interactions.\n    if (isEnabled()) {\n      if (mMultiTouchEnabled) {\n        mScaleDetector.onTouchEvent(event);\n      }\n\n      switch (event.getAction()) {\n        case MotionEvent.ACTION_DOWN:\n          onActionDown(event.getX(), event.getY());\n          return true;\n        case MotionEvent.ACTION_UP:\n        case MotionEvent.ACTION_CANCEL:\n          getParent().requestDisallowInterceptTouchEvent(false);\n          onActionUp();\n          return true;\n        case MotionEvent.ACTION_MOVE:\n          onActionMove(event.getX(), event.getY());\n          getParent().requestDisallowInterceptTouchEvent(true);\n          return true;\n        default:\n          return false;\n      }\n    } else {\n      return false;\n    }\n  }\n\n  /**\n   * On press down start crop window movment depending on the location of the press.<br>\n   * if press is far from crop window then no move handler is returned (null).\n   */\n  private void onActionDown(float x, float y) {\n    mMoveHandler = mCropWindowHandler.getMoveHandler(x, y, mTouchRadius, mCropShape);\n    if (mMoveHandler != null) {\n      invalidate();\n    }\n  }\n\n  /** Clear move handler starting in {@link #onActionDown(float, float)} if exists. */\n  private void onActionUp() {\n    if (mMoveHandler != null) {\n      mMoveHandler = null;\n      callOnCropWindowChanged(false);\n      invalidate();\n    }\n  }\n\n  /**\n   * Handle move of crop window using the move handler created in {@link #onActionDown(float,\n   * float)}.<br>\n   * The move handler will do the proper move/resize of the crop window.\n   */\n  private void onActionMove(float x, float y) {\n    if (mMoveHandler != null) {\n      float snapRadius = mSnapRadius;\n      RectF rect = mCropWindowHandler.getRect();\n\n      if (calculateBounds(rect)) {\n        snapRadius = 0;\n      }\n\n      mMoveHandler.move(\n          rect,\n          x,\n          y,\n          mCalcBounds,\n          mViewWidth,\n          mViewHeight,\n          snapRadius,\n          mFixAspectRatio,\n          mTargetAspectRatio);\n      mCropWindowHandler.setRect(rect);\n      callOnCropWindowChanged(true);\n      invalidate();\n    }\n  }\n\n  /**\n   * Calculate the bounding rectangle for current crop window, handle non-straight rotation angles.\n   * <br>\n   * If the rotation angle is straight then the bounds rectangle is the bitmap rectangle, otherwsie\n   * we find the max rectangle that is within the image bounds starting from the crop window\n   * rectangle.\n   *\n   * @param rect the crop window rectangle to start finsing bounded rectangle from\n   * @return true - non straight rotation in place, false - otherwise.\n   */\n  private boolean calculateBounds(RectF rect) {\n\n    float left = BitmapUtils.getRectLeft(mBoundsPoints);\n    float top = BitmapUtils.getRectTop(mBoundsPoints);\n    float right = BitmapUtils.getRectRight(mBoundsPoints);\n    float bottom = BitmapUtils.getRectBottom(mBoundsPoints);\n\n    if (!isNonStraightAngleRotated()) {\n      mCalcBounds.set(left, top, right, bottom);\n      return false;\n    } else {\n      float x0 = mBoundsPoints[0];\n      float y0 = mBoundsPoints[1];\n      float x2 = mBoundsPoints[4];\n      float y2 = mBoundsPoints[5];\n      float x3 = mBoundsPoints[6];\n      float y3 = mBoundsPoints[7];\n\n      if (mBoundsPoints[7] < mBoundsPoints[1]) {\n        if (mBoundsPoints[1] < mBoundsPoints[3]) {\n          x0 = mBoundsPoints[6];\n          y0 = mBoundsPoints[7];\n          x2 = mBoundsPoints[2];\n          y2 = mBoundsPoints[3];\n          x3 = mBoundsPoints[4];\n          y3 = mBoundsPoints[5];\n        } else {\n          x0 = mBoundsPoints[4];\n          y0 = mBoundsPoints[5];\n          x2 = mBoundsPoints[0];\n          y2 = mBoundsPoints[1];\n          x3 = mBoundsPoints[2];\n          y3 = mBoundsPoints[3];\n        }\n      } else if (mBoundsPoints[1] > mBoundsPoints[3]) {\n        x0 = mBoundsPoints[2];\n        y0 = mBoundsPoints[3];\n        x2 = mBoundsPoints[6];\n        y2 = mBoundsPoints[7];\n        x3 = mBoundsPoints[0];\n        y3 = mBoundsPoints[1];\n      }\n\n      float a0 = (y3 - y0) / (x3 - x0);\n      float a1 = -1f / a0;\n      float b0 = y0 - a0 * x0;\n      float b1 = y0 - a1 * x0;\n      float b2 = y2 - a0 * x2;\n      float b3 = y2 - a1 * x2;\n\n      float c0 = (rect.centerY() - rect.top) / (rect.centerX() - rect.left);\n      float c1 = -c0;\n      float d0 = rect.top - c0 * rect.left;\n      float d1 = rect.top - c1 * rect.right;\n\n      left = Math.max(left, (d0 - b0) / (a0 - c0) < rect.right ? (d0 - b0) / (a0 - c0) : left);\n      left = Math.max(left, (d0 - b1) / (a1 - c0) < rect.right ? (d0 - b1) / (a1 - c0) : left);\n      left = Math.max(left, (d1 - b3) / (a1 - c1) < rect.right ? (d1 - b3) / (a1 - c1) : left);\n      right = Math.min(right, (d1 - b1) / (a1 - c1) > rect.left ? (d1 - b1) / (a1 - c1) : right);\n      right = Math.min(right, (d1 - b2) / (a0 - c1) > rect.left ? (d1 - b2) / (a0 - c1) : right);\n      right = Math.min(right, (d0 - b2) / (a0 - c0) > rect.left ? (d0 - b2) / (a0 - c0) : right);\n\n      top = Math.max(top, Math.max(a0 * left + b0, a1 * right + b1));\n      bottom = Math.min(bottom, Math.min(a1 * left + b3, a0 * right + b2));\n\n      mCalcBounds.left = left;\n      mCalcBounds.top = top;\n      mCalcBounds.right = right;\n      mCalcBounds.bottom = bottom;\n      return true;\n    }\n  }\n\n  /** Is the cropping image has been rotated by NOT 0,90,180 or 270 degrees. */\n  private boolean isNonStraightAngleRotated() {\n    return mBoundsPoints[0] != mBoundsPoints[6] && mBoundsPoints[1] != mBoundsPoints[7];\n  }\n\n  /** Invoke on crop change listener safe, don't let the app crash on exception. */\n  private void callOnCropWindowChanged(boolean inProgress) {\n    try {\n      if (mCropWindowChangeListener != null) {\n        mCropWindowChangeListener.onCropWindowChanged(inProgress);\n      }\n    } catch (Exception e) {\n      Log.e(\"AIC\", \"Exception in crop window changed\", e);\n    }\n  }\n  // endregion\n\n  // region: Inner class: CropWindowChangeListener\n\n  /** Interface definition for a callback to be invoked when crop window rectangle is changing. */\n  public interface CropWindowChangeListener {\n\n    /**\n     * Called after a change in crop window rectangle.\n     *\n     * @param inProgress is the crop window change operation is still in progress by user touch\n     */\n    void onCropWindowChanged(boolean inProgress);\n  }\n  // endregion\n\n  // region: Inner class: ScaleListener\n\n  /** Handle scaling the rectangle based on two finger input */\n  private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {\n\n    @Override\n    @TargetApi(Build.VERSION_CODES.HONEYCOMB)\n    public boolean onScale(ScaleGestureDetector detector) {\n      RectF rect = mCropWindowHandler.getRect();\n\n      float x = detector.getFocusX();\n      float y = detector.getFocusY();\n      float dY = detector.getCurrentSpanY() / 2;\n      float dX = detector.getCurrentSpanX() / 2;\n\n      float newTop = y - dY;\n      float newLeft = x - dX;\n      float newRight = x + dX;\n      float newBottom = y + dY;\n\n      if (newLeft < newRight\n          && newTop <= newBottom\n          && newLeft >= 0\n          && newRight <= mCropWindowHandler.getMaxCropWidth()\n          && newTop >= 0\n          && newBottom <= mCropWindowHandler.getMaxCropHeight()) {\n\n        rect.set(newLeft, newTop, newRight, newBottom);\n        mCropWindowHandler.setRect(rect);\n        invalidate();\n      }\n\n      return true;\n    }\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowHandler.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.graphics.RectF;\n\n/** Handler from crop window stuff, moving and knowing possition. */\nfinal class CropWindowHandler {\n\n  // region: Fields and Consts\n\n  /** The 4 edges of the crop window defining its coordinates and size */\n  private final RectF mEdges = new RectF();\n\n  /**\n   * Rectangle used to return the edges rectangle without ability to change it and without creating\n   * new all the time.\n   */\n  private final RectF mGetEdges = new RectF();\n\n  /** Minimum width in pixels that the crop window can get. */\n  private float mMinCropWindowWidth;\n\n  /** Minimum height in pixels that the crop window can get. */\n  private float mMinCropWindowHeight;\n\n  /** Maximum width in pixels that the crop window can CURRENTLY get. */\n  private float mMaxCropWindowWidth;\n\n  /** Maximum height in pixels that the crop window can CURRENTLY get. */\n  private float mMaxCropWindowHeight;\n\n  /**\n   * Minimum width in pixels that the result of cropping an image can get, affects crop window width\n   * adjusted by width scale factor.\n   */\n  private float mMinCropResultWidth;\n\n  /**\n   * Minimum height in pixels that the result of cropping an image can get, affects crop window\n   * height adjusted by height scale factor.\n   */\n  private float mMinCropResultHeight;\n\n  /**\n   * Maximum width in pixels that the result of cropping an image can get, affects crop window width\n   * adjusted by width scale factor.\n   */\n  private float mMaxCropResultWidth;\n\n  /**\n   * Maximum height in pixels that the result of cropping an image can get, affects crop window\n   * height adjusted by height scale factor.\n   */\n  private float mMaxCropResultHeight;\n\n  /** The width scale factor of shown image and actual image */\n  private float mScaleFactorWidth = 1;\n\n  /** The height scale factor of shown image and actual image */\n  private float mScaleFactorHeight = 1;\n  // endregion\n\n  /** Get the left/top/right/bottom coordinates of the crop window. */\n  public RectF getRect() {\n    mGetEdges.set(mEdges);\n    return mGetEdges;\n  }\n\n  /** Minimum width in pixels that the crop window can get. */\n  public float getMinCropWidth() {\n    return Math.max(mMinCropWindowWidth, mMinCropResultWidth / mScaleFactorWidth);\n  }\n\n  /** Minimum height in pixels that the crop window can get. */\n  public float getMinCropHeight() {\n    return Math.max(mMinCropWindowHeight, mMinCropResultHeight / mScaleFactorHeight);\n  }\n\n  /** Maximum width in pixels that the crop window can get. */\n  public float getMaxCropWidth() {\n    return Math.min(mMaxCropWindowWidth, mMaxCropResultWidth / mScaleFactorWidth);\n  }\n\n  /** Maximum height in pixels that the crop window can get. */\n  public float getMaxCropHeight() {\n    return Math.min(mMaxCropWindowHeight, mMaxCropResultHeight / mScaleFactorHeight);\n  }\n\n  /** get the scale factor (on width) of the showen image to original image. */\n  public float getScaleFactorWidth() {\n    return mScaleFactorWidth;\n  }\n\n  /** get the scale factor (on height) of the showen image to original image. */\n  public float getScaleFactorHeight() {\n    return mScaleFactorHeight;\n  }\n\n  /**\n   * the min size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMinCropResultSize(int minCropResultWidth, int minCropResultHeight) {\n    mMinCropResultWidth = minCropResultWidth;\n    mMinCropResultHeight = minCropResultHeight;\n  }\n\n  /**\n   * the max size the resulting cropping image is allowed to be, affects the cropping window limits\n   * (in pixels).<br>\n   */\n  public void setMaxCropResultSize(int maxCropResultWidth, int maxCropResultHeight) {\n    mMaxCropResultWidth = maxCropResultWidth;\n    mMaxCropResultHeight = maxCropResultHeight;\n  }\n\n  /**\n   * set the max width/height and scale factor of the showen image to original image to scale the\n   * limits appropriately.\n   */\n  public void setCropWindowLimits(\n      float maxWidth, float maxHeight, float scaleFactorWidth, float scaleFactorHeight) {\n    mMaxCropWindowWidth = maxWidth;\n    mMaxCropWindowHeight = maxHeight;\n    mScaleFactorWidth = scaleFactorWidth;\n    mScaleFactorHeight = scaleFactorHeight;\n  }\n\n  /** Set the variables to be used during crop window handling. */\n  public void setInitialAttributeValues(CropImageOptions options) {\n    mMinCropWindowWidth = options.minCropWindowWidth;\n    mMinCropWindowHeight = options.minCropWindowHeight;\n    mMinCropResultWidth = options.minCropResultWidth;\n    mMinCropResultHeight = options.minCropResultHeight;\n    mMaxCropResultWidth = options.maxCropResultWidth;\n    mMaxCropResultHeight = options.maxCropResultHeight;\n  }\n\n  /** Set the left/top/right/bottom coordinates of the crop window. */\n  public void setRect(RectF rect) {\n    mEdges.set(rect);\n  }\n\n  /**\n   * Indicates whether the crop window is small enough that the guidelines should be shown. Public\n   * because this function is also used to determine if the center handle should be focused.\n   *\n   * @return boolean Whether the guidelines should be shown or not\n   */\n  public boolean showGuidelines() {\n    return !(mEdges.width() < 100 || mEdges.height() < 100);\n  }\n\n  /**\n   * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding\n   * box, and the touch radius.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param targetRadius the target radius in pixels\n   * @return the Handle that was pressed; null if no Handle was pressed\n   */\n  public CropWindowMoveHandler getMoveHandler(\n      float x, float y, float targetRadius, CropImageView.CropShape cropShape) {\n    CropWindowMoveHandler.Type type =\n        cropShape == CropImageView.CropShape.OVAL\n            ? getOvalPressedMoveType(x, y)\n            : getRectanglePressedMoveType(x, y, targetRadius);\n    return type != null ? new CropWindowMoveHandler(type, this, x, y) : null;\n  }\n\n  // region: Private methods\n\n  /**\n   * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding\n   * box, and the touch radius.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param targetRadius the target radius in pixels\n   * @return the Handle that was pressed; null if no Handle was pressed\n   */\n  private CropWindowMoveHandler.Type getRectanglePressedMoveType(\n      float x, float y, float targetRadius) {\n    CropWindowMoveHandler.Type moveType = null;\n\n    // Note: corner-handles take precedence, then side-handles, then center.\n    if (CropWindowHandler.isInCornerTargetZone(x, y, mEdges.left, mEdges.top, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.TOP_LEFT;\n    } else if (CropWindowHandler.isInCornerTargetZone(\n        x, y, mEdges.right, mEdges.top, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.TOP_RIGHT;\n    } else if (CropWindowHandler.isInCornerTargetZone(\n        x, y, mEdges.left, mEdges.bottom, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT;\n    } else if (CropWindowHandler.isInCornerTargetZone(\n        x, y, mEdges.right, mEdges.bottom, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT;\n    } else if (CropWindowHandler.isInCenterTargetZone(\n            x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom)\n        && focusCenter()) {\n      moveType = CropWindowMoveHandler.Type.CENTER;\n    } else if (CropWindowHandler.isInHorizontalTargetZone(\n        x, y, mEdges.left, mEdges.right, mEdges.top, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.TOP;\n    } else if (CropWindowHandler.isInHorizontalTargetZone(\n        x, y, mEdges.left, mEdges.right, mEdges.bottom, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.BOTTOM;\n    } else if (CropWindowHandler.isInVerticalTargetZone(\n        x, y, mEdges.left, mEdges.top, mEdges.bottom, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.LEFT;\n    } else if (CropWindowHandler.isInVerticalTargetZone(\n        x, y, mEdges.right, mEdges.top, mEdges.bottom, targetRadius)) {\n      moveType = CropWindowMoveHandler.Type.RIGHT;\n    } else if (CropWindowHandler.isInCenterTargetZone(\n            x, y, mEdges.left, mEdges.top, mEdges.right, mEdges.bottom)\n        && !focusCenter()) {\n      moveType = CropWindowMoveHandler.Type.CENTER;\n    }\n\n    return moveType;\n  }\n\n  /**\n   * Determines which, if any, of the handles are pressed given the touch coordinates, the bounding\n   * box/oval, and the touch radius.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @return the Handle that was pressed; null if no Handle was pressed\n   */\n  private CropWindowMoveHandler.Type getOvalPressedMoveType(float x, float y) {\n\n    /*\n       Use a 6x6 grid system divided into 9 \"handles\", with the center the biggest region. While\n       this is not perfect, it's a good quick-to-ship approach.\n\n       TL T T T T TR\n        L C C C C R\n        L C C C C R\n        L C C C C R\n        L C C C C R\n       BL B B B B BR\n    */\n\n    float cellLength = mEdges.width() / 6;\n    float leftCenter = mEdges.left + cellLength;\n    float rightCenter = mEdges.left + (5 * cellLength);\n\n    float cellHeight = mEdges.height() / 6;\n    float topCenter = mEdges.top + cellHeight;\n    float bottomCenter = mEdges.top + 5 * cellHeight;\n\n    CropWindowMoveHandler.Type moveType;\n    if (x < leftCenter) {\n      if (y < topCenter) {\n        moveType = CropWindowMoveHandler.Type.TOP_LEFT;\n      } else if (y < bottomCenter) {\n        moveType = CropWindowMoveHandler.Type.LEFT;\n      } else {\n        moveType = CropWindowMoveHandler.Type.BOTTOM_LEFT;\n      }\n    } else if (x < rightCenter) {\n      if (y < topCenter) {\n        moveType = CropWindowMoveHandler.Type.TOP;\n      } else if (y < bottomCenter) {\n        moveType = CropWindowMoveHandler.Type.CENTER;\n      } else {\n        moveType = CropWindowMoveHandler.Type.BOTTOM;\n      }\n    } else {\n      if (y < topCenter) {\n        moveType = CropWindowMoveHandler.Type.TOP_RIGHT;\n      } else if (y < bottomCenter) {\n        moveType = CropWindowMoveHandler.Type.RIGHT;\n      } else {\n        moveType = CropWindowMoveHandler.Type.BOTTOM_RIGHT;\n      }\n    }\n\n    return moveType;\n  }\n\n  /**\n   * Determines if the specified coordinate is in the target touch zone for a corner handle.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param handleX the x-coordinate of the corner handle\n   * @param handleY the y-coordinate of the corner handle\n   * @param targetRadius the target radius in pixels\n   * @return true if the touch point is in the target touch zone; false otherwise\n   */\n  private static boolean isInCornerTargetZone(\n      float x, float y, float handleX, float handleY, float targetRadius) {\n    return Math.abs(x - handleX) <= targetRadius && Math.abs(y - handleY) <= targetRadius;\n  }\n\n  /**\n   * Determines if the specified coordinate is in the target touch zone for a horizontal bar handle.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param handleXStart the left x-coordinate of the horizontal bar handle\n   * @param handleXEnd the right x-coordinate of the horizontal bar handle\n   * @param handleY the y-coordinate of the horizontal bar handle\n   * @param targetRadius the target radius in pixels\n   * @return true if the touch point is in the target touch zone; false otherwise\n   */\n  private static boolean isInHorizontalTargetZone(\n      float x, float y, float handleXStart, float handleXEnd, float handleY, float targetRadius) {\n    return x > handleXStart && x < handleXEnd && Math.abs(y - handleY) <= targetRadius;\n  }\n\n  /**\n   * Determines if the specified coordinate is in the target touch zone for a vertical bar handle.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param handleX the x-coordinate of the vertical bar handle\n   * @param handleYStart the top y-coordinate of the vertical bar handle\n   * @param handleYEnd the bottom y-coordinate of the vertical bar handle\n   * @param targetRadius the target radius in pixels\n   * @return true if the touch point is in the target touch zone; false otherwise\n   */\n  private static boolean isInVerticalTargetZone(\n      float x, float y, float handleX, float handleYStart, float handleYEnd, float targetRadius) {\n    return Math.abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd;\n  }\n\n  /**\n   * Determines if the specified coordinate falls anywhere inside the given bounds.\n   *\n   * @param x the x-coordinate of the touch point\n   * @param y the y-coordinate of the touch point\n   * @param left the x-coordinate of the left bound\n   * @param top the y-coordinate of the top bound\n   * @param right the x-coordinate of the right bound\n   * @param bottom the y-coordinate of the bottom bound\n   * @return true if the touch point is inside the bounding rectangle; false otherwise\n   */\n  private static boolean isInCenterTargetZone(\n      float x, float y, float left, float top, float right, float bottom) {\n    return x > left && x < right && y > top && y < bottom;\n  }\n\n  /**\n   * Determines if the cropper should focus on the center handle or the side handles. If it is a\n   * small image, focus on the center handle so the user can move it. If it is a large image, focus\n   * on the side handles so user can grab them. Corresponds to the appearance of the\n   * RuleOfThirdsGuidelines.\n   *\n   * @return true if it is small enough such that it should focus on the center; less than\n   *     show_guidelines limit\n   */\n  private boolean focusCenter() {\n    return !showGuidelines();\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/java/com/theartofdev/edmodo/cropper/CropWindowMoveHandler.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper;\n\nimport android.graphics.Matrix;\nimport android.graphics.PointF;\nimport android.graphics.RectF;\n\n/**\n * Handler to update crop window edges by the move type - Horizontal, Vertical, Corner or Center.\n * <br>\n */\nfinal class CropWindowMoveHandler {\n\n  // region: Fields and Consts\n\n  /** Matrix used for rectangle rotation handling */\n  private static final Matrix MATRIX = new Matrix();\n\n  /** Minimum width in pixels that the crop window can get. */\n  private final float mMinCropWidth;\n\n  /** Minimum width in pixels that the crop window can get. */\n  private final float mMinCropHeight;\n\n  /** Maximum height in pixels that the crop window can get. */\n  private final float mMaxCropWidth;\n\n  /** Maximum height in pixels that the crop window can get. */\n  private final float mMaxCropHeight;\n\n  /** The type of crop window move that is handled. */\n  private final Type mType;\n\n  /**\n   * Holds the x and y offset between the exact touch location and the exact handle location that is\n   * activated. There may be an offset because we allow for some leeway (specified by mHandleRadius)\n   * in activating a handle. However, we want to maintain these offset values while the handle is\n   * being dragged so that the handle doesn't jump.\n   */\n  private final PointF mTouchOffset = new PointF();\n  // endregion\n\n  /**\n   * @param edgeMoveType the type of move this handler is executing\n   * @param horizontalEdge the primary edge associated with this handle; may be null\n   * @param verticalEdge the secondary edge associated with this handle; may be null\n   * @param cropWindowHandler main crop window handle to get and update the crop window edges\n   * @param touchX the location of the initial toch possition to measure move distance\n   * @param touchY the location of the initial toch possition to measure move distance\n   */\n  public CropWindowMoveHandler(\n      Type type, CropWindowHandler cropWindowHandler, float touchX, float touchY) {\n    mType = type;\n    mMinCropWidth = cropWindowHandler.getMinCropWidth();\n    mMinCropHeight = cropWindowHandler.getMinCropHeight();\n    mMaxCropWidth = cropWindowHandler.getMaxCropWidth();\n    mMaxCropHeight = cropWindowHandler.getMaxCropHeight();\n    calculateTouchOffset(cropWindowHandler.getRect(), touchX, touchY);\n  }\n\n  /**\n   * Updates the crop window by change in the toch location.<br>\n   * Move type handled by this instance, as initialized in creation, affects how the change in toch\n   * location changes the crop window position and size.<br>\n   * After the crop window position/size is changed by toch move it may result in values that\n   * vialate contraints: outside the bounds of the shown bitmap, smaller/larger than min/max size or\n   * missmatch in aspect ratio. So a series of fixes is executed on \"secondary\" edges to adjust it\n   * by the \"primary\" edge movement.<br>\n   * Primary is the edge directly affected by move type, secondary is the other edge.<br>\n   * The crop window is changed by directly setting the Edge coordinates.\n   *\n   * @param x the new x-coordinate of this handle\n   * @param y the new y-coordinate of this handle\n   * @param bounds the bounding rectangle of the image\n   * @param viewWidth The bounding image view width used to know the crop overlay is at view edges.\n   * @param viewHeight The bounding image view height used to know the crop overlay is at view\n   *     edges.\n   * @param parentView the parent View containing the image\n   * @param snapMargin the maximum distance (in pixels) at which the crop window should snap to the\n   *     image\n   * @param fixedAspectRatio is the aspect ration fixed and 'targetAspectRatio' should be used\n   * @param aspectRatio the aspect ratio to maintain\n   */\n  public void move(\n      RectF rect,\n      float x,\n      float y,\n      RectF bounds,\n      int viewWidth,\n      int viewHeight,\n      float snapMargin,\n      boolean fixedAspectRatio,\n      float aspectRatio) {\n\n    // Adjust the coordinates for the finger position's offset (i.e. the\n    // distance from the initial touch to the precise handle location).\n    // We want to maintain the initial touch's distance to the pressed\n    // handle so that the crop window size does not \"jump\".\n    float adjX = x + mTouchOffset.x;\n    float adjY = y + mTouchOffset.y;\n\n    if (mType == Type.CENTER) {\n      moveCenter(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin);\n    } else {\n      if (fixedAspectRatio) {\n        moveSizeWithFixedAspectRatio(\n            rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin, aspectRatio);\n      } else {\n        moveSizeWithFreeAspectRatio(rect, adjX, adjY, bounds, viewWidth, viewHeight, snapMargin);\n      }\n    }\n  }\n\n  // region: Private methods\n\n  /**\n   * Calculates the offset of the touch point from the precise location of the specified handle.<br>\n   * Save these values in a member variable since we want to maintain this offset as we drag the\n   * handle.\n   */\n  private void calculateTouchOffset(RectF rect, float touchX, float touchY) {\n\n    float touchOffsetX = 0;\n    float touchOffsetY = 0;\n\n    // Calculate the offset from the appropriate handle.\n    switch (mType) {\n      case TOP_LEFT:\n        touchOffsetX = rect.left - touchX;\n        touchOffsetY = rect.top - touchY;\n        break;\n      case TOP_RIGHT:\n        touchOffsetX = rect.right - touchX;\n        touchOffsetY = rect.top - touchY;\n        break;\n      case BOTTOM_LEFT:\n        touchOffsetX = rect.left - touchX;\n        touchOffsetY = rect.bottom - touchY;\n        break;\n      case BOTTOM_RIGHT:\n        touchOffsetX = rect.right - touchX;\n        touchOffsetY = rect.bottom - touchY;\n        break;\n      case LEFT:\n        touchOffsetX = rect.left - touchX;\n        touchOffsetY = 0;\n        break;\n      case TOP:\n        touchOffsetX = 0;\n        touchOffsetY = rect.top - touchY;\n        break;\n      case RIGHT:\n        touchOffsetX = rect.right - touchX;\n        touchOffsetY = 0;\n        break;\n      case BOTTOM:\n        touchOffsetX = 0;\n        touchOffsetY = rect.bottom - touchY;\n        break;\n      case CENTER:\n        touchOffsetX = rect.centerX() - touchX;\n        touchOffsetY = rect.centerY() - touchY;\n        break;\n      default:\n        break;\n    }\n\n    mTouchOffset.x = touchOffsetX;\n    mTouchOffset.y = touchOffsetY;\n  }\n\n  /** Center move only changes the position of the crop window without changing the size. */\n  private void moveCenter(\n      RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapRadius) {\n    float dx = x - rect.centerX();\n    float dy = y - rect.centerY();\n    if (rect.left + dx < 0\n        || rect.right + dx > viewWidth\n        || rect.left + dx < bounds.left\n        || rect.right + dx > bounds.right) {\n      dx /= 1.05f;\n      mTouchOffset.x -= dx / 2;\n    }\n    if (rect.top + dy < 0\n        || rect.bottom + dy > viewHeight\n        || rect.top + dy < bounds.top\n        || rect.bottom + dy > bounds.bottom) {\n      dy /= 1.05f;\n      mTouchOffset.y -= dy / 2;\n    }\n    rect.offset(dx, dy);\n    snapEdgesToBounds(rect, bounds, snapRadius);\n  }\n\n  /**\n   * Change the size of the crop window on the required edge (or edges for corner size move) without\n   * affecting \"secondary\" edges.<br>\n   * Only the primary edge(s) are fixed to stay within limits.\n   */\n  private void moveSizeWithFreeAspectRatio(\n      RectF rect, float x, float y, RectF bounds, int viewWidth, int viewHeight, float snapMargin) {\n    switch (mType) {\n      case TOP_LEFT:\n        adjustTop(rect, y, bounds, snapMargin, 0, false, false);\n        adjustLeft(rect, x, bounds, snapMargin, 0, false, false);\n        break;\n      case TOP_RIGHT:\n        adjustTop(rect, y, bounds, snapMargin, 0, false, false);\n        adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);\n        break;\n      case BOTTOM_LEFT:\n        adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);\n        adjustLeft(rect, x, bounds, snapMargin, 0, false, false);\n        break;\n      case BOTTOM_RIGHT:\n        adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);\n        adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);\n        break;\n      case LEFT:\n        adjustLeft(rect, x, bounds, snapMargin, 0, false, false);\n        break;\n      case TOP:\n        adjustTop(rect, y, bounds, snapMargin, 0, false, false);\n        break;\n      case RIGHT:\n        adjustRight(rect, x, bounds, viewWidth, snapMargin, 0, false, false);\n        break;\n      case BOTTOM:\n        adjustBottom(rect, y, bounds, viewHeight, snapMargin, 0, false, false);\n        break;\n      default:\n        break;\n    }\n  }\n\n  /**\n   * Change the size of the crop window on the required \"primary\" edge WITH affect to relevant\n   * \"secondary\" edge via aspect ratio.<br>\n   * Example: change in the left edge (primary) will affect top and bottom edges (secondary) to\n   * preserve the given aspect ratio.\n   */\n  private void moveSizeWithFixedAspectRatio(\n      RectF rect,\n      float x,\n      float y,\n      RectF bounds,\n      int viewWidth,\n      int viewHeight,\n      float snapMargin,\n      float aspectRatio) {\n    switch (mType) {\n      case TOP_LEFT:\n        if (calculateAspectRatio(x, y, rect.right, rect.bottom) < aspectRatio) {\n          adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, false);\n          adjustLeftByAspectRatio(rect, aspectRatio);\n        } else {\n          adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, false);\n          adjustTopByAspectRatio(rect, aspectRatio);\n        }\n        break;\n      case TOP_RIGHT:\n        if (calculateAspectRatio(rect.left, y, x, rect.bottom) < aspectRatio) {\n          adjustTop(rect, y, bounds, snapMargin, aspectRatio, false, true);\n          adjustRightByAspectRatio(rect, aspectRatio);\n        } else {\n          adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, false);\n          adjustTopByAspectRatio(rect, aspectRatio);\n        }\n        break;\n      case BOTTOM_LEFT:\n        if (calculateAspectRatio(x, rect.top, rect.right, y) < aspectRatio) {\n          adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, false);\n          adjustLeftByAspectRatio(rect, aspectRatio);\n        } else {\n          adjustLeft(rect, x, bounds, snapMargin, aspectRatio, false, true);\n          adjustBottomByAspectRatio(rect, aspectRatio);\n        }\n        break;\n      case BOTTOM_RIGHT:\n        if (calculateAspectRatio(rect.left, rect.top, x, y) < aspectRatio) {\n          adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, false, true);\n          adjustRightByAspectRatio(rect, aspectRatio);\n        } else {\n          adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, false, true);\n          adjustBottomByAspectRatio(rect, aspectRatio);\n        }\n        break;\n      case LEFT:\n        adjustLeft(rect, x, bounds, snapMargin, aspectRatio, true, true);\n        adjustTopBottomByAspectRatio(rect, bounds, aspectRatio);\n        break;\n      case TOP:\n        adjustTop(rect, y, bounds, snapMargin, aspectRatio, true, true);\n        adjustLeftRightByAspectRatio(rect, bounds, aspectRatio);\n        break;\n      case RIGHT:\n        adjustRight(rect, x, bounds, viewWidth, snapMargin, aspectRatio, true, true);\n        adjustTopBottomByAspectRatio(rect, bounds, aspectRatio);\n        break;\n      case BOTTOM:\n        adjustBottom(rect, y, bounds, viewHeight, snapMargin, aspectRatio, true, true);\n        adjustLeftRightByAspectRatio(rect, bounds, aspectRatio);\n        break;\n      default:\n        break;\n    }\n  }\n\n  /** Check if edges have gone out of bounds (including snap margin), and fix if needed. */\n  private void snapEdgesToBounds(RectF edges, RectF bounds, float margin) {\n    if (edges.left < bounds.left + margin) {\n      edges.offset(bounds.left - edges.left, 0);\n    }\n    if (edges.top < bounds.top + margin) {\n      edges.offset(0, bounds.top - edges.top);\n    }\n    if (edges.right > bounds.right - margin) {\n      edges.offset(bounds.right - edges.right, 0);\n    }\n    if (edges.bottom > bounds.bottom - margin) {\n      edges.offset(0, bounds.bottom - edges.bottom);\n    }\n  }\n\n  /**\n   * Get the resulting x-position of the left edge of the crop window given the handle's position\n   * and the image's bounding box and snap radius.\n   *\n   * @param left the position that the left edge is dragged to\n   * @param bounds the bounding box of the image that is being cropped\n   * @param snapMargin the snap distance to the image edge (in pixels)\n   */\n  private void adjustLeft(\n      RectF rect,\n      float left,\n      RectF bounds,\n      float snapMargin,\n      float aspectRatio,\n      boolean topMoves,\n      boolean bottomMoves) {\n\n    float newLeft = left;\n\n    if (newLeft < 0) {\n      newLeft /= 1.05f;\n      mTouchOffset.x -= newLeft / 1.1f;\n    }\n\n    if (newLeft < bounds.left) {\n      mTouchOffset.x -= (newLeft - bounds.left) / 2f;\n    }\n\n    if (newLeft - bounds.left < snapMargin) {\n      newLeft = bounds.left;\n    }\n\n    // Checks if the window is too small horizontally\n    if (rect.right - newLeft < mMinCropWidth) {\n      newLeft = rect.right - mMinCropWidth;\n    }\n\n    // Checks if the window is too large horizontally\n    if (rect.right - newLeft > mMaxCropWidth) {\n      newLeft = rect.right - mMaxCropWidth;\n    }\n\n    if (newLeft - bounds.left < snapMargin) {\n      newLeft = bounds.left;\n    }\n\n    // check vertical bounds if aspect ratio is in play\n    if (aspectRatio > 0) {\n      float newHeight = (rect.right - newLeft) / aspectRatio;\n\n      // Checks if the window is too small vertically\n      if (newHeight < mMinCropHeight) {\n        newLeft = Math.max(bounds.left, rect.right - mMinCropHeight * aspectRatio);\n        newHeight = (rect.right - newLeft) / aspectRatio;\n      }\n\n      // Checks if the window is too large vertically\n      if (newHeight > mMaxCropHeight) {\n        newLeft = Math.max(bounds.left, rect.right - mMaxCropHeight * aspectRatio);\n        newHeight = (rect.right - newLeft) / aspectRatio;\n      }\n\n      // if top AND bottom edge moves by aspect ratio check that it is within full height bounds\n      if (topMoves && bottomMoves) {\n        newLeft =\n            Math.max(newLeft, Math.max(bounds.left, rect.right - bounds.height() * aspectRatio));\n      } else {\n        // if top edge moves by aspect ratio check that it is within bounds\n        if (topMoves && rect.bottom - newHeight < bounds.top) {\n          newLeft = Math.max(bounds.left, rect.right - (rect.bottom - bounds.top) * aspectRatio);\n          newHeight = (rect.right - newLeft) / aspectRatio;\n        }\n\n        // if bottom edge moves by aspect ratio check that it is within bounds\n        if (bottomMoves && rect.top + newHeight > bounds.bottom) {\n          newLeft =\n              Math.max(\n                  newLeft,\n                  Math.max(bounds.left, rect.right - (bounds.bottom - rect.top) * aspectRatio));\n        }\n      }\n    }\n\n    rect.left = newLeft;\n  }\n\n  /**\n   * Get the resulting x-position of the right edge of the crop window given the handle's position\n   * and the image's bounding box and snap radius.\n   *\n   * @param right the position that the right edge is dragged to\n   * @param bounds the bounding box of the image that is being cropped\n   * @param viewWidth\n   * @param snapMargin the snap distance to the image edge (in pixels)\n   */\n  private void adjustRight(\n      RectF rect,\n      float right,\n      RectF bounds,\n      int viewWidth,\n      float snapMargin,\n      float aspectRatio,\n      boolean topMoves,\n      boolean bottomMoves) {\n\n    float newRight = right;\n\n    if (newRight > viewWidth) {\n      newRight = viewWidth + (newRight - viewWidth) / 1.05f;\n      mTouchOffset.x -= (newRight - viewWidth) / 1.1f;\n    }\n\n    if (newRight > bounds.right) {\n      mTouchOffset.x -= (newRight - bounds.right) / 2f;\n    }\n\n    // If close to the edge\n    if (bounds.right - newRight < snapMargin) {\n      newRight = bounds.right;\n    }\n\n    // Checks if the window is too small horizontally\n    if (newRight - rect.left < mMinCropWidth) {\n      newRight = rect.left + mMinCropWidth;\n    }\n\n    // Checks if the window is too large horizontally\n    if (newRight - rect.left > mMaxCropWidth) {\n      newRight = rect.left + mMaxCropWidth;\n    }\n\n    // If close to the edge\n    if (bounds.right - newRight < snapMargin) {\n      newRight = bounds.right;\n    }\n\n    // check vertical bounds if aspect ratio is in play\n    if (aspectRatio > 0) {\n      float newHeight = (newRight - rect.left) / aspectRatio;\n\n      // Checks if the window is too small vertically\n      if (newHeight < mMinCropHeight) {\n        newRight = Math.min(bounds.right, rect.left + mMinCropHeight * aspectRatio);\n        newHeight = (newRight - rect.left) / aspectRatio;\n      }\n\n      // Checks if the window is too large vertically\n      if (newHeight > mMaxCropHeight) {\n        newRight = Math.min(bounds.right, rect.left + mMaxCropHeight * aspectRatio);\n        newHeight = (newRight - rect.left) / aspectRatio;\n      }\n\n      // if top AND bottom edge moves by aspect ratio check that it is within full height bounds\n      if (topMoves && bottomMoves) {\n        newRight =\n            Math.min(newRight, Math.min(bounds.right, rect.left + bounds.height() * aspectRatio));\n      } else {\n        // if top edge moves by aspect ratio check that it is within bounds\n        if (topMoves && rect.bottom - newHeight < bounds.top) {\n          newRight = Math.min(bounds.right, rect.left + (rect.bottom - bounds.top) * aspectRatio);\n          newHeight = (newRight - rect.left) / aspectRatio;\n        }\n\n        // if bottom edge moves by aspect ratio check that it is within bounds\n        if (bottomMoves && rect.top + newHeight > bounds.bottom) {\n          newRight =\n              Math.min(\n                  newRight,\n                  Math.min(bounds.right, rect.left + (bounds.bottom - rect.top) * aspectRatio));\n        }\n      }\n    }\n\n    rect.right = newRight;\n  }\n\n  /**\n   * Get the resulting y-position of the top edge of the crop window given the handle's position and\n   * the image's bounding box and snap radius.\n   *\n   * @param top the x-position that the top edge is dragged to\n   * @param bounds the bounding box of the image that is being cropped\n   * @param snapMargin the snap distance to the image edge (in pixels)\n   */\n  private void adjustTop(\n      RectF rect,\n      float top,\n      RectF bounds,\n      float snapMargin,\n      float aspectRatio,\n      boolean leftMoves,\n      boolean rightMoves) {\n\n    float newTop = top;\n\n    if (newTop < 0) {\n      newTop /= 1.05f;\n      mTouchOffset.y -= newTop / 1.1f;\n    }\n\n    if (newTop < bounds.top) {\n      mTouchOffset.y -= (newTop - bounds.top) / 2f;\n    }\n\n    if (newTop - bounds.top < snapMargin) {\n      newTop = bounds.top;\n    }\n\n    // Checks if the window is too small vertically\n    if (rect.bottom - newTop < mMinCropHeight) {\n      newTop = rect.bottom - mMinCropHeight;\n    }\n\n    // Checks if the window is too large vertically\n    if (rect.bottom - newTop > mMaxCropHeight) {\n      newTop = rect.bottom - mMaxCropHeight;\n    }\n\n    if (newTop - bounds.top < snapMargin) {\n      newTop = bounds.top;\n    }\n\n    // check horizontal bounds if aspect ratio is in play\n    if (aspectRatio > 0) {\n      float newWidth = (rect.bottom - newTop) * aspectRatio;\n\n      // Checks if the crop window is too small horizontally due to aspect ratio adjustment\n      if (newWidth < mMinCropWidth) {\n        newTop = Math.max(bounds.top, rect.bottom - (mMinCropWidth / aspectRatio));\n        newWidth = (rect.bottom - newTop) * aspectRatio;\n      }\n\n      // Checks if the crop window is too large horizontally due to aspect ratio adjustment\n      if (newWidth > mMaxCropWidth) {\n        newTop = Math.max(bounds.top, rect.bottom - (mMaxCropWidth / aspectRatio));\n        newWidth = (rect.bottom - newTop) * aspectRatio;\n      }\n\n      // if left AND right edge moves by aspect ratio check that it is within full width bounds\n      if (leftMoves && rightMoves) {\n        newTop = Math.max(newTop, Math.max(bounds.top, rect.bottom - bounds.width() / aspectRatio));\n      } else {\n        // if left edge moves by aspect ratio check that it is within bounds\n        if (leftMoves && rect.right - newWidth < bounds.left) {\n          newTop = Math.max(bounds.top, rect.bottom - (rect.right - bounds.left) / aspectRatio);\n          newWidth = (rect.bottom - newTop) * aspectRatio;\n        }\n\n        // if right edge moves by aspect ratio check that it is within bounds\n        if (rightMoves && rect.left + newWidth > bounds.right) {\n          newTop =\n              Math.max(\n                  newTop,\n                  Math.max(bounds.top, rect.bottom - (bounds.right - rect.left) / aspectRatio));\n        }\n      }\n    }\n\n    rect.top = newTop;\n  }\n\n  /**\n   * Get the resulting y-position of the bottom edge of the crop window given the handle's position\n   * and the image's bounding box and snap radius.\n   *\n   * @param bottom the position that the bottom edge is dragged to\n   * @param bounds the bounding box of the image that is being cropped\n   * @param viewHeight\n   * @param snapMargin the snap distance to the image edge (in pixels)\n   */\n  private void adjustBottom(\n      RectF rect,\n      float bottom,\n      RectF bounds,\n      int viewHeight,\n      float snapMargin,\n      float aspectRatio,\n      boolean leftMoves,\n      boolean rightMoves) {\n\n    float newBottom = bottom;\n\n    if (newBottom > viewHeight) {\n      newBottom = viewHeight + (newBottom - viewHeight) / 1.05f;\n      mTouchOffset.y -= (newBottom - viewHeight) / 1.1f;\n    }\n\n    if (newBottom > bounds.bottom) {\n      mTouchOffset.y -= (newBottom - bounds.bottom) / 2f;\n    }\n\n    if (bounds.bottom - newBottom < snapMargin) {\n      newBottom = bounds.bottom;\n    }\n\n    // Checks if the window is too small vertically\n    if (newBottom - rect.top < mMinCropHeight) {\n      newBottom = rect.top + mMinCropHeight;\n    }\n\n    // Checks if the window is too small vertically\n    if (newBottom - rect.top > mMaxCropHeight) {\n      newBottom = rect.top + mMaxCropHeight;\n    }\n\n    if (bounds.bottom - newBottom < snapMargin) {\n      newBottom = bounds.bottom;\n    }\n\n    // check horizontal bounds if aspect ratio is in play\n    if (aspectRatio > 0) {\n      float newWidth = (newBottom - rect.top) * aspectRatio;\n\n      // Checks if the window is too small horizontally\n      if (newWidth < mMinCropWidth) {\n        newBottom = Math.min(bounds.bottom, rect.top + mMinCropWidth / aspectRatio);\n        newWidth = (newBottom - rect.top) * aspectRatio;\n      }\n\n      // Checks if the window is too large horizontally\n      if (newWidth > mMaxCropWidth) {\n        newBottom = Math.min(bounds.bottom, rect.top + mMaxCropWidth / aspectRatio);\n        newWidth = (newBottom - rect.top) * aspectRatio;\n      }\n\n      // if left AND right edge moves by aspect ratio check that it is within full width bounds\n      if (leftMoves && rightMoves) {\n        newBottom =\n            Math.min(newBottom, Math.min(bounds.bottom, rect.top + bounds.width() / aspectRatio));\n      } else {\n        // if left edge moves by aspect ratio check that it is within bounds\n        if (leftMoves && rect.right - newWidth < bounds.left) {\n          newBottom = Math.min(bounds.bottom, rect.top + (rect.right - bounds.left) / aspectRatio);\n          newWidth = (newBottom - rect.top) * aspectRatio;\n        }\n\n        // if right edge moves by aspect ratio check that it is within bounds\n        if (rightMoves && rect.left + newWidth > bounds.right) {\n          newBottom =\n              Math.min(\n                  newBottom,\n                  Math.min(bounds.bottom, rect.top + (bounds.right - rect.left) / aspectRatio));\n        }\n      }\n    }\n\n    rect.bottom = newBottom;\n  }\n\n  /**\n   * Adjust left edge by current crop window height and the given aspect ratio, the right edge\n   * remains in possition while the left adjusts to keep aspect ratio to the height.\n   */\n  private void adjustLeftByAspectRatio(RectF rect, float aspectRatio) {\n    rect.left = rect.right - rect.height() * aspectRatio;\n  }\n\n  /**\n   * Adjust top edge by current crop window width and the given aspect ratio, the bottom edge\n   * remains in possition while the top adjusts to keep aspect ratio to the width.\n   */\n  private void adjustTopByAspectRatio(RectF rect, float aspectRatio) {\n    rect.top = rect.bottom - rect.width() / aspectRatio;\n  }\n\n  /**\n   * Adjust right edge by current crop window height and the given aspect ratio, the left edge\n   * remains in possition while the left adjusts to keep aspect ratio to the height.\n   */\n  private void adjustRightByAspectRatio(RectF rect, float aspectRatio) {\n    rect.right = rect.left + rect.height() * aspectRatio;\n  }\n\n  /**\n   * Adjust bottom edge by current crop window width and the given aspect ratio, the top edge\n   * remains in possition while the top adjusts to keep aspect ratio to the width.\n   */\n  private void adjustBottomByAspectRatio(RectF rect, float aspectRatio) {\n    rect.bottom = rect.top + rect.width() / aspectRatio;\n  }\n\n  /**\n   * Adjust left and right edges by current crop window height and the given aspect ratio, both\n   * right and left edges adjusts equally relative to center to keep aspect ratio to the height.\n   */\n  private void adjustLeftRightByAspectRatio(RectF rect, RectF bounds, float aspectRatio) {\n    rect.inset((rect.width() - rect.height() * aspectRatio) / 2, 0);\n    if (rect.left < bounds.left) {\n      rect.offset(bounds.left - rect.left, 0);\n    }\n    if (rect.right > bounds.right) {\n      rect.offset(bounds.right - rect.right, 0);\n    }\n  }\n\n  /**\n   * Adjust top and bottom edges by current crop window width and the given aspect ratio, both top\n   * and bottom edges adjusts equally relative to center to keep aspect ratio to the width.\n   */\n  private void adjustTopBottomByAspectRatio(RectF rect, RectF bounds, float aspectRatio) {\n    rect.inset(0, (rect.height() - rect.width() / aspectRatio) / 2);\n    if (rect.top < bounds.top) {\n      rect.offset(0, bounds.top - rect.top);\n    }\n    if (rect.bottom > bounds.bottom) {\n      rect.offset(0, bounds.bottom - rect.bottom);\n    }\n  }\n\n  /** Calculates the aspect ratio given a rectangle. */\n  private static float calculateAspectRatio(float left, float top, float right, float bottom) {\n    return (right - left) / (bottom - top);\n  }\n  // endregion\n\n  // region: Inner class: Type\n\n  /** The type of crop window move that is handled. */\n  public enum Type {\n    TOP_LEFT,\n    TOP_RIGHT,\n    BOTTOM_LEFT,\n    BOTTOM_RIGHT,\n    LEFT,\n    TOP,\n    RIGHT,\n    BOTTOM,\n    CENTER\n  }\n  // endregion\n}\n"
  },
  {
    "path": "cropper/src/main/res/layout/crop_image_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"/>"
  },
  {
    "path": "cropper/src/main/res/layout/crop_image_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <ImageView\n        android:id=\"@+id/ImageView_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:adjustViewBounds=\"true\"\n        android:scaleType=\"centerInside\"\n        tools:ignore=\"contentDescription\"/>\n\n    <com.theartofdev.edmodo.cropper.CropOverlayView\n        android:id=\"@+id/CropOverlayView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"invisible\"/>\n\n    <ProgressBar\n        android:id=\"@+id/CropProgressBar\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"/>\n\n</merge>"
  },
  {
    "path": "cropper/src/main/res/menu/crop_image_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/crop_image_menu_rotate_left\"\n        android:icon=\"@drawable/crop_image_menu_rotate_left\"\n        android:title=\"@string/crop_image_menu_rotate_left\"\n        android:visible=\"false\"\n        app:showAsAction=\"ifRoom\"/>\n    <item\n        android:id=\"@+id/crop_image_menu_rotate_right\"\n        android:icon=\"@drawable/crop_image_menu_rotate_right\"\n        android:title=\"@string/crop_image_menu_rotate_right\"\n        app:showAsAction=\"ifRoom\"/>\n    <item\n        android:id=\"@+id/crop_image_menu_flip\"\n        android:icon=\"@drawable/crop_image_menu_flip\"\n        android:title=\"@string/crop_image_menu_flip\"\n        app:showAsAction=\"ifRoom\">\n        <menu>\n            <item\n                android:id=\"@+id/crop_image_menu_flip_horizontally\"\n                android:title=\"@string/crop_image_menu_flip_horizontally\"/>\n            <item\n                android:id=\"@+id/crop_image_menu_flip_vertically\"\n                android:title=\"@string/crop_image_menu_flip_vertically\"/>\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/crop_image_menu_crop\"\n        android:title=\"@string/crop_image_menu_crop\"\n        app:showAsAction=\"always\"/>\n\n</menu>"
  },
  {
    "path": "cropper/src/main/res/values/attrs.xml",
    "content": "<resources>\n\n    <declare-styleable name=\"CropImageView\">\n        <attr name=\"cropGuidelines\">\n            <enum name=\"off\" value=\"0\"/>\n            <enum name=\"onTouch\" value=\"1\"/>\n            <enum name=\"on\" value=\"2\"/>\n        </attr>\n        <attr name=\"cropScaleType\">\n            <enum name=\"fitCenter\" value=\"0\"/>\n            <enum name=\"center\" value=\"1\"/>\n            <enum name=\"centerCrop\" value=\"2\"/>\n            <enum name=\"centerInside\" value=\"3\"/>\n        </attr>\n        <attr name=\"cropShape\">\n            <enum name=\"rectangle\" value=\"0\"/>\n            <enum name=\"oval\" value=\"1\"/>\n        </attr>\n        <attr name=\"cropAutoZoomEnabled\" format=\"boolean\"/>\n        <attr name=\"cropMaxZoom\" format=\"integer\"/>\n        <attr name=\"cropMultiTouchEnabled\" format=\"boolean\"/>\n        <attr name=\"cropFixAspectRatio\" format=\"boolean\"/>\n        <attr name=\"cropAspectRatioX\" format=\"integer\"/>\n        <attr name=\"cropAspectRatioY\" format=\"integer\"/>\n        <attr name=\"cropInitialCropWindowPaddingRatio\" format=\"float\"/>\n        <attr name=\"cropBorderLineThickness\" format=\"dimension\"/>\n        <attr name=\"cropBorderLineColor\" format=\"color\"/>\n        <attr name=\"cropBorderCornerThickness\" format=\"dimension\"/>\n        <attr name=\"cropBorderCornerOffset\" format=\"dimension\"/>\n        <attr name=\"cropBorderCornerLength\" format=\"dimension\"/>\n        <attr name=\"cropBorderCornerColor\" format=\"color\"/>\n        <attr name=\"cropGuidelinesThickness\" format=\"dimension\"/>\n        <attr name=\"cropGuidelinesColor\" format=\"color\"/>\n        <attr name=\"cropBackgroundColor\" format=\"color\"/>\n        <attr name=\"cropSnapRadius\" format=\"dimension\"/>\n        <attr name=\"cropTouchRadius\" format=\"dimension\"/>\n        <attr name=\"cropSaveBitmapToInstanceState\" format=\"boolean\"/>\n        <attr name=\"cropShowCropOverlay\" format=\"boolean\"/>\n        <attr name=\"cropShowProgressBar\" format=\"boolean\"/>\n        <attr name=\"cropMinCropWindowWidth\" format=\"dimension\"/>\n        <attr name=\"cropMinCropWindowHeight\" format=\"dimension\"/>\n        <attr name=\"cropMinCropResultWidthPX\" format=\"float\"/>\n        <attr name=\"cropMinCropResultHeightPX\" format=\"float\"/>\n        <attr name=\"cropMaxCropResultWidthPX\" format=\"float\"/>\n        <attr name=\"cropMaxCropResultHeightPX\" format=\"float\"/>\n        <attr name=\"cropFlipHorizontally\" format=\"boolean\"/>\n        <attr name=\"cropFlipVertically\" format=\"boolean\"/>\n    </declare-styleable>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Rotate counter clockwise</string>\n    <string name=\"crop_image_menu_rotate_right\">Rotate</string>\n    <string name=\"crop_image_menu_crop\">Crop</string>\n    <string name=\"crop_image_menu_flip\">Flip</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Flip horizontally</string>\n    <string name=\"crop_image_menu_flip_vertically\">Flip vertically</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Select source</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Cancelling, required permissions are not granted</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">أدر عكس اتجاه عقارب الساعة</string>\n    <string name=\"crop_image_menu_rotate_right\">أدر</string>\n    <string name=\"crop_image_menu_crop\">قُصّ</string>\n    <string name=\"crop_image_menu_flip\">اقلب</string>\n    <string name=\"crop_image_menu_flip_horizontally\">اقلب أفقيًا</string>\n    <string name=\"crop_image_menu_flip_vertically\">اقلب رأسيًا</string>\n\n    <string name=\"pick_image_intent_chooser_title\">اختر مصدرًا</string>\n\n    <string name=\"crop_image_activity_no_permissions\">إلغاء؛ الأذونات المطلوبة غير ممنوحة</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-cs/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Otočit proti směru hodinových ručiček</string>\n    <string name=\"crop_image_menu_rotate_right\">Otočit</string>\n    <string name=\"crop_image_menu_crop\">Oříznout</string>\n    <string name=\"crop_image_menu_flip\">Překlopit</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Překlopit vodorovně</string>\n    <string name=\"crop_image_menu_flip_vertically\">Překlopit svisle</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Vybrat zdroj</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Probíhá storno, požadovaná povolení nejsou udělena</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">gegen den Uhrzeigersinn drehen</string>\n    <string name=\"crop_image_menu_rotate_right\">drehen</string>\n    <string name=\"crop_image_menu_crop\">zuschneiden</string>\n    <string name=\"crop_image_menu_flip\">spiegeln</string>\n    <string name=\"crop_image_menu_flip_horizontally\">horizontal spiegeln</string>\n    <string name=\"crop_image_menu_flip_vertically\">vertikal spiegeln</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Quelle wählen</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Vorgang wird abgebrochen, benötigte Berechtigungen wurden nicht erteilt.</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Rotar a la izquierda</string>\n    <string name=\"crop_image_menu_rotate_right\">Rotar a la derecha</string>\n    <string name=\"crop_image_menu_crop\">Cortar</string>\n    <string name=\"crop_image_menu_flip\">Dar la vuelta</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Voltear horizontalmente</string>\n    <string name=\"crop_image_menu_flip_vertically\">Voltear verticalmente</string>\n    <string name=\"pick_image_intent_chooser_title\">Seleccionar fuente</string>\n    <string name=\"crop_image_activity_no_permissions\">Cancelando, los permisos requeridos no han sido otorgados</string>\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-es-rGT/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Girar a la izquierda</string>\n    <string name=\"crop_image_menu_rotate_right\">Girar a la derecha</string>\n    <string name=\"crop_image_menu_crop\">Cortar</string>\n    <string name=\"crop_image_menu_flip\">Dar la vuelta</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Voltear horizontalmente</string>\n    <string name=\"crop_image_menu_flip_vertically\">Voltear verticalmente</string>\n    <string name=\"pick_image_intent_chooser_title\">Seleccionar fuente</string>\n    <string name=\"crop_image_activity_no_permissions\">Cancelando, los permisos requeridos no se otorgaron</string>\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-fa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">چرخش در جهت عقربه های ساعت</string>\n    <string name=\"crop_image_menu_rotate_right\">چرخش</string>\n    <string name=\"crop_image_menu_crop\">بریدن (کراپ)</string>\n    <string name=\"crop_image_menu_flip\">آیینه کردن</string>\n    <string name=\"crop_image_menu_flip_horizontally\">آیینه کردن به صورت افقی</string>\n    <string name=\"crop_image_menu_flip_vertically\">آیینه کردن به صورت عمودی</string>\n    <string name=\"pick_image_intent_chooser_title\">منبع را انتخاب کنید</string>\n    <string name=\"crop_image_activity_no_permissions\">لغو، مجوزهای مورد نیاز ارائه نشده</string>\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Pivoter à gauche</string>\n    <string name=\"crop_image_menu_rotate_right\">Pivoter à droite</string>\n    <string name=\"crop_image_menu_crop\">Redimensionner</string>\n    <string name=\"crop_image_menu_flip\">Retourner</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Retourner horizontalement</string>\n    <string name=\"crop_image_menu_flip_vertically\">Retourner verticalement</string>\n    <string name=\"pick_image_intent_chooser_title\">Sélectionner la source</string>\n    <string name=\"crop_image_activity_no_permissions\">Annulation, il manque des permissions requises</string>\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-he/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">סובב נגד כיוון השעון</string>\n    <string name=\"crop_image_menu_rotate_right\">סובב</string>\n    <string name=\"crop_image_menu_crop\">חתוך</string>\n    <string name=\"crop_image_menu_flip\">הפוך</string>\n    <string name=\"crop_image_menu_flip_horizontally\">הפוך אופקית</string>\n    <string name=\"crop_image_menu_flip_vertically\">הפוך אנכית</string>\n\n    <string name=\"pick_image_intent_chooser_title\">בחר מקור</string>\n\n    <string name=\"crop_image_activity_no_permissions\">ההרשאות הנדרשות חסרות, מבטל</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-hi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">घड़ी की सुई के विपरीत दिशा में घुमाइए</string>\n    <string name=\"crop_image_menu_rotate_right\">घुमाएँ</string>\n    <string name=\"crop_image_menu_crop\">फ़सल</string>\n    <string name=\"crop_image_menu_flip\">फ्लिप</string>\n    <string name=\"crop_image_menu_flip_horizontally\">क्षैतिज फ्लिप</string>\n    <string name=\"crop_image_menu_flip_vertically\">लंबवत फ्लिप करें</string>\n    <string name=\"pick_image_intent_chooser_title\">सोर्स चुनें</string>\n    <string name=\"crop_image_activity_no_permissions\">रद्द करना, आवश्यक अनुमतियां नहीं दी गई हैं</string>\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-id/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">Putar berlawanan arah jarum jam</string>\n    <string name=\"crop_image_menu_rotate_right\">Putar</string>\n    <string name=\"crop_image_menu_crop\">Potong</string>\n    <string name=\"crop_image_menu_flip\">Balik</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Balik secara horizontal</string>\n    <string name=\"crop_image_menu_flip_vertically\">Balik secara vertikal</string>\n    <string name=\"pick_image_intent_chooser_title\">Pilih sumber</string>\n    <string name=\"crop_image_activity_no_permissions\">Membatalkan, tidak mendapatkan izin yang diperlukan</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">Putar berlawanan arah jarum jam</string>\n    <string name=\"crop_image_menu_rotate_right\">Putar</string>\n    <string name=\"crop_image_menu_crop\">Potong</string>\n    <string name=\"crop_image_menu_flip\">Balik</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Balik secara horizontal</string>\n    <string name=\"crop_image_menu_flip_vertically\">Balik secara vertikal</string>\n    <string name=\"pick_image_intent_chooser_title\">Pilih sumber</string>\n    <string name=\"crop_image_activity_no_permissions\">Membatalkan, tidak mendapatkan izin yang diperlukan</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Ruota in senso antiorario</string>\n    <string name=\"crop_image_menu_rotate_right\">Ruota</string>\n    <string name=\"crop_image_menu_crop\">Ritaglia</string>\n    <string name=\"crop_image_menu_flip\">Capovolgi</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Capovolgi orizzontalmente</string>\n    <string name=\"crop_image_menu_flip_vertically\">Capovolgi verticalmente</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Seleziona origine</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Annullamento in corso, autorizzazione richieste non concesse</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">左回転</string>\n    <string name=\"crop_image_menu_rotate_right\">右回転</string>\n    <string name=\"crop_image_menu_crop\">切り取り</string>\n    <string name=\"crop_image_menu_flip\">反転</string>\n    <string name=\"crop_image_menu_flip_horizontally\">左右反転</string>\n    <string name=\"crop_image_menu_flip_vertically\">上下反転</string>\n    <string name=\"pick_image_intent_chooser_title\">画像を選択</string>\n    <string name=\"crop_image_activity_no_permissions\">必要な権限がありません、キャンセルしています。</string>\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">반시계 회전</string>\n    <string name=\"crop_image_menu_rotate_right\">회전</string>\n    <string name=\"crop_image_menu_crop\">자르기</string>\n    <string name=\"crop_image_menu_flip\">반전</string>\n    <string name=\"crop_image_menu_flip_horizontally\">좌우반전</string>\n    <string name=\"crop_image_menu_flip_vertically\">상하반전</string>\n\n    <string name=\"pick_image_intent_chooser_title\">이미지 선택</string>\n\n    <string name=\"crop_image_activity_no_permissions\">필수 권한이 없어서 취소합니다.</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-ms/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">Putar arah berlawanan jam</string>\n    <string name=\"crop_image_menu_rotate_right\">Putar</string>\n    <string name=\"crop_image_menu_crop\">Potong</string>\n    <string name=\"crop_image_menu_flip\">Flip</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Flip melintang</string>\n    <string name=\"crop_image_menu_flip_vertically\">Flip menegak</string>\n    <string name=\"pick_image_intent_chooser_title\">Pilih sumber</string>\n    <string name=\"crop_image_activity_no_permissions\">Membatal, tidak mendapat kebenaran yang diperlukan</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-nb/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Roter teller med urviseren</string>\n    <string name=\"crop_image_menu_rotate_right\">Roter</string>\n    <string name=\"crop_image_menu_crop\">Beskjær</string>\n    <string name=\"crop_image_menu_flip\">Vend</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Vend vannrett</string>\n    <string name=\"crop_image_menu_flip_vertically\">Vend loddrett</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Velg kilde</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Avbryter, nødvendige tillatelser er ikke gitt</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-nl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Tegen de klok in draaien</string>\n    <string name=\"crop_image_menu_rotate_right\">Draaien</string>\n    <string name=\"crop_image_menu_crop\">Bijsnijden</string>\n    <string name=\"crop_image_menu_flip\">Spiegelen</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Horizontaal spiegelen</string>\n    <string name=\"crop_image_menu_flip_vertically\">Verticaal spiegelen</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Bron selecteren</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Wordt geannuleerd, vereiste machtigingen zijn niet toegekend</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Obróć w lewo</string>\n    <string name=\"crop_image_menu_rotate_right\">Obróć</string>\n    <string name=\"crop_image_menu_crop\">Przytnij</string>\n    <string name=\"crop_image_menu_flip\">Odbij</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Odbij poziomo</string>\n    <string name=\"crop_image_menu_flip_vertically\">Odbij pionowo</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Wybierz źródło</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Przerywaniem, potrzebne uprawnienia nie zostały nadane</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Girar para a esquerda</string>\n    <string name=\"crop_image_menu_rotate_right\">Girar para a direita</string>\n    <string name=\"crop_image_menu_crop\">Cortar</string>\n    <string name=\"crop_image_menu_flip\">Espelhar</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Espelhar na horizontal</string>\n    <string name=\"crop_image_menu_flip_vertically\">Espelhar na vertifcal</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Escolher foto a partir de</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-ru-rRU/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">Повернуть налево</string>\n    <string name=\"crop_image_menu_rotate_right\">Повернуть направо</string>\n    <string name=\"crop_image_menu_crop\">Обрезать</string>\n    <string name=\"crop_image_menu_flip\">Отразить</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Отразить по горизонтали</string>\n    <string name=\"crop_image_menu_flip_vertically\">Отразить по вертикали</string>\n    <string name=\"pick_image_intent_chooser_title\">Выбрать источник</string>\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-sv/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Rotera vänster</string>\n    <string name=\"crop_image_menu_rotate_right\">Rotera höger</string>\n    <string name=\"crop_image_menu_crop\">Beskär</string>\n    <string name=\"crop_image_menu_flip\">Vänd</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Vänd horisontellt</string>\n    <string name=\"crop_image_menu_flip_vertically\">Vänd vertikalt</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Välj bild</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Avbryter, nödvändiga behörigheter beviljas inte</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">Saat yönünde döndür</string>\n    <string name=\"crop_image_menu_rotate_right\">döndürmek</string>\n    <string name=\"crop_image_menu_crop\">ekin</string>\n    <string name=\"crop_image_menu_flip\">fiske</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Yatay olarak çevir</string>\n    <string name=\"crop_image_menu_flip_vertically\">Dikey olarak çevir</string>\n    <string name=\"pick_image_intent_chooser_title\">Kaynağı seçin</string>\n    <string name=\"crop_image_activity_no_permissions\">İptal ediliyor, gerekli izinler verilmiyor</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-ur/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"crop_image_menu_rotate_left\">گھڑی وار گھڑی گھومیں</string>\n    <string name=\"crop_image_menu_rotate_right\">گھمائیں</string>\n    <string name=\"crop_image_menu_crop\">فصل</string>\n    <string name=\"crop_image_menu_flip\">پلٹائیں</string>\n    <string name=\"crop_image_menu_flip_horizontally\">افقی پلٹائیں</string>\n    <string name=\"crop_image_menu_flip_vertically\">عمودی طور پر پلٹائیں</string>\n    <string name=\"pick_image_intent_chooser_title\">ذریعہ منتخب کریں</string>\n    <string name=\"crop_image_activity_no_permissions\">منسوخ کرنا، ضروری اجازت نہیں دی جاتی ہیں</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">Xoay theo chiều kim đồng hồ</string>\n    <string name=\"crop_image_menu_rotate_right\">Xoay</string>\n    <string name=\"crop_image_menu_crop\">Cắt</string>\n    <string name=\"crop_image_menu_flip\">Lật</string>\n    <string name=\"crop_image_menu_flip_horizontally\">Lật theo chiều ngang</string>\n    <string name=\"crop_image_menu_flip_vertically\">Lật theo chiều dọc</string>\n\n    <string name=\"pick_image_intent_chooser_title\">Chọn nguồn</string>\n\n    <string name=\"crop_image_activity_no_permissions\">Đang hủy, các quyền đã yêu cầu không được cấp</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-zh/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">逆时针旋转</string>\n    <string name=\"crop_image_menu_rotate_right\">旋转</string>\n    <string name=\"crop_image_menu_crop\">裁剪</string>\n    <string name=\"crop_image_menu_flip\">翻转</string>\n    <string name=\"crop_image_menu_flip_horizontally\">水平翻转</string>\n    <string name=\"crop_image_menu_flip_vertically\">垂直翻转</string>\n\n    <string name=\"pick_image_intent_chooser_title\">选择来源</string>\n\n    <string name=\"crop_image_activity_no_permissions\">正在取消，该操作未获得所需权限。</string>\n\n</resources>\n"
  },
  {
    "path": "cropper/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">逆时针旋转</string>\n    <string name=\"crop_image_menu_rotate_right\">旋转</string>\n    <string name=\"crop_image_menu_crop\">裁切</string>\n    <string name=\"crop_image_menu_flip\">翻转</string>\n    <string name=\"crop_image_menu_flip_horizontally\">水平翻转</string>\n    <string name=\"crop_image_menu_flip_vertically\">垂直翻转</string>\n\n    <string name=\"pick_image_intent_chooser_title\">选择来源</string>\n\n    <string name=\"crop_image_activity_no_permissions\">取消中，未授予所需权限</string>\n\n</resources>"
  },
  {
    "path": "cropper/src/main/res/values-zh-rTW/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"crop_image_activity_title\"></string>\n    <string name=\"crop_image_menu_rotate_left\">逆時針旋轉</string>\n    <string name=\"crop_image_menu_rotate_right\">旋轉</string>\n    <string name=\"crop_image_menu_crop\">裁切</string>\n    <string name=\"crop_image_menu_flip\">翻轉</string>\n    <string name=\"crop_image_menu_flip_horizontally\">水平翻轉</string>\n    <string name=\"crop_image_menu_flip_vertically\">垂直翻轉</string>\n\n    <string name=\"pick_image_intent_chooser_title\">選擇來源</string>\n\n    <string name=\"crop_image_activity_no_permissions\">取消中，未授予所需權限</string>\n\n</resources>"
  },
  {
    "path": "gradle/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Thu Jan 16 17:26:55 PST 2014\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=http\\://services.gradle.org/distributions/gradle-1.8-bin.zip\n"
  },
  {
    "path": "gradle/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradle/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Fri Apr 06 15:20:13 IDT 2018\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-4.6-all.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "#\n# Copyright (c) 2018. DNA Software. All rights reserved.\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\nandroid.enableJetifier=true\nandroid.useAndroidX=true"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "local.sh",
    "content": "#!/bin/sh\ngradle clean build publishToMavenLocal\n"
  },
  {
    "path": "quick-start/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.compileSdkVersion\n    buildToolsVersion rootProject.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.compileSdkVersion\n        versionCode 1\n        versionName '1.0'\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    api project(':cropper')\n    api \"androidx.appcompat:appcompat:$androidXLibraryVersion\"\n}\n"
  },
  {
    "path": "quick-start/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"com.theartofdev.edmodo.cropper.quick.start\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.CAMERA\"/>\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/Base.Theme.AppCompat\">\n        <activity\n            android:name=\".MainActivity\"\n            android:label=\"@string/app_name\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.theartofdev.edmodo.cropper.CropImageActivity\"/>\n    </application>\n</manifest>\n"
  },
  {
    "path": "quick-start/src/main/java/com/theartofdev/edmodo/cropper/quick/start/MainActivity.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.quick.start;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.Toast;\n\nimport com.theartofdev.edmodo.cropper.CropImage;\nimport com.theartofdev.edmodo.cropper.CropImageView;\n\npublic class MainActivity extends AppCompatActivity {\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n  }\n\n  /** Start pick image activity with chooser. */\n  public void onSelectImageClick(View view) {\n    CropImage.activity()\n        .setGuidelines(CropImageView.Guidelines.ON)\n        .setActivityTitle(\"My Crop\")\n        .setCropShape(CropImageView.CropShape.OVAL)\n        .setCropMenuCropButtonTitle(\"Done\")\n        .setRequestedSize(400, 400)\n        .setCropMenuCropButtonIcon(R.drawable.ic_launcher)\n        .start(this);\n  }\n\n  @Override\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n\n    // handle result of CropImageActivity\n    if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {\n      CropImage.ActivityResult result = CropImage.getActivityResult(data);\n      if (resultCode == RESULT_OK) {\n        ((ImageView) findViewById(R.id.quick_start_cropped_image)).setImageURI(result.getUri());\n        Toast.makeText(\n                this, \"Cropping successful, Sample: \" + result.getSampleSize(), Toast.LENGTH_LONG)\n            .show();\n      } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {\n        Toast.makeText(this, \"Cropping failed: \" + result.getError(), Toast.LENGTH_LONG).show();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "quick-start/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@android:color/black\">\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"#7000\"\n        android:padding=\"12dp\"\n        android:text=\"@string/main_activity_desciption\"/>\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_margin=\"12dp\"\n        android:onClick=\"onSelectImageClick\"\n        android:text=\"@string/main_button_select_image\"/>\n\n    <ImageView\n        android:id=\"@+id/quick_start_cropped_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:contentDescription=\"@string/result_image_desc\"\n        android:src=\"@drawable/placeholder\"/>\n\n</LinearLayout>\n"
  },
  {
    "path": "quick-start/src/main/res/values/dimens.xml",
    "content": "<resources>\n\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">12dp</dimen>\n    <dimen name=\"activity_vertical_margin\">12dp</dimen>\n\n</resources>"
  },
  {
    "path": "quick-start/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\">Image Cropper Quick Start</string>\n    <string name=\"result_image_desc\">crop result</string>\n    <string name=\"main_button_select_image\">Select Image</string>\n    <string name=\"main_activity_desciption\">Sample activity to start image cropping.\\nExample: User profile activity to select avatar.</string>\n</resources>\n"
  },
  {
    "path": "quick-start/src/main/res/values/styles.xml",
    "content": "<resources>\n\n</resources>"
  },
  {
    "path": "release.sh",
    "content": "#!/bin/sh\ngradlew clean build generateRelease\n"
  },
  {
    "path": "sample/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.compileSdkVersion\n    buildToolsVersion rootProject.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.compileSdkVersion\n        versionCode 1\n        versionName '1.0'\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    api project(':cropper')\n    api \"androidx.appcompat:appcompat:$androidXLibraryVersion\"\n}\n"
  },
  {
    "path": "sample/project.properties",
    "content": "# This file is automatically generated by Android Tools.\n# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n#\n# This file must be checked in Version Control Systems.\n#\n# To customize properties used by the Ant build system edit\n# \"ant.properties\", and override values to adapt the script to your\n# project structure.\n#\n# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):\n#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt\n# Project target.\ntarget=android-17\nandroid.library.reference.1=../cropper\n"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest\n    package=\"com.example.croppersample\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.CAMERA\"/>\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n\n    <application\n        android:allowBackup=\"false\"\n        android:icon=\"@drawable/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/Theme.AppCompat\">\n        <activity\n            android:name=\"com.theartofdev.edmodo.cropper.sample.MainActivity\"\n            android:label=\"@string/app_title\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.theartofdev.edmodo.cropper.sample.CropResultActivity\">\n        </activity>\n        <activity android:name=\"com.theartofdev.edmodo.cropper.CropImageActivity\">\n        </activity>\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropDemoPreset.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.sample;\n\nenum CropDemoPreset {\n  RECT,\n  CIRCULAR,\n  CUSTOMIZED_OVERLAY,\n  MIN_MAX_OVERRIDE,\n  SCALE_CENTER_INSIDE,\n  CUSTOM\n}\n"
  },
  {
    "path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropImageViewOptions.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.sample;\n\nimport android.util.Pair;\n\nimport com.theartofdev.edmodo.cropper.CropImageView;\n\n/** The crop image view options that can be changed live. */\nfinal class CropImageViewOptions {\n\n  public CropImageView.ScaleType scaleType = CropImageView.ScaleType.CENTER_INSIDE;\n\n  public CropImageView.CropShape cropShape = CropImageView.CropShape.RECTANGLE;\n\n  public CropImageView.Guidelines guidelines = CropImageView.Guidelines.ON_TOUCH;\n\n  public Pair<Integer, Integer> aspectRatio = new Pair<>(1, 1);\n\n  public boolean autoZoomEnabled;\n\n  public int maxZoomLevel;\n\n  public boolean fixAspectRatio;\n\n  public boolean multitouch;\n\n  public boolean showCropOverlay;\n\n  public boolean showProgressBar;\n\n  public boolean flipHorizontally;\n\n  public boolean flipVertically;\n}\n"
  },
  {
    "path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/CropResultActivity.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.sample;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Bitmap;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport android.view.View;\nimport android.view.Window;\nimport android.widget.ImageView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.example.croppersample.R;\n\npublic final class CropResultActivity extends Activity {\n\n  /** The image to show in the activity. */\n  static Bitmap mImage;\n\n  private ImageView imageView;\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    requestWindowFeature(Window.FEATURE_NO_TITLE);\n    setContentView(R.layout.activity_crop_result);\n\n    imageView = ((ImageView) findViewById(R.id.resultImageView));\n    imageView.setBackgroundResource(R.drawable.backdrop);\n\n    Intent intent = getIntent();\n    if (mImage != null) {\n      imageView.setImageBitmap(mImage);\n      int sampleSize = intent.getIntExtra(\"SAMPLE_SIZE\", 1);\n      double ratio = ((int) (10 * mImage.getWidth() / (double) mImage.getHeight())) / 10d;\n      int byteCount = 0;\n      if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {\n        byteCount = mImage.getByteCount() / 1024;\n      }\n      String desc =\n          \"(\"\n              + mImage.getWidth()\n              + \", \"\n              + mImage.getHeight()\n              + \"), Sample: \"\n              + sampleSize\n              + \", Ratio: \"\n              + ratio\n              + \", Bytes: \"\n              + byteCount\n              + \"K\";\n      ((TextView) findViewById(R.id.resultImageText)).setText(desc);\n    } else {\n      Uri imageUri = intent.getParcelableExtra(\"URI\");\n      if (imageUri != null) {\n        imageView.setImageURI(imageUri);\n      } else {\n        Toast.makeText(this, \"No image is set to show\", Toast.LENGTH_LONG).show();\n      }\n    }\n  }\n\n  @Override\n  public void onBackPressed() {\n    releaseBitmap();\n    super.onBackPressed();\n  }\n\n  public void onImageViewClicked(View view) {\n    releaseBitmap();\n    finish();\n  }\n\n  private void releaseBitmap() {\n    if (mImage != null) {\n      mImage.recycle();\n      mImage = null;\n    }\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainActivity.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.sample;\n\nimport android.Manifest;\nimport android.annotation.SuppressLint;\nimport android.content.Intent;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.drawerlayout.widget.DrawerLayout;\nimport androidx.appcompat.app.ActionBarDrawerToggle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.util.Pair;\nimport android.view.Menu;\nimport android.view.MenuInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport com.example.croppersample.R;\nimport com.theartofdev.edmodo.cropper.CropImage;\nimport com.theartofdev.edmodo.cropper.CropImageView;\n\npublic class MainActivity extends AppCompatActivity {\n\n  // region: Fields and Consts\n\n  DrawerLayout mDrawerLayout;\n\n  private ActionBarDrawerToggle mDrawerToggle;\n\n  private MainFragment mCurrentFragment;\n\n  private Uri mCropImageUri;\n\n  private CropImageViewOptions mCropImageViewOptions = new CropImageViewOptions();\n  // endregion\n\n  public void setCurrentFragment(MainFragment fragment) {\n    mCurrentFragment = fragment;\n  }\n\n  public void setCurrentOptions(CropImageViewOptions options) {\n    mCropImageViewOptions = options;\n    updateDrawerTogglesByOptions(options);\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n\n    getSupportActionBar().setDisplayHomeAsUpEnabled(true);\n    getSupportActionBar().setHomeButtonEnabled(true);\n\n    mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);\n\n    mDrawerToggle =\n        new ActionBarDrawerToggle(\n            this, mDrawerLayout, R.string.main_drawer_open, R.string.main_drawer_close);\n    mDrawerToggle.setDrawerIndicatorEnabled(true);\n    mDrawerLayout.setDrawerListener(mDrawerToggle);\n\n    if (savedInstanceState == null) {\n      setMainFragmentByPreset(CropDemoPreset.RECT);\n    }\n  }\n\n  @Override\n  protected void onPostCreate(Bundle savedInstanceState) {\n    super.onPostCreate(savedInstanceState);\n    mDrawerToggle.syncState();\n    mCurrentFragment.updateCurrentCropViewOptions();\n  }\n\n  @Override\n  public boolean onCreateOptionsMenu(Menu menu) {\n    MenuInflater inflater = getMenuInflater();\n    inflater.inflate(R.menu.main, menu);\n    return true;\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    if (mDrawerToggle.onOptionsItemSelected(item)) {\n      return true;\n    }\n    if (mCurrentFragment != null && mCurrentFragment.onOptionsItemSelected(item)) {\n      return true;\n    }\n    return super.onOptionsItemSelected(item);\n  }\n\n  @Override\n  @SuppressLint(\"NewApi\")\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n    super.onActivityResult(requestCode, resultCode, data);\n\n    if (requestCode == CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE\n        && resultCode == AppCompatActivity.RESULT_OK) {\n      Uri imageUri = CropImage.getPickImageResultUri(this, data);\n\n      // For API >= 23 we need to check specifically that we have permissions to read external\n      // storage,\n      // but we don't know if we need to for the URI so the simplest is to try open the stream and\n      // see if we get error.\n      boolean requirePermissions = false;\n      if (CropImage.isReadExternalStoragePermissionsRequired(this, imageUri)) {\n\n        // request permissions and handle the result in onRequestPermissionsResult()\n        requirePermissions = true;\n        mCropImageUri = imageUri;\n        requestPermissions(\n            new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},\n            CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE);\n      } else {\n\n        mCurrentFragment.setImageUri(imageUri);\n      }\n    }\n  }\n\n  @Override\n  public void onRequestPermissionsResult(\n      int requestCode, String permissions[], int[] grantResults) {\n    if (requestCode == CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE) {\n      if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n        CropImage.startPickImageActivity(this);\n      } else {\n        Toast.makeText(this, \"Cancelling, required permissions are not granted\", Toast.LENGTH_LONG)\n            .show();\n      }\n    }\n    if (requestCode == CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE) {\n      if (mCropImageUri != null\n          && grantResults.length > 0\n          && grantResults[0] == PackageManager.PERMISSION_GRANTED) {\n        mCurrentFragment.setImageUri(mCropImageUri);\n      } else {\n        Toast.makeText(this, \"Cancelling, required permissions are not granted\", Toast.LENGTH_LONG)\n            .show();\n      }\n    }\n  }\n\n  @SuppressLint(\"NewApi\")\n  public void onDrawerOptionClicked(View view) {\n    switch (view.getId()) {\n      case R.id.drawer_option_load:\n        if (CropImage.isExplicitCameraPermissionRequired(this)) {\n          requestPermissions(\n              new String[] {Manifest.permission.CAMERA},\n              CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE);\n        } else {\n          CropImage.startPickImageActivity(this);\n        }\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_oval:\n        setMainFragmentByPreset(CropDemoPreset.CIRCULAR);\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_rect:\n        setMainFragmentByPreset(CropDemoPreset.RECT);\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_customized_overlay:\n        setMainFragmentByPreset(CropDemoPreset.CUSTOMIZED_OVERLAY);\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_min_max_override:\n        setMainFragmentByPreset(CropDemoPreset.MIN_MAX_OVERRIDE);\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_scale_center:\n        setMainFragmentByPreset(CropDemoPreset.SCALE_CENTER_INSIDE);\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_toggle_scale:\n        mCropImageViewOptions.scaleType =\n            mCropImageViewOptions.scaleType == CropImageView.ScaleType.FIT_CENTER\n                ? CropImageView.ScaleType.CENTER_INSIDE\n                : mCropImageViewOptions.scaleType == CropImageView.ScaleType.CENTER_INSIDE\n                    ? CropImageView.ScaleType.CENTER\n                    : mCropImageViewOptions.scaleType == CropImageView.ScaleType.CENTER\n                        ? CropImageView.ScaleType.CENTER_CROP\n                        : CropImageView.ScaleType.FIT_CENTER;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_shape:\n        mCropImageViewOptions.cropShape =\n            mCropImageViewOptions.cropShape == CropImageView.CropShape.RECTANGLE\n                ? CropImageView.CropShape.OVAL\n                : CropImageView.CropShape.RECTANGLE;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_guidelines:\n        mCropImageViewOptions.guidelines =\n            mCropImageViewOptions.guidelines == CropImageView.Guidelines.OFF\n                ? CropImageView.Guidelines.ON\n                : mCropImageViewOptions.guidelines == CropImageView.Guidelines.ON\n                    ? CropImageView.Guidelines.ON_TOUCH\n                    : CropImageView.Guidelines.OFF;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_aspect_ratio:\n        if (!mCropImageViewOptions.fixAspectRatio) {\n          mCropImageViewOptions.fixAspectRatio = true;\n          mCropImageViewOptions.aspectRatio = new Pair<>(1, 1);\n        } else {\n          if (mCropImageViewOptions.aspectRatio.first == 1\n              && mCropImageViewOptions.aspectRatio.second == 1) {\n            mCropImageViewOptions.aspectRatio = new Pair<>(4, 3);\n          } else if (mCropImageViewOptions.aspectRatio.first == 4\n              && mCropImageViewOptions.aspectRatio.second == 3) {\n            mCropImageViewOptions.aspectRatio = new Pair<>(16, 9);\n          } else if (mCropImageViewOptions.aspectRatio.first == 16\n              && mCropImageViewOptions.aspectRatio.second == 9) {\n            mCropImageViewOptions.aspectRatio = new Pair<>(9, 16);\n          } else {\n            mCropImageViewOptions.fixAspectRatio = false;\n          }\n        }\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_auto_zoom:\n        mCropImageViewOptions.autoZoomEnabled = !mCropImageViewOptions.autoZoomEnabled;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_max_zoom:\n        mCropImageViewOptions.maxZoomLevel =\n            mCropImageViewOptions.maxZoomLevel == 4\n                ? 8\n                : mCropImageViewOptions.maxZoomLevel == 8 ? 2 : 4;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_set_initial_crop_rect:\n        mCurrentFragment.setInitialCropRect();\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_reset_crop_rect:\n        mCurrentFragment.resetCropRect();\n        mDrawerLayout.closeDrawers();\n        break;\n      case R.id.drawer_option_toggle_multitouch:\n        mCropImageViewOptions.multitouch = !mCropImageViewOptions.multitouch;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_show_overlay:\n        mCropImageViewOptions.showCropOverlay = !mCropImageViewOptions.showCropOverlay;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      case R.id.drawer_option_toggle_show_progress_bar:\n        mCropImageViewOptions.showProgressBar = !mCropImageViewOptions.showProgressBar;\n        mCurrentFragment.setCropImageViewOptions(mCropImageViewOptions);\n        updateDrawerTogglesByOptions(mCropImageViewOptions);\n        break;\n      default:\n        Toast.makeText(this, \"Unknown drawer option clicked\", Toast.LENGTH_LONG).show();\n    }\n  }\n\n  private void setMainFragmentByPreset(CropDemoPreset demoPreset) {\n    FragmentManager fragmentManager = getSupportFragmentManager();\n    fragmentManager\n        .beginTransaction()\n        .replace(R.id.container, MainFragment.newInstance(demoPreset))\n        .commit();\n  }\n\n  private void updateDrawerTogglesByOptions(CropImageViewOptions options) {\n    ((TextView) findViewById(R.id.drawer_option_toggle_scale))\n        .setText(\n            getResources()\n                .getString(R.string.drawer_option_toggle_scale, options.scaleType.name()));\n    ((TextView) findViewById(R.id.drawer_option_toggle_shape))\n        .setText(\n            getResources()\n                .getString(R.string.drawer_option_toggle_shape, options.cropShape.name()));\n    ((TextView) findViewById(R.id.drawer_option_toggle_guidelines))\n        .setText(\n            getResources()\n                .getString(R.string.drawer_option_toggle_guidelines, options.guidelines.name()));\n    ((TextView) findViewById(R.id.drawer_option_toggle_multitouch))\n        .setText(\n            getResources()\n                .getString(\n                    R.string.drawer_option_toggle_multitouch,\n                    Boolean.toString(options.multitouch)));\n    ((TextView) findViewById(R.id.drawer_option_toggle_show_overlay))\n        .setText(\n            getResources()\n                .getString(\n                    R.string.drawer_option_toggle_show_overlay,\n                    Boolean.toString(options.showCropOverlay)));\n    ((TextView) findViewById(R.id.drawer_option_toggle_show_progress_bar))\n        .setText(\n            getResources()\n                .getString(\n                    R.string.drawer_option_toggle_show_progress_bar,\n                    Boolean.toString(options.showProgressBar)));\n\n    String aspectRatio = \"FREE\";\n    if (options.fixAspectRatio) {\n      aspectRatio = options.aspectRatio.first + \":\" + options.aspectRatio.second;\n    }\n    ((TextView) findViewById(R.id.drawer_option_toggle_aspect_ratio))\n        .setText(getResources().getString(R.string.drawer_option_toggle_aspect_ratio, aspectRatio));\n\n    ((TextView) findViewById(R.id.drawer_option_toggle_auto_zoom))\n        .setText(\n            getResources()\n                .getString(\n                    R.string.drawer_option_toggle_auto_zoom,\n                    options.autoZoomEnabled ? \"Enabled\" : \"Disabled\"));\n    ((TextView) findViewById(R.id.drawer_option_toggle_max_zoom))\n        .setText(\n            getResources().getString(R.string.drawer_option_toggle_max_zoom, options.maxZoomLevel));\n  }\n}\n"
  },
  {
    "path": "sample/src/main/java/com/theartofdev/edmodo/cropper/sample/MainFragment.java",
    "content": "// \"Therefore those skilled at the unorthodox\n// are infinite as heaven and earth,\n// inexhaustible as the great rivers.\n// When they come to an end,\n// they begin again,\n// like the days and months;\n// they die and are reborn,\n// like the four seasons.\"\n//\n// - Sun Tsu,\n// \"The Art of War\"\n\npackage com.theartofdev.edmodo.cropper.sample;\n\nimport android.app.Activity;\nimport android.content.Intent;\nimport android.graphics.Rect;\nimport android.net.Uri;\nimport android.os.Bundle;\nimport androidx.fragment.app.Fragment;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.MenuItem;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.Toast;\n\nimport com.example.croppersample.R;\nimport com.theartofdev.edmodo.cropper.CropImage;\nimport com.theartofdev.edmodo.cropper.CropImageView;\n\n/** The fragment that will show the Image Cropping UI by requested preset. */\npublic final class MainFragment extends Fragment\n    implements CropImageView.OnSetImageUriCompleteListener,\n        CropImageView.OnCropImageCompleteListener {\n\n  // region: Fields and Consts\n\n  private CropDemoPreset mDemoPreset;\n\n  private CropImageView mCropImageView;\n  // endregion\n\n  /** Returns a new instance of this fragment for the given section number. */\n  public static MainFragment newInstance(CropDemoPreset demoPreset) {\n    MainFragment fragment = new MainFragment();\n    Bundle args = new Bundle();\n    args.putString(\"DEMO_PRESET\", demoPreset.name());\n    fragment.setArguments(args);\n    return fragment;\n  }\n\n  /** Set the image to show for cropping. */\n  public void setImageUri(Uri imageUri) {\n    mCropImageView.setImageUriAsync(imageUri);\n    //        CropImage.activity(imageUri)\n    //                .start(getContext(), this);\n  }\n\n  /** Set the options of the crop image view to the given values. */\n  public void setCropImageViewOptions(CropImageViewOptions options) {\n    mCropImageView.setScaleType(options.scaleType);\n    mCropImageView.setCropShape(options.cropShape);\n    mCropImageView.setGuidelines(options.guidelines);\n    mCropImageView.setAspectRatio(options.aspectRatio.first, options.aspectRatio.second);\n    mCropImageView.setFixedAspectRatio(options.fixAspectRatio);\n    mCropImageView.setMultiTouchEnabled(options.multitouch);\n    mCropImageView.setShowCropOverlay(options.showCropOverlay);\n    mCropImageView.setShowProgressBar(options.showProgressBar);\n    mCropImageView.setAutoZoomEnabled(options.autoZoomEnabled);\n    mCropImageView.setMaxZoom(options.maxZoomLevel);\n    mCropImageView.setFlippedHorizontally(options.flipHorizontally);\n    mCropImageView.setFlippedVertically(options.flipVertically);\n  }\n\n  /** Set the initial rectangle to use. */\n  public void setInitialCropRect() {\n    mCropImageView.setCropRect(new Rect(100, 300, 500, 1200));\n  }\n\n  /** Reset crop window to initial rectangle. */\n  public void resetCropRect() {\n    mCropImageView.resetCropRect();\n  }\n\n  public void updateCurrentCropViewOptions() {\n    CropImageViewOptions options = new CropImageViewOptions();\n    options.scaleType = mCropImageView.getScaleType();\n    options.cropShape = mCropImageView.getCropShape();\n    options.guidelines = mCropImageView.getGuidelines();\n    options.aspectRatio = mCropImageView.getAspectRatio();\n    options.fixAspectRatio = mCropImageView.isFixAspectRatio();\n    options.showCropOverlay = mCropImageView.isShowCropOverlay();\n    options.showProgressBar = mCropImageView.isShowProgressBar();\n    options.autoZoomEnabled = mCropImageView.isAutoZoomEnabled();\n    options.maxZoomLevel = mCropImageView.getMaxZoom();\n    options.flipHorizontally = mCropImageView.isFlippedHorizontally();\n    options.flipVertically = mCropImageView.isFlippedVertically();\n    ((MainActivity) getActivity()).setCurrentOptions(options);\n  }\n\n  @Override\n  public View onCreateView(\n      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n    View rootView;\n    switch (mDemoPreset) {\n      case RECT:\n        rootView = inflater.inflate(R.layout.fragment_main_rect, container, false);\n        break;\n      case CIRCULAR:\n        rootView = inflater.inflate(R.layout.fragment_main_oval, container, false);\n        break;\n      case CUSTOMIZED_OVERLAY:\n        rootView = inflater.inflate(R.layout.fragment_main_customized, container, false);\n        break;\n      case MIN_MAX_OVERRIDE:\n        rootView = inflater.inflate(R.layout.fragment_main_min_max, container, false);\n        break;\n      case SCALE_CENTER_INSIDE:\n        rootView = inflater.inflate(R.layout.fragment_main_scale_center, container, false);\n        break;\n      case CUSTOM:\n        rootView = inflater.inflate(R.layout.fragment_main_rect, container, false);\n        break;\n      default:\n        throw new IllegalStateException(\"Unknown preset: \" + mDemoPreset);\n    }\n    return rootView;\n  }\n\n  @Override\n  public void onViewCreated(View view, Bundle savedInstanceState) {\n    super.onViewCreated(view, savedInstanceState);\n\n    mCropImageView = view.findViewById(R.id.cropImageView);\n    mCropImageView.setOnSetImageUriCompleteListener(this);\n    mCropImageView.setOnCropImageCompleteListener(this);\n\n    updateCurrentCropViewOptions();\n\n    if (savedInstanceState == null) {\n      if (mDemoPreset == CropDemoPreset.SCALE_CENTER_INSIDE) {\n        mCropImageView.setImageResource(R.drawable.cat_small);\n      } else {\n        mCropImageView.setImageResource(R.drawable.cat);\n      }\n    }\n  }\n\n  @Override\n  public boolean onOptionsItemSelected(MenuItem item) {\n    if (item.getItemId() == R.id.main_action_crop) {\n      mCropImageView.getCroppedImageAsync();\n      return true;\n    } else if (item.getItemId() == R.id.main_action_rotate) {\n      mCropImageView.rotateImage(90);\n      return true;\n    } else if (item.getItemId() == R.id.main_action_flip_horizontally) {\n      mCropImageView.flipImageHorizontally();\n      return true;\n    } else if (item.getItemId() == R.id.main_action_flip_vertically) {\n      mCropImageView.flipImageVertically();\n      return true;\n    }\n    return super.onOptionsItemSelected(item);\n  }\n\n  @Override\n  public void onAttach(Activity activity) {\n    super.onAttach(activity);\n    mDemoPreset = CropDemoPreset.valueOf(getArguments().getString(\"DEMO_PRESET\"));\n    ((MainActivity) activity).setCurrentFragment(this);\n  }\n\n  @Override\n  public void onDetach() {\n    super.onDetach();\n    if (mCropImageView != null) {\n      mCropImageView.setOnSetImageUriCompleteListener(null);\n      mCropImageView.setOnCropImageCompleteListener(null);\n    }\n  }\n\n  @Override\n  public void onSetImageUriComplete(CropImageView view, Uri uri, Exception error) {\n    if (error == null) {\n      Toast.makeText(getActivity(), \"Image load successful\", Toast.LENGTH_SHORT).show();\n    } else {\n      Log.e(\"AIC\", \"Failed to load image by URI\", error);\n      Toast.makeText(getActivity(), \"Image load failed: \" + error.getMessage(), Toast.LENGTH_LONG)\n          .show();\n    }\n  }\n\n  @Override\n  public void onCropImageComplete(CropImageView view, CropImageView.CropResult result) {\n    handleCropResult(result);\n  }\n\n  @Override\n  public void onActivityResult(int requestCode, int resultCode, Intent data) {\n    super.onActivityResult(requestCode, resultCode, data);\n    if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {\n      CropImage.ActivityResult result = CropImage.getActivityResult(data);\n      handleCropResult(result);\n    }\n  }\n\n  private void handleCropResult(CropImageView.CropResult result) {\n    if (result.getError() == null) {\n      Intent intent = new Intent(getActivity(), CropResultActivity.class);\n      intent.putExtra(\"SAMPLE_SIZE\", result.getSampleSize());\n      if (result.getUri() != null) {\n        intent.putExtra(\"URI\", result.getUri());\n      } else {\n        CropResultActivity.mImage =\n            mCropImageView.getCropShape() == CropImageView.CropShape.OVAL\n                ? CropImage.toOvalBitmap(result.getBitmap())\n                : result.getBitmap();\n      }\n      startActivity(intent);\n    } else {\n      Log.e(\"AIC\", \"Failed to crop image\", result.getError());\n      Toast.makeText(\n              getActivity(),\n              \"Image crop failed: \" + result.getError().getMessage(),\n              Toast.LENGTH_LONG)\n          .show();\n    }\n  }\n}\n"
  },
  {
    "path": "sample/src/main/res/drawable/backdrop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <!-- the checkerboard pattern. -->\n    <item android:drawable=\"@drawable/checkerboard\"/>\n\n    <!-- This mutes the checkerboard similar to the styled background. -->\n    <item android:drawable=\"@drawable/muted\"/>\n\n\n</layer-list>"
  },
  {
    "path": "sample/src/main/res/drawable/checkerboard.xml",
    "content": "<bitmap\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:dither=\"true\"\n    android:src=\"@drawable/checktile\"\n    android:tileMode=\"repeat\"/>"
  },
  {
    "path": "sample/src/main/res/drawable/muted.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n    <solid android:color=\"#77000000\"/>\n</shape>"
  },
  {
    "path": "sample/src/main/res/layout/activity_crop_result.xml",
    "content": "<FrameLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ImageView\n        android:id=\"@+id/resultImageView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/result_image_desc\"\n        android:onClick=\"onImageViewClicked\"/>\n\n    <TextView\n        android:id=\"@+id/resultImageText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"8dp\"\n        android:background=\"#7000\"\n        android:textIsSelectable=\"true\"/>\n\n</FrameLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/activity_main.xml",
    "content": "<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. -->\n<androidx.drawerlayout.widget.DrawerLayout\n    android:id=\"@+id/drawer_layout\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"com.theartofdev.edmodo.cropper.sample.MainActivity\">\n\n    <!-- main content view. -->\n    <FrameLayout\n        android:id=\"@+id/container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n\n    <!-- drawer view. -->\n    <ScrollView\n        android:id=\"@+id/navigation_drawer\"\n        android:layout_width=\"@dimen/navigation_drawer_width\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        android:background=\"#303030\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"@dimen/navigation_drawer_width\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"start\"\n            android:background=\"#303030\"\n            android:orientation=\"vertical\"\n            android:padding=\"12dp\">\n\n            <TextView\n                android:id=\"@+id/drawer_option_load\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/main_drawer_load\"/>\n\n            <View style=\"@style/Cropper.Widget.Drawer.Seperator\"/>\n\n            <TextView\n                style=\"@style/Cropper.Widget.Drawer.Title.TextView\"\n                android:text=\"@string/drawer_option_title\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_oval\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_oval\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_rect\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_rect\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_customized_overlay\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_customized_overlay\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_min_max_override\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_min_max\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_scale_center\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_scale_center\"/>\n\n            <View style=\"@style/Cropper.Widget.Drawer.Seperator\"/>\n\n            <TextView\n                style=\"@style/Cropper.Widget.Drawer.Title.TextView\"\n                android:text=\"@string/drawer_option_title2\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_scale\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_scale\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_shape\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_shape\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_guidelines\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_guidelines\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_aspect_ratio\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_aspect_ratio\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_auto_zoom\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_auto_zoom\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_max_zoom\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_max_zoom\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_set_initial_crop_rect\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_set_initial_crop_rect\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_reset_crop_rect\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_reset_crop_rect\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_multitouch\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_multitouch\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_show_overlay\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_show_overlay\"/>\n\n            <TextView\n                android:id=\"@+id/drawer_option_toggle_show_progress_bar\"\n                style=\"@style/Cropper.Widget.Drawer.Option.TextView\"\n                android:onClick=\"onDrawerOptionClicked\"\n                android:text=\"@string/drawer_option_toggle_show_progress_bar\"/>\n\n        </LinearLayout>\n    </ScrollView>\n\n</androidx.drawerlayout.widget.DrawerLayout>\n"
  },
  {
    "path": "sample/src/main/res/layout/fragment_main_customized.xml",
    "content": "<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:cropBackgroundColor=\"#88AA66CC\"\n    app:cropBorderCornerColor=\"@android:color/holo_blue_bright\"\n    app:cropBorderCornerOffset=\"0dp\"\n    app:cropBorderCornerThickness=\"5dp\"\n    app:cropBorderLineColor=\"@android:color/holo_green_light\"\n    app:cropBorderLineThickness=\"1dp\"\n    app:cropGuidelines=\"on\"\n    app:cropGuidelinesColor=\"@android:color/holo_red_dark\"\n    app:cropInitialCropWindowPaddingRatio=\"0\"\n    app:cropSnapRadius=\"0dp\"/>"
  },
  {
    "path": "sample/src/main/res/layout/fragment_main_min_max.xml",
    "content": "<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:cropMaxCropResultHeightPX=\"1200\"\n    app:cropMaxCropResultWidthPX=\"1600\"\n    app:cropMinCropResultHeightPX=\"800\"\n    app:cropMinCropResultWidthPX=\"400\"/>"
  },
  {
    "path": "sample/src/main/res/layout/fragment_main_oval.xml",
    "content": "<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:cropAspectRatioX=\"5\"\n    app:cropAspectRatioY=\"5\"\n    app:cropShape=\"oval\"/>"
  },
  {
    "path": "sample/src/main/res/layout/fragment_main_rect.xml",
    "content": "<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"/>"
  },
  {
    "path": "sample/src/main/res/layout/fragment_main_scale_center.xml",
    "content": "<com.theartofdev.edmodo.cropper.CropImageView\n    android:id=\"@+id/cropImageView\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    app:cropAutoZoomEnabled=\"false\"\n    app:cropScaleType=\"centerInside\"/>"
  },
  {
    "path": "sample/src/main/res/menu/main.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n      xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/main_action_rotate\"\n        android:icon=\"@android:drawable/ic_menu_rotate\"\n        android:title=\"@string/main_action_rotate\"\n        app:showAsAction=\"ifRoom\"/>\n    <item\n        android:id=\"@+id/main_action_flip\"\n        android:icon=\"@drawable/crop_image_menu_flip\"\n        android:title=\"@string/main_action_flip\"\n        app:showAsAction=\"ifRoom\">\n        <menu>\n            <item\n                android:id=\"@+id/main_action_flip_horizontally\"\n                android:title=\"@string/main_action_flip_horizontally\"/>\n            <item\n                android:id=\"@+id/main_action_flip_vertically\"\n                android:title=\"@string/main_action_flip_vertically\"/>\n        </menu>\n    </item>\n\n    <item\n        android:id=\"@+id/main_action_crop\"\n        android:title=\"@string/main_action_crop\"\n        app:showAsAction=\"always\"/>\n\n</menu>\n"
  },
  {
    "path": "sample/src/main/res/values/dimens.xml",
    "content": "<resources>\n\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">12dp</dimen>\n    <dimen name=\"activity_vertical_margin\">12dp</dimen>\n\n    <!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp:\n         https://developer.android.com/design/patterns/navigation-drawer.html -->\n    <dimen name=\"navigation_drawer_width\">240dp</dimen>\n    <dimen name=\"drawer_text_size\">15sp</dimen>\n    <dimen name=\"drawer_text_padding\">8dp</dimen>\n\n</resources>"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string name=\"app_name\">Image Cropper Demo</string>\n    <string name=\"app_title\">Image Cropper</string>\n    <string name=\"result_image_desc\">crop result</string>\n\n    <string name=\"main_drawer_load\">Load Image for cropping</string>\n    <string name=\"main_drawer_open\">Open navigation drawer</string>\n    <string name=\"main_drawer_close\">Close navigation drawer</string>\n\n    <string name=\"main_action_crop\">CROP</string>\n    <string name=\"main_action_rotate\">Rotate 90 degrees clockwise</string>\n    <string name=\"main_action_flip\">Flip</string>\n    <string name=\"main_action_flip_horizontally\">Flip horizontally</string>\n    <string name=\"main_action_flip_vertically\">Flip vertically</string>\n    <string name=\"drawer_option_title\">Crop View Presets</string>\n    <string name=\"drawer_option_oval\">Circular</string>\n    <string name=\"drawer_option_rect\">Rectangular</string>\n    <string name=\"drawer_option_min_max\">Min/Max limits override</string>\n    <string name=\"drawer_option_customized_overlay\">Customized Overlay</string>\n    <string name=\"drawer_option_scale_center\">Scale Center Inside</string>\n    <string name=\"drawer_option_title2\">Customization Toggles</string>\n    <string name=\"drawer_option_toggle_scale\">Image Scale: %1s</string>\n    <string name=\"drawer_option_toggle_shape\">Crop Shape: %1s</string>\n    <string name=\"drawer_option_toggle_guidelines\">Guidelines: %1s</string>\n    <string name=\"drawer_option_toggle_aspect_ratio\">Aspect Ratio: %1s</string>\n    <string name=\"drawer_option_toggle_auto_zoom\">Auto zoom: %1s</string>\n    <string name=\"drawer_option_toggle_max_zoom\">Max zoom level: %1s</string>\n    <string name=\"drawer_option_set_initial_crop_rect\">Set initial crop rectangle</string>\n    <string name=\"drawer_option_reset_crop_rect\">Reset crop rectangle to initial</string>\n    <string name=\"drawer_option_toggle_multitouch\">Multitouch: %1s</string>\n    <string name=\"drawer_option_toggle_show_overlay\">Show Overlay: %1s</string>\n    <string name=\"drawer_option_toggle_show_progress_bar\">Show Progress Bar: %1s</string>\n\n</resources>\n"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <style name=\"Cropper.Widget.Drawer.Seperator\" parent=\"android:Widget.DeviceDefault.Light.TextView\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">1dp</item>\n        <item name=\"android:layout_marginBottom\">@dimen/drawer_text_padding</item>\n        <item name=\"android:layout_marginTop\">@dimen/drawer_text_padding</item>\n        <item name=\"android:background\">#5FFF</item>\n    </style>\n\n    <style name=\"Cropper.Widget.Drawer.Title.TextView\" parent=\"android:Widget.DeviceDefault.Light.TextView\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:padding\">@dimen/drawer_text_padding</item>\n        <item name=\"android:textSize\">@dimen/drawer_text_size</item>\n        <item name=\"android:clickable\">false</item>\n        <item name=\"android:textColor\">#8FFF</item>\n    </style>\n\n    <style name=\"Cropper.Widget.Drawer.Option.TextView\" parent=\"android:Widget.DeviceDefault.Light.TextView\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:padding\">@dimen/drawer_text_padding</item>\n        <item name=\"android:textSize\">@dimen/drawer_text_size</item>\n        <item name=\"android:clickable\">true</item>\n        <item name=\"android:textColor\">@android:color/white</item>\n        <item name=\"android:background\">?android:attr/selectableItemBackground</item>\n\n    </style>\n</resources>"
  },
  {
    "path": "settings.gradle",
    "content": "include ':test'\ninclude 'cropper'\ninclude 'sample'\ninclude 'quick-start'"
  },
  {
    "path": "test/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "test/build.gradle",
    "content": "apply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.compileSdkVersion\n    buildToolsVersion rootProject.buildToolsVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion 28\n        versionCode 1\n        versionName '1.0'\n    }\n    lintOptions {\n        abortOnError false\n    }\n}\n\ndependencies {\n    api \"androidx.appcompat:appcompat:$androidXLibraryVersion\"\n    implementation project(\":cropper\")\n\n}\n"
  },
  {
    "path": "test/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest package=\"com.example.test\"\n          xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme\">\n        <activity android:name=\"com.theartofdev.edmodo.cropper.test.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\"/>\n\n                <category android:name=\"android.intent.category.LAUNCHER\"/>\n            </intent-filter>\n        </activity>\n        <activity android:name=\"com.theartofdev.edmodo.cropper.CropImageActivity\"/>\n    </application>\n\n</manifest>"
  },
  {
    "path": "test/src/main/java/com/theartofdev/edmodo/cropper/test/MainActivity.java",
    "content": "package com.theartofdev.edmodo.cropper.test;\n\nimport android.content.Intent;\nimport android.os.Bundle;\nimport androidx.appcompat.app.AppCompatActivity;\nimport android.view.View;\nimport android.widget.ImageView;\nimport android.widget.Toast;\n\nimport com.example.test.R;\nimport com.theartofdev.edmodo.cropper.CropImage;\nimport com.theartofdev.edmodo.cropper.CropImageView;\n\npublic class MainActivity extends AppCompatActivity {\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.activity_main);\n  }\n\n  /** Start pick image activity with chooser. */\n  public void onSelectImageClick(View view) {\n    CropImage.activity(null).setGuidelines(CropImageView.Guidelines.ON).start(this);\n  }\n\n  @Override\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n\n    // handle result of CropImageActivity\n    if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {\n      CropImage.ActivityResult result = CropImage.getActivityResult(data);\n      if (resultCode == RESULT_OK) {\n        ((ImageView) findViewById(R.id.quick_start_cropped_image)).setImageURI(result.getUri());\n        Toast.makeText(\n                this, \"Cropping successful, Sample: \" + result.getSampleSize(), Toast.LENGTH_LONG)\n            .show();\n      } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {\n        Toast.makeText(this, \"Cropping failed: \" + result.getError(), Toast.LENGTH_LONG).show();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "test/src/main/res/layout/activity_main.xml",
    "content": "<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@android:color/black\">\n\n    <Button\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_margin=\"12dp\"\n        android:onClick=\"onSelectImageClick\"\n        android:text=\"Start Activity\"/>\n\n    <ImageView\n        android:id=\"@+id/quick_start_cropped_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"/>\n\n</LinearLayout>\n"
  },
  {
    "path": "test/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"colorPrimary\">#3F51B5</color>\n    <color name=\"colorPrimaryDark\">#303F9F</color>\n    <color name=\"colorAccent\">#FF4081</color>\n</resources>\n"
  },
  {
    "path": "test/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Test</string>\n</resources>\n"
  },
  {
    "path": "test/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.AppCompat.Light\">\n        <!-- Customize your theme here. -->\n        <item name=\"colorPrimary\">@color/colorPrimary</item>\n        <item name=\"colorPrimaryDark\">@color/colorPrimaryDark</item>\n        <item name=\"colorAccent\">@color/colorAccent</item>\n    </style>\n\n</resources>\n"
  }
]