zoukankan      html  css  js  c++  java
  • uCrop图片裁剪

    uCrop使用

    github地址

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

    效果预览

    app/build.gradle

    compile 'com.yalantis:ucrop:1.5.0'

    AndroidManifest.xml

    1 <activity
    2     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:9
    38     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 @Override
    2 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         @Override
    13         public void onBitmapCropped(@NonNull Uri resultUri) {
    14             setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio());
    15             finish();
    16         }
    17         @Override
    18         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         //指定裁剪后图片路径,初始化新的ExifInterface
    110         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     @Override
    126     protected void onPostExecute(@Nullable Throwable t) {
    127         if (mCropCallback != null) {
    128             if (t == null) {
    129                 //接口回调,over
    130                 mCropCallback.onBitmapCropped(Uri.fromFile(new File(mImageOutputPath)));
    131             } else {
    132                 mCropCallback.onCropFailure(t);
    133             }
    134         }
    135     }
    136 }

    总结

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

  • 相关阅读:
    机器学习: t-Stochastic Neighbor Embedding 降维算法 (二)
    数学辨异 —— 泰勒展开与等比数列求和
    HDU 4705 Y
    C#实现的内存分页机制的一个实例
    java程序获得SqlServer数据表的表结构
    GLSL中的各种变量总结
    HTTP协议学习
    Jedis中的一致性hash
    C语言数据结构----双向链表
    ios7毛玻璃效果实现
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/8490518.html
Copyright © 2011-2022 走看看