uCrop使用

github地址

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

效果预览

app/build.gradle

compile 'com.yalantis:ucrop:1.5.0'

AndroidManifest.xml

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

这里theme可以改成自己的

配置uCrop

  /**
* 启动裁剪
* @param activity 上下文
* @param sourceFilePath 需要裁剪图片的绝对路径
* @param requestCode 比如:UCrop.REQUEST_CROP
* @param aspectRatioX 裁剪图片宽高比
* @param aspectRatioY 裁剪图片宽高比
* @return
*/
public static String startUCrop(Activity activity, String sourceFilePath,
int requestCode, float aspectRatioX, float aspectRatioY) {
Uri sourceUri = Uri.fromFile(new File(sourceFilePath));
File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!outDir.exists()) {
outDir.mkdirs();
}
File outFile = new File(outDir, System.currentTimeMillis() + ".jpg");
//裁剪后图片的绝对路径
String cameraScalePath = outFile.getAbsolutePath();
Uri destinationUri = Uri.fromFile(outFile);
//初始化,第一个参数:需要裁剪的图片;第二个参数:裁剪后图片
UCrop uCrop = UCrop.of(sourceUri, destinationUri);
//初始化UCrop配置
UCrop.Options options = new UCrop.Options();
//设置裁剪图片可操作的手势
options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL);
//是否隐藏底部容器,默认显示
options.setHideBottomControls(true);
//设置toolbar颜色
options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));
//设置状态栏颜色
options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary));
//是否能调整裁剪框
options.setFreeStyleCropEnabled(true);
//UCrop配置
uCrop.withOptions(options);
//设置裁剪图片的宽高比,比如16:9
uCrop.withAspectRatio(aspectRatioX, aspectRatioY);
//uCrop.useSourceImageAspectRatio();
//跳转裁剪页面
uCrop.start(activity, requestCode);
return cameraScalePath;
}

其他配置

 //设置Toolbar标题
void setToolbarTitle(@Nullable String text)
//设置裁剪的图片格式
void setCompressionFormat(@NonNull Bitmap.CompressFormat format)
//设置裁剪的图片质量,取值0-100
void setCompressionQuality(@IntRange(from = ) int compressQuality)
//设置最多缩放的比例尺
void setMaxScaleMultiplier(@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier)
//动画时间
void setImageToCropBoundsAnimDuration(@IntRange(from = ) int durationMillis)
//设置图片压缩最大值
void setMaxBitmapSize(@IntRange(from = ) int maxBitmapSize)
//是否显示椭圆裁剪框阴影
void setOvalDimmedLayer(boolean isOval)
//设置椭圆裁剪框阴影颜色
void setDimmedLayerColor(@ColorInt int color)
//是否显示裁剪框
void setShowCropFrame(boolean show)
//设置裁剪框边的宽度
void setCropFrameStrokeWidth(@IntRange(from = ) int width)
//是否显示裁剪框网格
void setShowCropGrid(boolean show)
//设置裁剪框网格颜色
void setCropGridColor(@ColorInt int color)
//设置裁剪框网格宽
void setCropGridStrokeWidth(@IntRange(from = ) int width)

onActivityResult

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

 @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) {
final Uri resultUri = UCrop.getOutput(data);
} else if (resultCode == UCrop.RESULT_ERROR) {
final Throwable cropError = UCrop.getError(data);
}
}

uCrop源码浅析

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

类关系

首先有个大概了解:

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

入口

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

 @Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_crop) {
cropAndSaveImage();
}
return super.onOptionsItemSelected(item);
}
//裁剪和保存图片
protected void cropAndSaveImage() {
……
mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() {
@Override
public void onBitmapCropped(@NonNull Uri resultUri) {
setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio());
finish();
}
@Override
public void onCropFailure(@NonNull Throwable t) {
setResultError(t);
finish();
}
});
}

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

 /**
* @param compressFormat 图片压缩格式
* @param compressQuality 图片压缩质量
* @param cropCallback 图片压缩回调
*/
public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int compressQuality,@Nullable BitmapCropCallback cropCallback) {
//取消所有动画
cancelAllAnimations();
//判断裁剪图片是否充满裁剪框
setImageToWrapCropBounds(false);
//进行裁剪
new BitmapCropTask(getViewBitmap(), mCropRect, RectUtils.trapToRect(mCurrentImageCorners),
getCurrentScale(), getCurrentAngle(),
mMaxResultImageSizeX, mMaxResultImageSizeY,
compressFormat, compressQuality,
getImageInputPath(), getImageOutputPath(),
cropCallback).execute();
}

