uCrop图片裁剪

来源:转载

uCrop使用

github地址

https://github.com/Yalantis/uCrop
然后clone或下载到本地,运行之。

效果预览

app/build.gradle

compile 'com.yalantis:ucrop:1.5.0'

AndroidManifest.xml

1 <activity2 android:name="com.yalantis.ucrop.UCropActivity"3 android:screenOrientation="portrait"4 android:theme="@style/Theme.AppCompat.Light.NoActionBar" />

这里theme可以改成自己的

配置uCrop

 1 /** 2 * 启动裁剪 3 * @param activity 上下文 4 * @param sourceFilePath 需要裁剪图片的绝对路径 5 * @param requestCode 比如:UCrop.REQUEST_CROP 6 * @param aspectRatioX 裁剪图片宽高比 7 * @param aspectRatioY 裁剪图片宽高比 8 * @return 9 */10 public static String startUCrop(Activity activity, String sourceFilePath, 11 int requestCode, float aspectRatioX, float aspectRatioY) {12 Uri sourceUri = Uri.fromFile(new File(sourceFilePath));13 File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);14 if (!outDir.exists()) {15 outDir.mkdirs();16 }17 File outFile = new File(outDir, System.currentTimeMillis() + ".jpg");18 //裁剪后图片的绝对路径19 String cameraScalePath = outFile.getAbsolutePath();20 Uri destinationUri = Uri.fromFile(outFile);21 //初始化,第一个参数:需要裁剪的图片;第二个参数:裁剪后图片22 UCrop uCrop = UCrop.of(sourceUri, destinationUri);23 //初始化UCrop配置24 UCrop.Options options = new UCrop.Options();25 //设置裁剪图片可操作的手势26 options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);27 //是否隐藏底部容器,默认显示28 options.setHideBottomControls(true);29 //设置toolbar颜色30 options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));31 //设置状态栏颜色32 options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));33 //是否能调整裁剪框34 options.setFreeStyleCropEnabled(true);35 //UCrop配置36 uCrop.withOptions(options);37 //设置裁剪图片的宽高比,比如16:938 uCrop.withAspectRatio(aspectRatioX, aspectRatioY);39 //uCrop.useSourceImageAspectRatio();40 //跳转裁剪页面41 uCrop.start(activity, requestCode);42 return cameraScalePath;43 }

其他配置

 1 //设置Toolbar标题 2 void setToolbarTitle(@Nullable String text) 3 //设置裁剪的图片格式 4 void setCompressionFormat(@NonNull Bitmap.CompressFormat format) 5 //设置裁剪的图片质量,取值0-100 6 void setCompressionQuality(@IntRange(from = 0) int compressQuality) 7 //设置最多缩放的比例尺 8 void setMaxScaleMultiplier(@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier) 9 //动画时间10 void setImageToCropBoundsAnimDuration(@IntRange(from = 100) int durationMillis)11 //设置图片压缩最大值12 void setMaxBitmapSize(@IntRange(from = 100) int maxBitmapSize)13 //是否显示椭圆裁剪框阴影14 void setOvalDimmedLayer(boolean isOval) 15 //设置椭圆裁剪框阴影颜色16 void setDimmedLayerColor(@ColorInt int color)17 //是否显示裁剪框18 void setShowCropFrame(boolean show)19 //设置裁剪框边的宽度20 void setCropFrameStrokeWidth(@IntRange(from = 0) int width)21 //是否显示裁剪框网格22 void setShowCropGrid(boolean show) 23 //设置裁剪框网格颜色24 void setCropGridColor(@ColorInt int color)25 //设置裁剪框网格宽26 void setCropGridStrokeWidth(@IntRange(from = 0) int width)

onActivityResult

经过裁剪,返回结果,这里我一般只需要裁剪后的图片绝对路径(调用上面startUCrop,即返回图片路径),然后调接口上传服务器。

1 @Override2 public void onActivityResult(int requestCode, int resultCode, Intent data) {3 if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {4 final Uri resultUri = UCrop.getOutput(data);5 } else if (resultCode == UCrop.RESULT_ERROR) {6 final Throwable cropError = UCrop.getError(data);7 }8 }

uCrop源码浅析

uCrop源码能学习的东西有很多,比如左右滑的标尺,不过我们这里源码浅析只关注裁剪部分。

类关系

首先有个大概了解:

GestureCropImageView:负责监听各种手势
CropImageView:主要完成图片裁剪工作,和判断裁剪图片是否充满裁剪框
TransformImageView:负责图片旋转、缩放、位移操作

入口

由上面的效果图可知,点击右上角,调用裁剪操作,代码如下:

 1 @Override 2 public boolean onOptionsItemSelected(MenuItem item) { 3 if (item.getItemId() == R.id.menu_crop) { 4 cropAndSaveImage(); 5 } 6 return super.onOptionsItemSelected(item); 7 } 8 //裁剪和保存图片 9 protected void cropAndSaveImage() {10 ……11 mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() {12 @Override13 public void onBitmapCropped(@NonNull Uri resultUri) {14 setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio());15 finish();16 }17 @Override18 public void onCropFailure(@NonNull Throwable t) {19 setResultError(t);20 finish();21 }22 });23 }

