【Android】Android图像处理—图像变换处理探究

Android图像处理—图像变换处理探究

此篇博客主要研究了通过各种方式来达到对图像形状变换的效果。

矩阵变换基础

图像的变换同样可以通过矩阵来实现。下面将介绍 平移变换、旋转变换 缩放变换以及错切变换四种变换方式。

1.平移变换

当我们要将一个点从x0,y0移动到x,y的时候,可以通过下图的矩阵变换来实现:

当如图的矩阵左乘原坐标矩阵后,就会得到一个平移到x,y的新坐标矩阵。

2.旋转变换

当我们想要将一个图像顺时针旋转θ角时, 通过如下的矩阵运算可以得到旋转后的坐标点的位置x,y。

3.缩放变换

想要将一个图片进行缩放就比较简单了,通过如下矩阵运算即可得到:

4.错切变换

错切变换比较少接触,效果大概是这样:

仍然是通过矩阵运算,将图像的坐标矩阵左乘如图的矩阵,即可得到:

通过矩阵方法调整图像

界面的实现

首先,我们新建一个ImageMatrixView,代码如下,用于显示一个原图和改变后的图像,用于对比变换前后的情况。

public class ImageMatrixView extends View {

    private Bitmap mBitmap;
    private Matrix mMatrix;

    public ImageMatrixView(Context context) {
        super(context);
        initView();
    }

    public ImageMatrixView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public ImageMatrixView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView(){
        Log.d("ImageMatrixView","initView");
        mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.pic);
        setImageMatrix(new Matrix());
    }

    public void setImageMatrix(Matrix matrix){
        mMatrix = matrix;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //方便对比,画两张图片来对比
        Log.d("ImageMatrixView","onDraw");
        canvas.drawBitmap(mBitmap,0,0,null);    //原图
        canvas.drawBitmap(mBitmap,mMatrix,null);         //矩阵变换后图片
    }
}

然后,再用如下的布局文件,以达到和之前颜色变换矩阵处一样的效果。

实现通过矩阵来改变图像

我们在代码中,分别通过两个数组来存储矩阵的值以及EditText,给每个EditText一个初始值,也就是我们的初等矩阵。然后当按下改变按钮后,则获取每个EditText的值并应用在ImageMatrixView中。

public class MatrixActivity extends AppCompatActivity {
    private GridLayout mGridLayout;
    private ImageMatrixView mImageMatrixView;
    private int mEdWidth;
    private int mEdHeight;
    private float[] mImageMatrix = new float[9];
    private EditText[] mEditTexts = new EditText[9];
    private Button mBtnChange;
    private Button mBtnReset;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_matrix);
        mImageMatrixView = findViewById(R.id.matrix_view);
        mGridLayout = findViewById(R.id.group);
        mBtnChange = findViewById(R.id.btn_change);
        mBtnReset = findViewById(R.id.btn_reset);
        mGridLayout.post(new Runnable() {
            @Override
            public void run() {
                mEdWidth = mGridLayout.getWidth() / 3;
                mEdHeight = mGridLayout.getHeight() / 3;
                addEditTexts();
                initImageMatrix();
            }
        });

        mBtnChange.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                change();
            }
        });

        mBtnReset.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                initImageMatrix();
                Matrix matrix = new Matrix();
                matrix.setValues(mImageMatrix);
                mImageMatrixView.setImageMatrix(matrix);
                mImageMatrixView.invalidate();
            }
        });

    }

    private void addEditTexts() {
        for (int i = 0; i < 9; i++) {
            EditText editText = new EditText(this);
            editText.setGravity(Gravity.CENTER);
            mGridLayout.addView(editText, mEdWidth, mEdHeight);
            mEditTexts[i] = editText;
        }
    }

    private void getImageMatrix() {
        for (int i = 0; i < 9; i++) {
            EditText editText = mEditTexts[i];
            mImageMatrix[i] = Float.parseFloat(editText.getText().toString());
        }
    }

    private void change(){
        getImageMatrix();
        Matrix matrix = new Matrix();
        matrix.setValues(mImageMatrix);
        mImageMatrixView.setImageMatrix(matrix);
        mImageMatrixView.invalidate();
    }

    private void initImageMatrix() {
        for (int i = 0; i < 9; i++) {
            if (i % 4 == 0) {
                mEditTexts[i].setText("1");
            } else {
                mEditTexts[i].setText("0");
            }
        }
    }
}

效果

如图,分别是图像的四种变换后的效果:

平移变换

旋转变换

缩放变换

错切变换

Android API来实现