裁剪之前

setImageToWrapCropBounds

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

 public void setImageToWrapCropBounds(boolean animate) {
//mBitmapLaidOut图片加载OK,isImageWrapCropBounds()检查图片是否充满裁剪框
if (mBitmapLaidOut && !isImageWrapCropBounds()) {
//当前图片中心X点
float currentX = mCurrentImageCenter[];
//当前图片中心Y点
float currentY = mCurrentImageCenter[];
//当前图片缩放值
float currentScale = getCurrentScale();
//差量
float deltaX = mCropRect.centerX() - currentX;
float deltaY = mCropRect.centerY() - currentY;
float deltaScale = ;
//临时矩阵重置
mTempMatrix.reset();
//临时矩阵移动
mTempMatrix.setTranslate(deltaX, deltaY);
//复制到新的数组
final float[] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);
//将此矩阵应用于二维点的数组,并编写转换后的指向数组的点
mTempMatrix.mapPoints(tempCurrentImageCorners);
//再检查图片是否充满裁剪框
boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners);
if (willImageWrapCropBoundsAfterTranslate) {
//图片缩进的数组
final float[] imageIndents = calculateImageIndents();
deltaX = -(imageIndents[] + imageIndents[]);
deltaY = -(imageIndents[] + imageIndents[]);
} else {
RectF tempCropRect = new RectF(mCropRect);
mTempMatrix.reset();
mTempMatrix.setRotate(getCurrentAngle());
mTempMatrix.mapRect(tempCropRect);
//获取裁剪图片的边
final float[] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners);
deltaScale = Math.max(tempCropRect.width() / currentImageSides[],
tempCropRect.height() / currentImageSides[]);
deltaScale = deltaScale * currentScale - currentScale;
}
if (animate) {
//移动或缩放图片(有动画效果)
post(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable(
CropImageView.this, mImageToWrapCropBoundsAnimDuration, currentX, currentY, deltaX, deltaY,
currentScale, deltaScale, willImageWrapCropBoundsAfterTranslate));
} else {
//移动图片
postTranslate(deltaX, deltaY);
if (!willImageWrapCropBoundsAfterTranslate) {
//缩放图片
zoomInImage(currentScale + deltaScale, mCropRect.centerX(), mCropRect.centerY());
}
}
}
}