这里调用了GestureCropImageView&cropAndSaveImage方法,如下:

 1 /** 2 * @param compressFormat 图片压缩格式 3 * @param compressQuality 图片压缩质量 4 * @param cropCallback 图片压缩回调 5 */ 6 public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int compressQuality,@Nullable BitmapCropCallback cropCallback) { 7 //取消所有动画 8 cancelAllAnimations(); 9 //判断裁剪图片是否充满裁剪框10 setImageToWrapCropBounds(false);11 //进行裁剪12 new BitmapCropTask(getViewBitmap(), mCropRect, RectUtils.trapToRect(mCurrentImageCorners),13 getCurrentScale(), getCurrentAngle(),14 mMaxResultImageSizeX, mMaxResultImageSizeY,15 compressFormat, compressQuality,16 getImageInputPath(), getImageOutputPath(),17 cropCallback).execute();18 }

裁剪之前

setImageToWrapCropBounds

裁剪之前,先判断裁剪图片是否充满裁剪框,如果没有,进行移动和缩放让其充满。

 1 public void setImageToWrapCropBounds(boolean animate) { 2 //mBitmapLaidOut图片加载OK,isImageWrapCropBounds()检查图片是否充满裁剪框 3 if (mBitmapLaidOut && !isImageWrapCropBounds()) { 4 //当前图片中心X点 5 float currentX = mCurrentImageCenter[0]; 6 //当前图片中心Y点 7 float currentY = mCurrentImageCenter[1]; 8 //当前图片缩放值 9 float currentScale = getCurrentScale();10 //差量11 float deltaX = mCropRect.centerX() - currentX;12 float deltaY = mCropRect.centerY() - currentY;13 float deltaScale = 0;14 //临时矩阵重置15 mTempMatrix.reset();16 //临时矩阵移动17 mTempMatrix.setTranslate(deltaX, deltaY);18 //复制到新的数组19 final float[] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);20 //将此矩阵应用于二维点的数组,并编写转换后的指向数组的点21 mTempMatrix.mapPoints(tempCurrentImageCorners);22 //再检查图片是否充满裁剪框23 boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners);24 if (willImageWrapCropBoundsAfterTranslate) {25 //图片缩进的数组26 final float[] imageIndents = calculateImageIndents();27 deltaX = -(imageIndents[0] + imageIndents[2]);28 deltaY = -(imageIndents[1] + imageIndents[3]);29 } else {30 RectF tempCropRect = new RectF(mCropRect);31 mTempMatrix.reset();32 mTempMatrix.setRotate(getCurrentAngle());33 mTempMatrix.mapRect(tempCropRect);34 //获取裁剪图片的边35 final float[] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners);36 deltaScale = Math.max(tempCropRect.width() / currentImageSides[0],37 tempCropRect.height() / currentImageSides[1]);38 deltaScale = deltaScale * currentScale - currentScale;39 }40 if (animate) {41 //移动或缩放图片(有动画效果)42 post(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable(43 CropImageView.this, mImageToWrapCropBoundsAnimDuration, currentX, currentY, deltaX, deltaY,44 currentScale, deltaScale, willImageWrapCropBoundsAfterTranslate));45 } else {46 //移动图片47 postTranslate(deltaX, deltaY);48 if (!willImageWrapCropBoundsAfterTranslate) {49 //缩放图片50 zoomInImage(currentScale + deltaScale, mCropRect.centerX(), mCropRect.centerY());51 }52 }53 }54 }

进行裁剪

裁剪放到了异步,即BitmapCropTask继承AsyncTask,先设置原始图片resizeScale值,然后通过ExifInterface保存新的图片,即裁剪后的图片

 1 public class BitmapCropTask extends AsyncTask<Void, Void, Throwable> { 2 3 …… 4 /** 5 * @param viewBitmap 裁剪图片bitmap 6 * @param cropRect 裁剪矩形 7 * @param currentImageRect 当前图片矩形 8 * @param currentScale 当前图片缩放值 9 * @param currentAngle 当前图片角度 10 * @param maxResultImageSizeX 图片裁剪后最大宽值 11 * @param maxResultImageSizeY 图片裁剪后最大高值 12 * @param compressFormat 图片裁剪的格式 13 * @param compressQuality 图片裁剪的质量 14 * @param imageInputPath 裁剪图片路径 15 * @param imageOutputPath 图片裁剪后路径 16 * @param cropCallback 裁剪回调 17 */ 18 public BitmapCropTask(@Nullable Bitmap viewBitmap, 19 @NonNull RectF cropRect, @NonNull RectF currentImageRect, 20 float currentScale, float currentAngle, 21 int maxResultImageSizeX, int maxResultImageSizeY, 22 @NonNull Bitmap.CompressFormat compressFormat, int compressQuality, 23 @NonNull String imageInputPath, @NonNull String imageOutputPath, 24 @Nullable BitmapCropCallback cropCallback) { 25 …… 26 } 27 @Override 28 @Nullable 29 protected Throwable doInBackground(Void... params) { 30 if (mViewBitmap == null || mViewBitmap.isRecycled()) { 31 return new NullPointerException("ViewBitmap is null or already recycled"); 32 } 33 if (mCurrentImageRect.isEmpty()) { 34 return new NullPointerException("CurrentImageRect is empty"); 35 } 36 //设置resizeScale值 37 float resizeScale = resize(); 38 try { 39 //裁剪 40 crop(resizeScale); 41 //回收 42 mViewBitmap.recycle(); 43 mViewBitmap = null; 44 } catch (Throwable throwable) { 45 return throwable; 46 } 47 return null; 48 } 49 private float resize() { 50 //初始Options 51 final BitmapFactory.Options options = new BitmapFactory.Options(); 52 //查询该位图,而无需分配存储器,可获取outHeight(图片原始高度)和 outWidth(图片的原始宽度) 53 options.inJustDecodeBounds = true; 54 //裁剪图片解码 55 BitmapFactory.decodeFile(mImageInputPath, options); 56 //原始图片和裁剪后图片比值 57 float scaleX = options.outWidth / mViewBitmap.getWidth(); 58 float scaleY = options.outHeight / mViewBitmap.getHeight(); 59 float resizeScale = Math.min(scaleX, scaleY); 60 mCurrentScale /= resizeScale; 61 //初始化值为1 62 resizeScale = 1; 63 if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) { 64 float cropWidth = mCropRect.width() / mCurrentScale; 65 float cropHeight = mCropRect.height() / mCurrentScale; 66 if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) { 67 scaleX = mMaxResultImageSizeX / cropWidth; 68 scaleY = mMaxResultImageSizeY / cropHeight; 69 //设置resizeScale,如果是2就是高度和宽度都是原始的一半 70 resizeScale = Math.min(scaleX, scaleY); 71 mCurrentScale /= resizeScale; 72 } 73 } 74 return resizeScale; 75 } 76 private boolean crop(float resizeScale) throws IOException { 77 //ExifInterface这个接口提供了图片文件的旋转,gps,时间等信息,从原始图片读出Exif标签 78 ExifInterface originalExif = new ExifInterface(mImageInputPath); 79 int top = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale); 80 int left = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale); 81 int width = Math.round(mCropRect.width() / mCurrentScale); 82 int height = Math.round(mCropRect.height() / mCurrentScale); 83 //复制图片 84 boolean cropped = cropCImg(mImageInputPath, mImageOutputPath, 85 left, top, width, height, mCurrentAngle, resizeScale, 86 mCompressFormat.ordinal(), mCompressQuality); 87 if (cropped) { 88 //拿到裁剪后图片 89 copyExif(originalExif, width, height); 90 } 91 return cropped; 92 } 93 @SuppressWarnings("JniMissingFunction") 94 native public boolean cropCImg(String inputPath, String outputPath, 95 int left, int top, int width, int height, float angle, float resizeScale, 96 int format, int quality) throws IOException, OutOfMemoryError; 97 /** 98 * @param originalExif 原始图片Exif 99 * @param width 裁剪后图片宽100 * @param height 裁剪后图片高101 * @throws IOException 是否异常102 */103 public void copyExif(ExifInterface originalExif, int width, int height) throws IOException {104 //Exif标签数组105 String[] attributes = new String[]{106 ExifInterface.TAG_APERTURE,107 ……108 };109 //指定裁剪后图片路径,初始化新的ExifInterface110 ExifInterface newExif = new ExifInterface(mImageOutputPath);111 String value;112 for (String attribute : attributes) {113 value = originalExif.getAttribute(attribute);114 if (!TextUtils.isEmpty(value)) {115 //设置Exif标签116 newExif.setAttribute(attribute, value);117 }118 }119 newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));120 newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));121 newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0");122 //保存123 newExif.saveAttributes();124 }125 @Override126 protected void onPostExecute(@Nullable Throwable t) {127 if (mCropCallback != null) {128 if (t == null) {129 //接口回调,over130 mCropCallback.onBitmapCropped(Uri.fromFile(new File(mImageOutputPath)));131 } else {132 mCropCallback.onCropFailure(t);133 }134 }135 }136 }

总结

uCrop功能强大,对于我来说,有很多东西值得学习,难点如Rect包含问题(其实这块还不是很理解),新知识如ExifInterface操作图片,BitmapFactory显示图片的知识点温故等,还有自定义左右滑的标尺,都是不错的学习源码。抛砖引玉至此,over。

分享给朋友:
您可能感兴趣的文章:
随机阅读: