diff options
author | Thomas <tschneider.ac@gmail.com> | 2022-06-05 15:48:58 +0200 |
---|---|---|
committer | Thomas <tschneider.ac@gmail.com> | 2022-06-05 15:48:58 +0200 |
commit | 07ba1d2219707ca9acdc8022777ad53e5462de62 (patch) | |
tree | c77f34303a571cf99882453a743cc040f2b08fc9 /cropper | |
parent | ed76c97e8bd9273f96c201aff7a71aa9001af7a5 (diff) |
Start adding media edition
Diffstat (limited to 'cropper')
57 files changed, 8671 insertions, 0 deletions
diff --git a/cropper/build.gradle b/cropper/build.gradle new file mode 100644 index 000000000..f603264a2 --- /dev/null +++ b/cropper/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.library' + +android { + + compileSdk 31 + defaultConfig { + minSdkVersion 14 + versionCode 1 + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + lintOptions { + abortOnError false + } +} + +dependencies { + api 'androidx.appcompat:appcompat:1.4.2' + implementation "androidx.exifinterface:exifinterface:1.3.3" +} + diff --git a/cropper/src/main/AndroidManifest.xml b/cropper/src/main/AndroidManifest.xml new file mode 100644 index 000000000..fe8baa84d --- /dev/null +++ b/cropper/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ +<manifest package="com.theartofdev.edmodo.cropper"> + +</manifest> diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java new file mode 100644 index 000000000..54d33df4e --- /dev/null +++ b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapCroppingWorkerTask.java @@ -0,0 +1,354 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +package com.theartofdev.edmodo.cropper; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.AsyncTask; + +import java.lang.ref.WeakReference; + +/** + * Task to crop bitmap asynchronously from the UI thread. + */ +final class BitmapCroppingWorkerTask + extends AsyncTask<Void, Void, BitmapCroppingWorkerTask.Result> { + + // region: Fields and Consts + + /** + * Use a WeakReference to ensure the ImageView can be garbage collected + */ + private final WeakReference<CropImageView> mCropImageViewReference; + + /** + * the bitmap to crop + */ + private final Bitmap mBitmap; + + /** + * The Android URI of the image to load + */ + private final Uri mUri; + + /** + * The context of the crop image view widget used for loading of bitmap by Android URI + */ + private final Context mContext; + + /** + * Required cropping 4 points (x0,y0,x1,y1,x2,y2,x3,y3) + */ + private final float[] mCropPoints; + + /** + * Degrees the image was rotated after loading + */ + private final int mDegreesRotated; + + /** + * the original width of the image to be cropped (for image loaded from URI) + */ + private final int mOrgWidth; + + /** + * the original height of the image to be cropped (for image loaded from URI) + */ + private final int mOrgHeight; + + /** + * is there is fixed aspect ratio for the crop rectangle + */ + private final boolean mFixAspectRatio; + + /** + * the X aspect ration of the crop rectangle + */ + private final int mAspectRatioX; + + /** + * the Y aspect ration of the crop rectangle + */ + private final int mAspectRatioY; + + /** + * required width of the cropping image + */ + private final int mReqWidth; + + /** + * required height of the cropping image + */ + private final int mReqHeight; + + /** + * is the image flipped horizontally + */ + private final boolean mFlipHorizontally; + + /** + * is the image flipped vertically + */ + private final boolean mFlipVertically; + + /** + * The option to handle requested width/height + */ + private final CropImageView.RequestSizeOptions mReqSizeOptions; + + /** + * the Android Uri to save the cropped image to + */ + private final Uri mSaveUri; + + /** + * the compression format to use when writing the image + */ + private final Bitmap.CompressFormat mSaveCompressFormat; + + /** + * the quality (if applicable) to use when writing the image (0 - 100) + */ + private final int mSaveCompressQuality; + // endregion + + BitmapCroppingWorkerTask( + CropImageView cropImageView, + Bitmap bitmap, + float[] cropPoints, + int degreesRotated, + boolean fixAspectRatio, + int aspectRatioX, + int aspectRatioY, + int reqWidth, + int reqHeight, + boolean flipHorizontally, + boolean flipVertically, + CropImageView.RequestSizeOptions options, + Uri saveUri, + Bitmap.CompressFormat saveCompressFormat, + int saveCompressQuality) { + + mCropImageViewReference = new WeakReference<>(cropImageView); + mContext = cropImageView.getContext(); + mBitmap = bitmap; + mCropPoints = cropPoints; + mUri = null; + mDegreesRotated = degreesRotated; + mFixAspectRatio = fixAspectRatio; + mAspectRatioX = aspectRatioX; + mAspectRatioY = aspectRatioY; + mReqWidth = reqWidth; + mReqHeight = reqHeight; + mFlipHorizontally = flipHorizontally; + mFlipVertically = flipVertically; + mReqSizeOptions = options; + mSaveUri = saveUri; + mSaveCompressFormat = saveCompressFormat; + mSaveCompressQuality = saveCompressQuality; + mOrgWidth = 0; + mOrgHeight = 0; + } + + BitmapCroppingWorkerTask( + CropImageView cropImageView, + Uri uri, + float[] cropPoints, + int degreesRotated, + int orgWidth, + int orgHeight, + boolean fixAspectRatio, + int aspectRatioX, + int aspectRatioY, + int reqWidth, + int reqHeight, + boolean flipHorizontally, + boolean flipVertically, + CropImageView.RequestSizeOptions options, + Uri saveUri, + Bitmap.CompressFormat saveCompressFormat, + int saveCompressQuality) { + + mCropImageViewReference = new WeakReference<>(cropImageView); + mContext = cropImageView.getContext(); + mUri = uri; + mCropPoints = cropPoints; + mDegreesRotated = degreesRotated; + mFixAspectRatio = fixAspectRatio; + mAspectRatioX = aspectRatioX; + mAspectRatioY = aspectRatioY; + mOrgWidth = orgWidth; + mOrgHeight = orgHeight; + mReqWidth = reqWidth; + mReqHeight = reqHeight; + mFlipHorizontally = flipHorizontally; + mFlipVertically = flipVertically; + mReqSizeOptions = options; + mSaveUri = saveUri; + mSaveCompressFormat = saveCompressFormat; + mSaveCompressQuality = saveCompressQuality; + mBitmap = null; + } + + /** + * The Android URI that this task is currently loading. + */ + public Uri getUri() { + return mUri; + } + + /** + * Crop image in background. + * + * @param params ignored + * @return the decoded bitmap data + */ + @Override + protected BitmapCroppingWorkerTask.Result doInBackground(Void... params) { + try { + if (!isCancelled()) { + + BitmapUtils.BitmapSampled bitmapSampled; + if (mUri != null) { + bitmapSampled = + BitmapUtils.cropBitmap( + mContext, + mUri, + mCropPoints, + mDegreesRotated, + mOrgWidth, + mOrgHeight, + mFixAspectRatio, + mAspectRatioX, + mAspectRatioY, + mReqWidth, + mReqHeight, + mFlipHorizontally, + mFlipVertically); + } else if (mBitmap != null) { + bitmapSampled = + BitmapUtils.cropBitmapObjectHandleOOM( + mBitmap, + mCropPoints, + mDegreesRotated, + mFixAspectRatio, + mAspectRatioX, + mAspectRatioY, + mFlipHorizontally, + mFlipVertically); + } else { + return new Result((Bitmap) null, 1); + } + + Bitmap bitmap = + BitmapUtils.resizeBitmap(bitmapSampled.bitmap, mReqWidth, mReqHeight, mReqSizeOptions); + + if (mSaveUri == null) { + return new Result(bitmap, bitmapSampled.sampleSize); + } else { + BitmapUtils.writeBitmapToUri( + mContext, bitmap, mSaveUri, mSaveCompressFormat, mSaveCompressQuality); + if (bitmap != null) { + bitmap.recycle(); + } + return new Result(mSaveUri, bitmapSampled.sampleSize); + } + } + return null; + } catch (Exception e) { + return new Result(e, mSaveUri != null); + } + } + + /** + * Once complete, see if ImageView is still around and set bitmap. + * + * @param result the result of bitmap cropping + */ + @Override + protected void onPostExecute(Result result) { + if (result != null) { + boolean completeCalled = false; + if (!isCancelled()) { + CropImageView cropImageView = mCropImageViewReference.get(); + if (cropImageView != null) { + completeCalled = true; + cropImageView.onImageCroppingAsyncComplete(result); + } + } + if (!completeCalled && result.bitmap != null) { + // fast release of unused bitmap + result.bitmap.recycle(); + } + } + } + + // region: Inner class: Result + + /** + * The result of BitmapCroppingWorkerTask async loading. + */ + static final class Result { + + /** + * The cropped bitmap + */ + public final Bitmap bitmap; + + /** + * The saved cropped bitmap uri + */ + public final Uri uri; + + /** + * The error that occurred during async bitmap cropping. + */ + final Exception error; + + /** + * is the cropping request was to get a bitmap or to save it to uri + */ + final boolean isSave; + + /** + * sample size used creating the crop bitmap to lower its size + */ + final int sampleSize; + + Result(Bitmap bitmap, int sampleSize) { + this.bitmap = bitmap; + this.uri = null; + this.error = null; + this.isSave = false; + this.sampleSize = sampleSize; + } + + Result(Uri uri, int sampleSize) { + this.bitmap = null; + this.uri = uri; + this.error = null; + this.isSave = true; + this.sampleSize = sampleSize; + } + + Result(Exception error, boolean isSave) { + this.bitmap = null; + this.uri = null; + this.error = error; + this.isSave = isSave; + this.sampleSize = 1; + } + } + // endregion +} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java new file mode 100644 index 000000000..a6ecf9396 --- /dev/null +++ b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapLoadingWorkerTask.java @@ -0,0 +1,176 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +package com.theartofdev.edmodo.cropper; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.DisplayMetrics; + +import java.lang.ref.WeakReference; + +/** + * Task to load bitmap asynchronously from the UI thread. + */ +final class BitmapLoadingWorkerTask extends AsyncTask<Void, Void, BitmapLoadingWorkerTask.Result> { + + // region: Fields and Consts + + /** + * Use a WeakReference to ensure the ImageView can be garbage collected + */ + private final WeakReference<CropImageView> mCropImageViewReference; + + /** + * The Android URI of the image to load + */ + private final Uri mUri; + + /** + * The context of the crop image view widget used for loading of bitmap by Android URI + */ + private final Context mContext; + + /** + * required width of the cropping image after density adjustment + */ + private final int mWidth; + + /** + * required height of the cropping image after density adjustment + */ + private final int mHeight; + // endregion + + public BitmapLoadingWorkerTask(CropImageView cropImageView, Uri uri) { + mUri = uri; + mCropImageViewReference = new WeakReference<>(cropImageView); + + mContext = cropImageView.getContext(); + + DisplayMetrics metrics = cropImageView.getResources().getDisplayMetrics(); + double densityAdj = metrics.density > 1 ? 1 / metrics.density : 1; + mWidth = (int) (metrics.widthPixels * densityAdj); + mHeight = (int) (metrics.heightPixels * densityAdj); + } + + /** + * The Android URI that this task is currently loading. + */ + public Uri getUri() { + return mUri; + } + + /** + * Decode image in background. + * + * @param params ignored + * @return the decoded bitmap data + */ + @Override + protected Result doInBackground(Void... params) { + try { + if (!isCancelled()) { + + BitmapUtils.BitmapSampled decodeResult = + BitmapUtils.decodeSampledBitmap(mContext, mUri, mWidth, mHeight); + + if (!isCancelled()) { + + BitmapUtils.RotateBitmapResult rotateResult = + BitmapUtils.rotateBitmapByExif(decodeResult.bitmap, mContext, mUri); + + return new Result( + mUri, rotateResult.bitmap, decodeResult.sampleSize, rotateResult.degrees); + } + } + return null; + } catch (Exception e) { + return new Result(mUri, e); + } + } + + /** + * Once complete, see if ImageView is still around and set bitmap. + * + * @param result the result of bitmap loading + */ + @Override + protected void onPostExecute(Result result) { + if (result != null) { + boolean completeCalled = false; + if (!isCancelled()) { + CropImageView cropImageView = mCropImageViewReference.get(); + if (cropImageView != null) { + completeCalled = true; + cropImageView.onSetImageUriAsyncComplete(result); + } + } + if (!completeCalled && result.bitmap != null) { + // fast release of unused bitmap + result.bitmap.recycle(); + } + } + } + + // region: Inner class: Result + + /** + * The result of BitmapLoadingWorkerTask async loading. + */ + public static final class Result { + + /** + * The Android URI of the image to load + */ + public final Uri uri; + + /** + * The loaded bitmap + */ + public final Bitmap bitmap; + + /** + * The sample size used to load the given bitmap + */ + public final int loadSampleSize; + + /** + * The degrees the image was rotated + */ + public final int degreesRotated; + + /** + * The error that occurred during async bitmap loading. + */ + public final Exception error; + + Result(Uri uri, Bitmap bitmap, int loadSampleSize, int degreesRotated) { + this.uri = uri; + this.bitmap = bitmap; + this.loadSampleSize = loadSampleSize; + this.degreesRotated = degreesRotated; + this.error = null; + } + + Result(Uri uri, Exception error) { + this.uri = uri; + this.bitmap = null; + this.loadSampleSize = 0; + this.degreesRotated = 0; + this.error = error; + } + } + // endregion +} diff --git a/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java new file mode 100644 index 000000000..7190bd429 --- /dev/null +++ b/cropper/src/main/java/com/theartofdev/edmodo/cropper/BitmapUtils.java @@ -0,0 +1,923 @@ +// "Therefore those skilled at the unorthodox +// are infinite as heaven and earth, +// inexhaustible as the great rivers. +// When they come to an end, +// they begin again, +// like the days and months; +// they die and are reborn, +// like the four seasons." +// +// - Sun Tsu, +// "The Art of War" + +package com.theartofdev.edmodo.cropper; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.net.Uri; +import android.util.Log; +import android.util.Pair; + +import androidx.exifinterface.media.ExifInterface; + +import java.io.Closeable; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.ref.WeakReference; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; + +/** + * Utility class that deals with operations with an ImageView. + */ +final class BitmapUtils { + + static final Rect EMPTY_RECT = new Rect(); + + static final RectF EMPTY_RECT_F = new RectF(); + + /** + * Reusable rectangle for general internal usage + */ + static final RectF RECT = new RectF(); + + /** + * Reusable point for general internal usage + */ + static final float[] POINTS = new flo |