进行裁剪

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

 public class BitmapCropTask extends AsyncTask<Void, Void, Throwable> {

     ……
/**
* @param viewBitmap 裁剪图片bitmap
* @param cropRect 裁剪矩形
* @param currentImageRect 当前图片矩形
* @param currentScale 当前图片缩放值
* @param currentAngle 当前图片角度
* @param maxResultImageSizeX 图片裁剪后最大宽值
* @param maxResultImageSizeY 图片裁剪后最大高值
* @param compressFormat 图片裁剪的格式
* @param compressQuality 图片裁剪的质量
* @param imageInputPath 裁剪图片路径
* @param imageOutputPath 图片裁剪后路径
* @param cropCallback 裁剪回调
*/
public BitmapCropTask(@Nullable Bitmap viewBitmap,
@NonNull RectF cropRect, @NonNull RectF currentImageRect,
float currentScale, float currentAngle,
int maxResultImageSizeX, int maxResultImageSizeY,
@NonNull Bitmap.CompressFormat compressFormat, int compressQuality,
@NonNull String imageInputPath, @NonNull String imageOutputPath,
@Nullable BitmapCropCallback cropCallback) {
……
}
@Override
@Nullable
protected Throwable doInBackground(Void... params) {
if (mViewBitmap == null || mViewBitmap.isRecycled()) {
return new NullPointerException("ViewBitmap is null or already recycled");
}
if (mCurrentImageRect.isEmpty()) {
return new NullPointerException("CurrentImageRect is empty");
}
//设置resizeScale值
float resizeScale = resize();
try {
//裁剪
crop(resizeScale);
//回收
mViewBitmap.recycle();
mViewBitmap = null;
} catch (Throwable throwable) {
return throwable;
}
return null;
}
private float resize() {
//初始Options
final BitmapFactory.Options options = new BitmapFactory.Options();
//查询该位图,而无需分配存储器,可获取outHeight(图片原始高度)和 outWidth(图片的原始宽度)
options.inJustDecodeBounds = true;
//裁剪图片解码
BitmapFactory.decodeFile(mImageInputPath, options);
//原始图片和裁剪后图片比值
float scaleX = options.outWidth / mViewBitmap.getWidth();
float scaleY = options.outHeight / mViewBitmap.getHeight();
float resizeScale = Math.min(scaleX, scaleY);
mCurrentScale /= resizeScale;
//初始化值为1
resizeScale = ;
if (mMaxResultImageSizeX > && mMaxResultImageSizeY > ) {
float cropWidth = mCropRect.width() / mCurrentScale;
float cropHeight = mCropRect.height() / mCurrentScale;
if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) {
scaleX = mMaxResultImageSizeX / cropWidth;
scaleY = mMaxResultImageSizeY / cropHeight;
//设置resizeScale,如果是2就是高度和宽度都是原始的一半
resizeScale = Math.min(scaleX, scaleY);
mCurrentScale /= resizeScale;
}
}
return resizeScale;
}
private boolean crop(float resizeScale) throws IOException {
//ExifInterface这个接口提供了图片文件的旋转,gps,时间等信息,从原始图片读出Exif标签
ExifInterface originalExif = new ExifInterface(mImageInputPath);
int top = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale);
int left = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale);
int width = Math.round(mCropRect.width() / mCurrentScale);
int height = Math.round(mCropRect.height() / mCurrentScale);
//复制图片
boolean cropped = cropCImg(mImageInputPath, mImageOutputPath,
left, top, width, height, mCurrentAngle, resizeScale,
mCompressFormat.ordinal(), mCompressQuality);
if (cropped) {
//拿到裁剪后图片
copyExif(originalExif, width, height);
}
return cropped;
}
@SuppressWarnings("JniMissingFunction")
native public boolean cropCImg(String inputPath, String outputPath,
int left, int top, int width, int height, float angle, float resizeScale,
int format, int quality) throws IOException, OutOfMemoryError;
/**
* @param originalExif 原始图片Exif
* @param width 裁剪后图片宽
* @param height 裁剪后图片高
* @throws IOException 是否异常
*/
public void copyExif(ExifInterface originalExif, int width, int height) throws IOException {
//Exif标签数组
String[] attributes = new String[]{
ExifInterface.TAG_APERTURE,
……
};
//指定裁剪后图片路径,初始化新的ExifInterface
ExifInterface newExif = new ExifInterface(mImageOutputPath);
String value;
for (String attribute : attributes) {
value = originalExif.getAttribute(attribute);
if (!TextUtils.isEmpty(value)) {
//设置Exif标签
newExif.setAttribute(attribute, value);
}
}
newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width));
newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height));
newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "");
//保存
newExif.saveAttributes();
}
@Override
protected void onPostExecute(@Nullable Throwable t) {
if (mCropCallback != null) {
if (t == null) {
//接口回调,over
mCropCallback.onBitmapCropped(Uri.fromFile(new File(mImageOutputPath)));
} else {
mCropCallback.onCropFailure(t);
}
}
}
}

总结

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

