uCrop图片裁剪
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图片裁剪的更多相关文章
- 好用的开源库(二)——uCrop 图片裁剪
最近想要实现图片裁剪的功能,在Github上找到了这个uCrop,star的人挺多的,便是决定入坑,结果长达一个小时的看资料+摸索,终于是在项目中实现了图片裁剪的功能,今天便是来介绍一下uCrop的使 ...
- Android 图片裁剪库 uCrop
引语 晚上好,我是猫咪,我的公众号「程序媛猫咪」会推荐 GitHub 上好玩的项目,挖掘开源的价值,欢迎关注我. 现在 Android 开发,离不开图片,必然也需要图片裁剪功能,这个实现可以调用系统的 ...
- 图片裁剪(基于RxPaparazzo)
图片裁剪(基于RxPaparazzo) 前言:基于RxPaparazzo的图片裁剪,图片旋转.比例放大|缩小. 效果: 开发环境:AndroidStudio2.2.1+gradle-2.14.1 涉及 ...
- iOS常见用户头像的圆形图片裁剪常见的几种方法
在开发中,基本上APP的用户头像的处理都需要把用户所上传的方形图片,处理为圆形图片.在这里就总结三种常见的处理圆形图片的方法. 1.使用位图上下文 2.使用UIView的layer进行处理 3.使用r ...
- Cropper – 简单的 jQuery 图片裁剪插件
Cropper 是一个简单的 jQuery 图像裁剪插件.它支持选项,方法,事件,触摸(移动),缩放,旋转.输出的裁剪数据基于原始图像大小,这样你就可以用它们来直接裁剪图像. 如果你尝试裁剪跨域图像, ...
- 自己积累的一些Emgu CV代码(主要有图片格式转换,图片裁剪,图片翻转,图片旋转和图片平移等功能)
using System; using System.Drawing; using Emgu.CV; using Emgu.CV.CvEnum; using Emgu.CV.Structure; na ...
- web开发实战--图片裁剪和上传
前言: 最近的开发中, 有一个上传头像的任务. 由于头像本身的特殊性, 其一般流程为选择图片, 编辑裁剪区域, 再继而上传图片操作. 看似简单的东西, 实则是挺麻烦的一件事. 借助这次开发机会, 来具 ...
- PHP图片裁剪_图片缩放_PHP生成缩略图
在制作网页过程中,为了排版整齐美观,对网页中的图片处理成固定大小尺寸的图片,或是要截去图片边角中含有水印的图片,对于图片量多,每天更新大量图,靠人工PS处理是不现实的,那么有没有自动处理图片的程序了! ...
- Croppic – 免费开源的 jQuery 图片裁剪插件
Croppic 这款开源的 jQuery 图片裁剪插件能够满足网站开发人员各种不同的使用需要.只需要简单的上传图片,就可以实现你想要的图像缩放和裁剪功能.因为使用了 HTML5 FormData 对 ...
随机推荐
- Linux 环境下使用g++编译C++
单个源文件生成可执行程序 下面是一个保存在文件 helloworld.cpp 中一个简单的 C++ 程序的代码: /* helloworld.cpp */ #include <iostream& ...
- 机器学习技法笔记:10 Random Forest
Roadmap Random Forest Algorithm Out-Of-Bag Estimate Feature Selection Random Forest in Action Summar ...
- [CTSC1999] 家园
使用并查集判断无解. 令月球是n+1,地球是0 枚举时长t,将点(地球.月球以及太空站)i拆为t个点(i,j)表示第j时刻的点i. 对于太空船云云建图,容量是h[i]. 源点S和(0,0)连边,容量k ...
- python之内置装饰器(property/staticmethod/classmethod)
python内置了property.staticmethod.classmethod三个装饰器,有时候我们也会用到,这里简单说明下 1.property 作用:顾名思义把函数装饰成属性 一般我们调用类 ...
- web自动化测试(java)---环境搭建
java的测试环境搭建相较于python还简单些,只要把相关的jar包导入即可了 1.安装java 从官网下载最新的java安装程序,双击安装(java1.8) 2.下载java版的selenium的 ...
- salesforce lightning零基础学习(六)Lightning Data Service(LDS)
本篇可参看:https://trailhead.salesforce.com/modules/lightning_data_service Lightning中针对object的detail页面,一个 ...
- HDFS-Architecture剖析
1.概述 从HDFS的应用层面来看,我们可以非常容易的使用其API来操作HDFS,实现目录的创建.删除,文件的上传下载.删除.追加(Hadoop2.x版本以后开始支持)等功能.然而仅仅局限与代码层面是 ...
- Unity3d中PureMVC框架的搭建及使用资料
1.下载PureMVC框架 https://github.com/PureMVC/puremvc-csharp-multicore-framework https://github.com/PureM ...
- 如何在线显示php源代码
通过php提供的函数highlight_file和highlight_string实现
- 给mysql添加一个只有某个数据库查询权限的用户
添加用户: insert into mysql.user(Host,User,Password,ssl_cipher,x509_issuer,x509_subject) values ("1 ...