其实,Android自带了api来给我们实现这四种变换:

  • matrix.setRotation() 旋转,参数为角度的值
  • matrix.setTranslate() 平移,参数分别为x轴、y轴的偏移量
  • matrix.setScale() 缩放,参数为横向与纵向的放大倍数
  • matrix.setSkew() 错切,参数为横向与纵向的k值
  • post 矩阵组合

前面四种与前文大同小异,但矩阵的组合运算在这里需要了解一下。

矩阵的组合运算

之前用矩阵进行图像变换的运算时,可以通过多种运算来达到图像变换叠加的作用,在这里同样可以,用法就是第一种变换用正常的setXXX方法,而后面的方法则用postXXX的方法即可,如:

matrix.setScale(2,2);
matrix.postTranslate(200,200);

Xfermode画笔风格处理图片

图层混合结构图

里面的第三张图以后的都是通过Src与Dst通过不同的混合形成的不同效果。

使用Xfermode将图片处理为圆角矩形

使用Xfermode之前,我们最好先将硬件加速关闭。通过setLayerType这个方法,传入LAYER_TYPE_SOFTWARE这个参数,第二个参数传入0,来禁用掉硬件加速。

这里,我们新建一个RoundXferModeView,目的是制作一个以圆角矩形的方式显示图片的View,首先让它继承自View,在里面定义一个原始的Bitmap以及处理后的Bitmap,以及一个画笔。之所以定义两个Bitmap是因为从资源文件获得的Bitmap是默认不可修改的,因此需要一个新的Bitmap来保存。

在initView中,我们做了一些之前颜色变换用到的初始化工作,如新建Canvas等等。通过我们之前的图层混合结构图可以知道,当两张图片进行混合的时候,如果我们选择了SrcIn这个模式,取的就是两张图片交集的部分。如果我们下面绘制一个圆角矩形,而上面绘制一张图片,则产生的效果就是一个圆角矩形的图片。(在Android的设计中,第一个绘制的就是Dst,第二个则是Src)因此,我们先在Canvas上绘制一个圆角矩形,之后再为mPaint调用setXfermode方法,传入一个PorterDuffDuffXfermode,将我们的SRC_IN模式传递进去。最后在Canvas中绘制我们的Bitmap。

public class RoundXferModeView extends View {
    private Bitmap mBitmap;
    private Bitmap mOutBitmap;
    private Paint mPaint;

    public RoundXferModeView(Context context) {
        super(context);
        initView();
    }

    public RoundXferModeView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public RoundXferModeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView(){
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.hotel);    //获取原始Bitmap
        mOutBitmap = Bitmap.createBitmap(mBitmap.getWidth(),mBitmap.getHeight(),Config.ARGB_8888);
        Canvas canvas = new Canvas(mOutBitmap);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        canvas.drawRoundRect(0,0,mOutBitmap.getWidth(),mOutBitmap.getHeight(),50,50,mPaint);
        mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        canvas.drawBitmap(mBitmap,0,0,mPaint);
        mPaint.setXfermode(null);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mOutBitmap,0,0,null);
    }
}

效果如图:

Shader风格处理图片

主要为了达成渐变的效果,有诸如线性渐变,环形渐变,扫描渐变,组合渐变等。虽然名字不同,显示的效果也不同,但是它们都属于颜色的一种渐变效果。而只有BitmapShader比较特殊,它属于图像渐变。通过BitmapShader,我们同样可以实现一个圆角矩形的图片显示View。

使用BitmapShader将图片处理为圆角矩形

实际上BitmapShader就是以Bitmap为背景绘制某个形状。

这里我们新建了一些变量后,直接重写onDraw方法。初始化了bitmap以及paint后,在初始化BitmapShader时我们传入bitmap,并把x,y轴都设置为CLAMP模式,然后为Painter设置这个shader。最后绘制一个圆,然后将画笔设置为绑定了shader的paint。

BitmapShader的三种模式
– CLAMP 拉伸(这里的拉伸是以最后一个像素点的颜色来填充)
– REPEAT 重复
– MIRROR 镜像

public class BitmapShaderView extends View {

    private Bitmap mBitmap;

    private Paint mPaint;

    private BitmapShader mBitmapShader;

    public BitmapShaderView(Context context) {
        super(context);
    }

    public BitmapShaderView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public BitmapShaderView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mPaint = new Paint();
        mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.pic2);
        mBitmapShader = new BitmapShader(mBitmap,TileMode.CLAMP,TileMode.CLAMP);
        mPaint.setShader(mBitmapShader);
        canvas.drawCircle(300,200,300,mPaint);
    }
}

效果如图:

N0tExpectErr0r

N0tExpectErr0r

一名热爱代码的 Android 开发者

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>