uCrop图片裁剪的更多相关文章

  1. 好用的开源库(二)——uCrop 图片裁剪

    最近想要实现图片裁剪的功能,在Github上找到了这个uCrop,star的人挺多的,便是决定入坑,结果长达一个小时的看资料+摸索,终于是在项目中实现了图片裁剪的功能,今天便是来介绍一下uCrop的使 ...

  2. Android 图片裁剪库 uCrop

    引语 晚上好,我是猫咪,我的公众号「程序媛猫咪」会推荐 GitHub 上好玩的项目,挖掘开源的价值,欢迎关注我. 现在 Android 开发,离不开图片,必然也需要图片裁剪功能,这个实现可以调用系统的 ...

  3. 图片裁剪(基于RxPaparazzo)

    图片裁剪(基于RxPaparazzo) 前言:基于RxPaparazzo的图片裁剪,图片旋转.比例放大|缩小. 效果: 开发环境:AndroidStudio2.2.1+gradle-2.14.1 涉及 ...

  4. iOS常见用户头像的圆形图片裁剪常见的几种方法

    在开发中,基本上APP的用户头像的处理都需要把用户所上传的方形图片,处理为圆形图片.在这里就总结三种常见的处理圆形图片的方法. 1.使用位图上下文 2.使用UIView的layer进行处理 3.使用r ...

  5. Cropper – 简单的 jQuery 图片裁剪插件

    Cropper 是一个简单的 jQuery 图像裁剪插件.它支持选项,方法,事件,触摸(移动),缩放,旋转.输出的裁剪数据基于原始图像大小,这样你就可以用它们来直接裁剪图像. 如果你尝试裁剪跨域图像, ...

  6. 自己积累的一些Emgu CV代码(主要有图片格式转换,图片裁剪,图片翻转,图片旋转和图片平移等功能)

    using System; using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; na ...

  7. web开发实战--图片裁剪和上传

    前言: 最近的开发中, 有一个上传头像的任务. 由于头像本身的特殊性, 其一般流程为选择图片, 编辑裁剪区域, 再继而上传图片操作. 看似简单的东西, 实则是挺麻烦的一件事. 借助这次开发机会, 来具 ...

  8. PHP图片裁剪_图片缩放_PHP生成缩略图

    在制作网页过程中,为了排版整齐美观,对网页中的图片处理成固定大小尺寸的图片,或是要截去图片边角中含有水印的图片,对于图片量多,每天更新大量图,靠人工PS处理是不现实的,那么有没有自动处理图片的程序了! ...

  9. Croppic – 免费开源的 jQuery 图片裁剪插件

    Croppic 这款开源的 jQuery 图片裁剪插件能够满足网站开发人员各种不同的使用需要.只需要简单的上传图片,就可以实现你想要的图像缩放和裁剪功能.因为使用了 HTML5 FormData  对 ...

随机推荐

  1. Spring Boot Tomcat配置详解

    参数配置容器 server.xx开头的是所有servlet容器通用的配置,server.tomcat.xx开头的是tomcat特有的参数,其它类似. 所有参数绑定配置类:org.springframe ...

  2. 列表list切片

    list1 = [1, 2, 3, 4, 5, 6] list1[::-1] >>>[6, 5, 4, 3, 2, 1] list1[:3:-1] >>>[6, 5 ...

  3. C# signtool error:no certificates were found that met all the given criteria 错误解决方案

    程序运行时报错:signtool error:no certificates were found that met all the given criteria (汉译:符号工具错误:没有找到符合所 ...

  4. table滑块

    我们做table时,常常想做一个滑块的移动效果,来让app显得更加生动,原理很简单,废话不说,直接上code public class AnimationActivity extends AppCom ...

  5. 2018.4.24-ml笔记(多元线性回归)

    numpy.dot作用于两个向量则是它们内积,作用于矩阵则是矩阵积. RMSE解决量纲问题,即单位 RMSE会放大差值比较大的值,所以选用MSE更好.

  6. Java SE 9(JDK9)环境安装及交互式编程环境Jshell使用示例

    目的 安装JDK 9, 练习Jshell工具的使用, 体验Java的交互式编程环境. 什么是Jshell 其实就是一个命令行工具,安装完JDK9后,可以在bin目录下找到该工具,与Python的解释器 ...

  7. Log4Net使用详解2

    首先说明一点的是,log4net解决的问题是提供一个记录日志的框架,它提供了向多种目标写入的实现,比如利用log4net可以方便地将日志信息记录到文件.控制台.Windows事件日志和数据库(包括MS ...

  8. ElasticSearch实战-编码实践

    1.概述 前面在<ElasticSearch实战-入门>中给大家分享如何搭建这样一个集群,在完成集群的搭建后,今天给大家分享如何实现对应的业务功能模块,下面是今天的分享内容,目录如下所示: ...

  9. msql 必知必会笔记

    Edit Mysql 必知必会 第一章 理解SQL 什么是数据库 数据库(database) 保存有组织的数据的容器 什么是表  一组特定类型的数据的结构化清单 什么是模式  数据库和表的布局及特性的 ...

  10. 探秘 Java 热部署三(Java agent agentmain)

    前言 让我们继续探秘 Java 热部署.在前文 探秘 Java 热部署二(Java agent premain)中,我们介绍了 Java agent premain.通过在main方法之前通过类似 A ...