图片裁剪开源框架cropper源码解析
来源:互联网 发布:代码高亮 源码 编辑:程序博客网 时间:2024/06/05 01:14
这段时间工作内容不是很多,偶然间看到了图片裁剪框架cropper,便对其产生了兴趣,经过几天的分析,由最初的丈二和尚到现在的深入了解也算是付出有所收获吧,故在此进行学习记录,不喜勿喷哈。
一、cropper说明文档部分翻译
github地址:https://github.com/edmodo/cropper/wiki
Class Overview
The Cropper is an image cropping tool. It provides a way to set an image in XML or programmatically, and displays a resizable crop window on top of the image. Calling the method getCroppedImage() will then return the Bitmap marked by the crop window.
译1:cropper框架是一个图片裁剪工具,它提供了一种在xml文件或程序中对image图片进行设置,同时在image表层显示一个尺寸可动态变化的裁剪框。我们可以通过调用getCroppedImage()来获取被裁剪框所标志的Bitmap。
Developers can customize the following attributes (both via XML and programmatically):
1、appearance of guidelines in the crop window
2、whether the aspect ratio is fixed or not
3、aspect ratio (if the aspect ratio is fixed)
4、image resource
译2:开发者可以自定义以下属性(通过xml和代码均可)
1、控制裁剪框参考线的动态显示
2、设置是否锁定纵横比
3、设置指定纵横比(锁定纵横比的情况下)
4、设置image资源文件
二、cropper框架使用
参考对源码工程(https://github.com/edmodo/cropper)中CropperSimple示例代码的分析,介绍该工具的使用:
<?xml version="1.0" encoding="utf-8"?><ScrollView android:id="@+id/scrollview" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/content_padding"> <TextView android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="@string/title" android:textSize="24sp" android:textStyle="bold"/> <com.edmodo.cropper.CropImageView android:id="@+id/CropImageView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/content_padding" android:adjustViewBounds="true" android:scaleType="centerInside" android:src="@drawable/butterfly"/> ...</ScrollView>
这个布局xml文件内容有点长,但内容非常简单,我们只需要关注com.edmodo.cropper.CropImageView(这就是自定义的可供裁剪的View,继承自ImageView)标签即可,该标签中指定了CropImageView的适配方式为centerInside(即将图片的内容完整居中显示),src图片为@drawable/butterfly。
接下来再来分析MainActivity.java代码:
package com.example.croppersample;import android.app.Activity;import android.graphics.Bitmap;import android.os.Bundle;import android.view.View;import android.view.Window;import android.widget.AdapterView;import android.widget.Button;import android.widget.CompoundButton;import android.widget.CompoundButton.OnCheckedChangeListener;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.SeekBar.OnSeekBarChangeListener;import android.widget.Spinner;import android.widget.TextView;import android.widget.ToggleButton;import com.edmodo.cropper.CropImageView;public class MainActivity extends Activity { // Private Constants /** * 指定初始裁剪框的参考线的显示模式: * 0:Off模式,参考线始终不显示 * 1:On Touch模式,裁剪框被触摸时显示参考线,默认方式 * 2:On模式,参考线一直显示 */ private static final int GUIDELINES_ON_TOUCH = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); //ToggleButton,用于设置裁剪框是否锁定纵横比 final ToggleButton fixedAspectRatioToggleButton = (ToggleButton) findViewById(R.id.fixedAspectRatioToggle); final TextView aspectRatioXTextView = (TextView) findViewById(R.id.aspectRatioX); //滑动条设置X轴的比例参数 final SeekBar aspectRatioXSeekBar = (SeekBar) findViewById(R.id.aspectRatioXSeek); final TextView aspectRatioYTextView = (TextView) findViewById(R.id.aspectRatioY); //滑动条设置Y轴的比例参数 final SeekBar aspectRatioYSeekBar = (SeekBar) findViewById(R.id.aspectRatioYSeek); final Spinner guidelinesSpinner = (Spinner) findViewById(R.id.showGuidelinesSpin); final CropImageView cropImageView = (CropImageView) findViewById(R.id.CropImageView); //获取自定义裁剪View对象 final ImageView croppedImageView = (ImageView) findViewById(R.id.croppedImageView); //图片裁剪按钮 final Button cropButton = (Button) findViewById(R.id.Button_crop); fixedAspectRatioToggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { cropImageView.setFixedAspectRatio(isChecked); cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress()); aspectRatioXSeekBar.setEnabled(isChecked); aspectRatioYSeekBar.setEnabled(isChecked); } }); // 初始设置X/Y轴的进度条均不可用 aspectRatioXSeekBar.setEnabled(false); aspectRatioYSeekBar.setEnabled(false); aspectRatioXTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress())); aspectRatioYTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress()));aspectRatioXSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar aspectRatioXSeekBar, int progress, boolean fromUser) { if (progress < 1) { aspectRatioXSeekBar.setProgress(1); }//设置裁剪框的X/Y轴的比例大小 cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress()); aspectRatioXTextView.setText(String.valueOf(aspectRatioXSeekBar.getProgress())); } @Override public void onStartTrackingTouch(SeekBar seekBar) { // Do nothing. } @Override public void onStopTrackingTouch(SeekBar seekBar) { // Do nothing. } }); // Initialize aspect ratio Y SeekBar. aspectRatioYSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar aspectRatioYSeekBar, int progress, boolean fromUser) { if (progress < 1) { aspectRatioYSeekBar.setProgress(1); } cropImageView.setAspectRatio(aspectRatioXSeekBar.getProgress(), aspectRatioYSeekBar.getProgress()); aspectRatioYTextView.setText(String.valueOf(aspectRatioYSeekBar.getProgress())); } @Override public void onStartTrackingTouch(SeekBar seekBar) { // Do nothing. } @Override public void onStopTrackingTouch(SeekBar seekBar) { // Do nothing. } }); // Set up the Guidelines Spinner. guidelinesSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) { cropImageView.setGuidelines(i); } public void onNothingSelected(AdapterView<?> adapterView) { // Do nothing. } }); //设置初始的参考线显示模式 guidelinesSpinner.setSelection(GUIDELINES_ON_TOUCH); // Initialize the Crop button. cropButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //通过裁剪按钮获得裁剪到的bitmap,并进行显示 final Bitmap croppedImage = cropImageView.getCroppedImage(); croppedImageView.setImageBitmap(croppedImage); } }); }}
通过以上代码我们可知,croppedImageView的裁剪分为两种方式:第一种为锁定纵横比方式,分别通过指定的在X/Y轴的比例大小设置纵横比,后续对裁剪框的大小调整便会参照此纵横比;第二种为自由方式,即用户可自由地进行裁剪框大小的设定,然后进行裁剪。
croppedImageView的主要方法如下:
//设置裁剪框是否保持纵横比public void setFixedAspectRatio(boolean fixAspectRatio) //设置指定的X/Y轴比例大小,纵横比=X/Y,此时需要fixAspectRatio==truepublic void setAspectRatio(int aspectRatioX, int aspectRatioY)//设置参考线的显示模式,模式说明参照前面介绍public void setGuidelines(int guidelinesMode)//获得裁剪得到的Bitmap对象public Bitmap getCroppedImage()
可以看到,CroppedImageView具有良好的封装性,基本上我们只需通过以上几个方法,便可实现对图片的裁剪功能,是不是非常简单和方便呢?总结为三步:
1、xml中引入CropImageView标签;
2、获得cropImageView对象,完成初始设置(锁定纵横比,参考线等);
3、调用getCroppedImage()获取裁剪的bitmap对象;
三、cropper框架结构
通过查看源码,发现了一个设计很巧妙的地方——枚举(enum),对,枚举的使用,也在此膜拜一下作者大神,源码中在两个地方用到了枚举:
1、edge包中的Edge,字面上可以猜测它和边界有关,是的,该枚举中对裁剪框的四个边界进行了总结,如下:
package com.edmodo.cropper.cropwindow.edge;import android.graphics.RectF;import android.support.annotation.NonNull;import com.edmodo.cropper.util.AspectRatioUtil;/** * Enum representing an edge in the crop window. */public enum Edge { LEFT, //裁剪框左边界 TOP, //裁剪框上边界 RIGHT, //裁剪框右边界 BOTTOM;//裁剪框下边界 private float mCoordinate; //边界坐标 ...
可以看到每个Edge对象中都维持了一个mCoordinate局部变量,这个变量非常重要,而且规定了当为Edge.LEFT或Edge.RIGHT时mCoordinate代表X方向横坐标,当为Edge.TOP或Edge.BOTTOM时代表Y方向纵坐标,可以思考一下为什么要这样规定呢?之所以要定义出四个边界的枚举,是为了确定出裁剪框的大小和坐标位置,通过上面的的规定,即知道了左右边界的X坐标和上下边界Y坐标,似乎是能够确定出裁剪框的大小和坐标位置的。答案是肯定的,因为四个边框的交接点的坐标确定了下来,故我们只要知道了左上(left-top)坐标和右下(right-bottom)坐标便能确定出裁剪框的尺寸大小和位置坐标了。
另外再看一下edge包中的另一个类EdgePair,其代码很短,也很简单:
package com.edmodo.cropper.cropwindow.edge;/** * Simple class to hold a pair of Edges. */public class EdgePair { public Edge primary; //X轴边界 public Edge secondary; //Y轴边界 // Constructor public EdgePair(Edge edge1, Edge edge2) { primary = edge1; secondary = edge2; }}
可以看到,EdgePair就是包含两个Edge的简单集合,关于它的作用,将在后面进行说明。
2、handle包中的Handle,也可以从字面上猜测它和处理有关,这个枚举中定义了对裁剪框的所有有效触摸类型,如触摸内部、触摸四个边界、触摸四个边角共9中方式,先来看看它的源码:
package com.edmodo.cropper.cropwindow.handle;import android.graphics.RectF;import android.support.annotation.NonNull;import com.edmodo.cropper.cropwindow.edge.Edge;public enum Handle { //触摸左上角 TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT)), //触摸右上角 TOP_RIGHT(new CornerHandleHelper(Edge.TOP, Edge.RIGHT)), //触摸左下角 BOTTOM_LEFT(new CornerHandleHelper(Edge.BOTTOM, Edge.LEFT)), //触摸右下角 BOTTOM_RIGHT(new CornerHandleHelper(Edge.BOTTOM, Edge.RIGHT)), //触摸左边界 LEFT(new VerticalHandleHelper(Edge.LEFT)), //触摸上边界 TOP(new HorizontalHandleHelper(Edge.TOP)), //触摸右边界 RIGHT(new VerticalHandleHelper(Edge.RIGHT)), //触摸下边界 BOTTOM(new HorizontalHandleHelper(Edge.BOTTOM)), //触摸裁剪框内部 CENTER(new CenterHandleHelper()); //HandleHelper为抽象类,定义了对不同触摸方式的处理 private HandleHelper mHelper; //构造函数必须传入一个触摸方式的处理类HandleHelper Handle(HandleHelper helper) { mHelper = helper; } //非锁定纵横比下,对触摸方式的响应,刷新裁剪框显示 public void updateCropWindow(float x, float y, @NonNull RectF imageRect, float snapRadius) { mHelper.updateCropWindow(x, y, imageRect, snapRadius); } //锁定纵横比下,对触摸方式的响应,刷新裁剪框显示 public void updateCropWindow(float x, float y, float targetAspectRatio, @NonNull RectF imageRect, float snapRadius) { mHelper.updateCropWindow(x, y, targetAspectRatio, imageRect, snapRadius); }}
前面把handle类理解为和处理有关其实是不太准确的,在这里更正一下,通过源码我们可以发现handle应该是理解为 待处理的触摸类型 对象,共有9种,真正的触摸处理是由HandleHelper对象完成的,该类为抽象类,这样是为了保证不同 待处理的触摸方式 有不同的触摸处理动作。举个栗子:当我们在触摸裁剪框内部时,触摸处理动作是裁剪框随着手指的移动而移动,裁剪框本身大小不会变化;当我们触摸裁剪框左边界时,触摸处理动作是裁剪框的左边界随着手指的移动而移动,裁剪框的大小会发生变化;当我们触摸裁剪框左上角时,触摸处理动作是裁剪框的左边界和上边界随着手指的移动而移动,裁剪框的大小也会发生变化。
所以,我们可以看到抽象类HandleHelper有四个继承子类,分别是CenterHandleHelper、CornerHandleHelper、HorizontalHandleHelper、VerticalHandleHelper,对应着不同的触摸处理动作。
接下来就是util包了,主要包含四个工具类:AspectRadioUtil/HandleUtil/MathUtil/PaintUtil,通过字面上就能够知道它们的作用了吧,下面在分析CropImageView源码时会逐一使用到。
四、cropper源码分析
cropper源码的主要体现为CropImageView类,它是整个框架的核心类,该类继承自ImageView,在ImageView的基础上增加了裁剪框的显示、拖拽和裁剪功能,下面就结合其源码进行分析:
首先看看Constructor
public CropImageView(Context context) { super(context); init(context, null); } public CropImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public CropImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); }
三个构造方法都调用了init(context, attrs)方法,主要完成一些初始化的设置
private void init(@NonNull Context context, @Nullable AttributeSet attrs) { //获取自定义属性 final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0); //分割线显示模式 mGuidelinesMode = typedArray.getInteger(R.styleable.CropImageView_guidelines, 1); //是否锁定纵横比 mFixAspectRatio = typedArray.getBoolean(R.styleable.CropImageView_fixAspectRatio, false); //纵横比X轴比例大小 mAspectRatioX = typedArray.getInteger(R.styleable.CropImageView_aspectRatioX, 1); //纵横比Y轴比例大小 mAspectRatioY = typedArray.getInteger(R.styleable.CropImageView_aspectRatioY, 1); typedArray.recycle(); final Resources resources = context.getResources(); //描绘边界的画笔 mBorderPaint = PaintUtil.newBorderPaint(resources); //描绘参考线的画笔 mGuidelinePaint = PaintUtil.newGuidelinePaint(resources); //描绘半透明蒙版(CropImageView之内裁剪框之外)的画笔 mSurroundingAreaOverlayPaint = PaintUtil.newSurroundingAreaOverlayPaint(resources); //描绘倒角的画笔 mCornerPaint = PaintUtil.newCornerPaint(resources); //手指触点距离裁剪框范围偏差 mHandleRadius = resources.getDimension(R.dimen.target_radius); //手指触点距离CropImageView边界偏差 mSnapRadius = resources.getDimension(R.dimen.snap_radius); //描边宽度 mBorderThickness = resources.getDimension(R.dimen.border_thickness); //倒角宽度 mCornerThickness = resources.getDimension(R.dimen.corner_thickness); //倒角长度 mCornerLength = resources.getDimension(R.dimen.corner_length); }
相关注释在源码中都已标注,其中比较难以理解的是mHandleRadius和mSnapRadius,这两个变量代表偏差的意思,我们知道在用手指触摸手机屏幕时,由于手指和屏幕是大面积接触,在计算接触点的时候是存在一定误差的,所以在处理时需要一定的方法来抵消掉这种误差。mHandleRadius就是用于消除手指触摸裁剪框(包括边界、倒角和内部)时的误差,mSnapRadius是用于当手指拖拽裁剪框到接近(未到达)CropImageView边界时,使得裁剪框的边界和CropImageView的边界重合。
接下来就是onLayout()方法了,CropImageView对该方法进行了复写:
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); //获取CropImageView的坐标信息(left,top,right,bottom),保存于mBitmapRect中 mBitmapRect = getBitmapRect(); //初始化裁剪框 initCropWindow(mBitmapRect); }
再来看看initCropWindow(mBitmapRect)方法:
private void initCropWindow(@NonNull RectF bitmapRect) { //锁定纵横比 if (mFixAspectRatio) { initCropWindowWithFixedAspectRatio(bitmapRect); } else { //未锁定纵横比 final float horizontalPadding = 0.1f * bitmapRect.width(); final float verticalPadding = 0.1f * bitmapRect.height(); Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding); Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding); Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding); Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding); } }
可以看到裁剪框的初始化分为两种情况,主要功能是完成对裁剪框的四边界(Edge)坐标进行赋值,例如未锁定纵横比的情况下,设置的边界尺寸是CropImageView的对应边界值减去默认的内边距(padding,左右padding为宽度的1/10,上下padding为高度的1/10),后续的对于裁剪框的绘制使用的都是边界(Edge)坐标。
再接下来就是重要的onDraw()方法了
@Override protected void onDraw(Canvas canvas) { //调用父类绘制方法 super.onDraw(canvas); /** * 下面四步完成裁剪框的绘制; * 1、绘制半透明蒙版效果 * 2、绘制参考线 * 3、绘制边界 * 4、绘制倒角 */ drawDarkenedSurroundingArea(canvas); drawGuidelines(canvas); drawBorder(canvas); drawCorners(canvas); }
关于每一步的绘制过程不再过多分析,只是强调一点,每一步用到的坐标数据都来自于onLayout()中保存至Edge的坐标值。~~太唠叨了…
最后分析一下最最重要的onTouchEvent(MotionEvent event)方法
@Override public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: onActionDown(event.getX(), event.getY()); return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); onActionUp(); return true; case MotionEvent.ACTION_MOVE: onActionMove(event.getX(), event.getY()); getParent().requestDisallowInterceptTouchEvent(true); return true; default: return false; } }
首先在MotionEvent.ACTION_DOWN中调用了onActionDown(event.getX(), event.getY())方法,看看里面做了哪些处理
private void onActionDown(float x, float y) { //获取裁剪框的左上角和右下角坐标 final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); //根据手指触点坐标和裁剪框坐标以及可允许误差mHandleRadius判断是哪种触摸种类(前面总结的9种中的一种)并返回 mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius); //如果获取的触摸种类不为空,获取其偏移量(x,y方向) if (mPressedHandle != null) { HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom, mTouchOffset); invalidate(); } }
这里解释一下为什么要获取一个偏移量mTouchOffset(PointF类型),这个偏移量用于后续对裁剪框进行拖拽或大小改变时的坐标补偿,因为在获取触摸种类时使用了mHandleRadius作为允许的偏差,所以在这个偏差范围内的触点误差是需要补偿回来的,不然会导致拖拽裁剪框的时候会有“一跳”的现象,影响界面友好性。到这里你也许会问为什么要引入mHandleRadius这样一个偏差参数,如果不引入的话就不需要进行误差补偿了,对的,理论上就应该是这样的。但是这样的话,对用户的要求就非常高了,如果没有可允许偏差mHandleRadius,只有用户非常精确地按下裁剪框的特殊位置(如边界和边角处),程序才会返回特定的触摸类型,这样用户在使用的时候是会被逼疯的…
接下来就是MotionEvent.ACTION_MOVE中的onActionMove(event.getX(), event.getY())方法了
private void onActionMove(float x, float y) { if (mPressedHandle == null) { return; } //x,y坐标分别通过mTouchOffset进行误差补偿 x += mTouchOffset.x; y += mTouchOffset.y; //锁定纵横比 if (mFixAspectRatio) { mPressedHandle.updateCropWindow(x, y, getTargetAspectRatio(), mBitmapRect, mSnapRadius); } else { //非锁定纵横比 mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius); } invalidate(); }
再次以非锁定纵横比的情况进行分析,源码中可以看到在完成坐标补偿后,便对特定触摸类型进行了更新坐标的操作,这里以相对复杂的触摸左上倒角为例(mPressedHandle==Handle.TOP_LEFT),分析其中的处理过程:
mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius)会调用至mHelper.updateCropWindow(x, y, imageRect, snapRadius)方法,翻看其源码
void updateCropWindow(float x, float y, @NonNull RectF imageRect, float snapRadius) { //获取EdgePair(边界对)对象 final EdgePair activeEdges = getActiveEdges(); //返回第一个边界 final Edge primaryEdge = activeEdges.primary; //返回第二个边界 final Edge secondaryEdge = activeEdges.secondary; if (primaryEdge != null) primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT); if (secondaryEdge != null) secondaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT); }
可以看到前面提到的EdgePair在这里使用到了,其作用就是保存了两条边界,即在构造TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT))时传入的两条边界,当用户拖拽倒角的时候分别调用两条边的adjustCoordinate(…)方法进行坐标更新。
待坐标更新完成后再进行重绘。requestDisallowInterceptTouchEvent(true)方法是保证父类的touch事件能够传递下来。
然后就是MotionEvent.ACTION_UP和MotionEvent.ACTION_CANCEL的onActionUp()方法了
private void onActionUp() { if (mPressedHandle != null) { mPressedHandle = null; invalidate(); } }
很简单吧,就是进行触摸完成后的清理工作。
最后便是裁剪操作了,再分析一下其源码
public Bitmap getCroppedImage() { final Drawable drawable = getDrawable(); if (drawable == null || !(drawable instanceof BitmapDrawable)) { return null; } final float[] matrixValues = new float[9]; getImageMatrix().getValues(matrixValues); final float scaleX = matrixValues[Matrix.MSCALE_X]; final float scaleY = matrixValues[Matrix.MSCALE_Y]; final float transX = matrixValues[Matrix.MTRANS_X]; final float transY = matrixValues[Matrix.MTRANS_Y]; final float bitmapLeft = (transX < 0) ? Math.abs(transX) : 0; final float bitmapTop = (transY < 0) ? Math.abs(transY) : 0; //获取原始的bitmap final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap(); //获取X轴裁剪的起始坐标 final float cropX = (bitmapLeft + Edge.LEFT.getCoordinate()) / scaleX; //获取Y轴裁剪的起始坐标 final float cropY = (bitmapTop + Edge.TOP.getCoordinate()) / scaleY; //获取裁剪宽度 final float cropWidth = Math.min(Edge.getWidth() / scaleX, originalBitmap.getWidth() - cropX); //获取裁剪高度 final float cropHeight = Math.min(Edge.getHeight() / scaleY, originalBitmap.getHeight() - cropY); //返回裁剪后的bitmap return Bitmap.createBitmap(originalBitmap, (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight); }
由于裁剪的对象是原始的bitmap(即未经缩放处理),而裁剪边界是经过缩放处理后的值,所以需要对裁剪边界的坐标或宽高进行等比例的放大,最后形成的起始坐标和宽高才是对原始bitmap进行裁剪操作。
至此,关于cropper框架的分析过程基本就完成了。欢迎踊跃拍砖。。。
- 图片裁剪开源框架cropper源码解析
- Image Cropper 裁剪图片
- cropper裁剪图片(一)
- cropper裁剪图片(二)
- cropper 图片裁剪,固定图片宽高
- Android第三方开源图片裁剪截取:cropper
- 微信小程序图片裁剪工具we-cropper
- 上传及裁剪图片(WebUploader+cropper)
- ArthurHub/Android-Image-Cropper 相册裁剪框架学习
- 一个基于jQuery的图片裁剪插件:Cropper
- Java 利用jquery库cropper完成图片裁剪功能
- spring-mvc整合jquery cropper图片裁剪插件
- vue移动端裁剪图片结合插件Cropper的使用
- vue移动端裁剪图片结合插件Cropper的使用
- cropper.js 实现裁剪图片并上传(PC端)
- cropper.js 实现裁剪图片并上传(移动端)
- cropper.js HTML5 裁剪图片 canvas 转base64
- 基于cropper.js封装vue在线图片裁剪组件
- 自定义UITableViewCell(registerNib: 与 registerClass: 的区别)
- Cocos2d-x3.2与OpenGL渲染总结(一)Cocos2d-x3.2的渲染流程
- c#生成图片验证码,避免被软件识别的完美方案
- linux下的C语言开发(makefile编写)
- Linux 字符设备驱动
- 图片裁剪开源框架cropper源码解析
- 【Boost】boost库中thread多线程详解10——condition条件变量
- [案例]更高效,更安全,北京高法开启“掌上法院”新时代
- YTU 2844: 改错题A-看电影
- Generative Modeling of Convolutional Neural Networks
- Spark优化一则 - 减少Shuffle
- Android 自己自定义监听模式套路
- Myeclipse使用html模板
- MPICH2 bcast广播函数使用