您的位置:首页 > 移动开发 > Android开发

Android 图片处理

2015-06-07 10:15 686 查看

1. 色相、饱和度和亮度调节

色光三原色(RGBA模型):

R — red (红)

G — green (绿)

B — blue (蓝)

A — alpha (透明度)

色相:物体的颜色。

饱和度:颜色的纯度,从0(灰)到100%(饱和)来描述。

亮度:颜色的相对明暗程度。

先修改主界面 activity_main.xml 布局,如图所示:



代码如下:

[code]<LinearLayout 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_marginBottom="40dp"
        android:layout_gravity="center_horizontal"
        android:text="图片处理"
        android:textSize="40sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="PrimaryColor"
        android:onClick="primaryColorClick" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ColorMatrix"
        android:onClick="colorMatrixClick"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="PixelsEffect"
        android:onClick="pixelsEffectClick"/>
</LinearLayout>


注意:在布局中引入 android:onClick 属性可以更方便的添加点击事件。但与点击事件的方法名要保持一致!

接下来,新建一个颜色处理的工具类 ImageHelper,封装对图片的操作,方便将来直接调用。代码如下:

[code]public class ImageHelper {

    /**
     * 对传入的bitmap调整色光三原色(注意:传入的bitmap默认不可修改,不能直接处理,否则会报错)
     * @param bitmap: 传入的图片
     * @param hue: 色相
     * @param saturation: 饱和度
     * @param lum: 亮度
     * @return
     */
    public static Bitmap handleImage(Bitmap bitmap, float hue, float saturation, float lum) {

        /**
         * 创建一个同样大小的图片
         * Bitmap.Config.ARGB_8888: 颜色模式,bitmap的最高处理方式
         */
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);

        /**
         * 创建一张与传入的bitmap一样大小的画布(canvas),之后的操作都在画布上
         * ANTI_ALIAS_FLAG: 抗锯齿
         */
        Canvas canvas = new Canvas(bmp);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

        /**
         * setRotate: 设置色相(RGB)
         * 0:RED
         * 1:GREEN
         * 2:BLUE
         */
        ColorMatrix hurMatrix = new ColorMatrix();
        hurMatrix.setRotate(0, hue);
        hurMatrix.setRotate(1, hue);
        hurMatrix.setRotate(2, hue);

        /**
         * setSaturation: 设置饱和度
         */
        ColorMatrix saturationMatrix = new ColorMatrix();
        saturationMatrix.setSaturation(saturation);

        /**
         * setScale: 设置亮度(4个参数分别为:r、g、b、透明度)
         */
        ColorMatrix lumMatrix = new ColorMatrix();
        lumMatrix.setScale(lum, lum, lum, 1);

        /**
         * 综合前面设置几个属性
         */
        ColorMatrix imageMatrix = new ColorMatrix();
        imageMatrix.postConcat(hurMatrix);
        imageMatrix.postConcat(saturationMatrix);
        imageMatrix.postConcat(lumMatrix);

        /**
         * 传入imageMatrix,画到新的画布上
         * 在canvas上绘制出图片
         */
        paint.setColorFilter(new ColorMatrixColorFilter(imageMatrix));
        canvas.drawBitmap(bitmap, 0, 0, paint);// paint包含了前面设置的三个属性(色调、饱和度、亮度)

        return bmp;
    }
}


注意 bitmap 和 bmp 不要混淆。

新建一个布局,名为 primary_color.xml,用以显示图片和需要调节的参数。如图所示(代码附文末源码中):



新建 PrimaryColor 类,继承自 Activity,对 SeekBar 做相应的处理逻辑,

[code]public class PrimaryColor extends Activity implements SeekBar.OnSeekBarChangeListener {

    private ImageView imageView;
    private SeekBar seekBarHue, seekBarSaturation, seekBarLum;
    public static final int MAX_VALUE = 255;
    public static final int MID_VALUE = 127;
    private float mHue, mSaturation, mLum;// 传递给图像处理工具类参数
    private Bitmap bitmap;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.primary_color);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.p3);// 将资源中的图片转化为bitmap

        imageView = (ImageView) findViewById(R.id.image);
        seekBarHue = (SeekBar) findViewById(R.id.seekBarHue);// 实例化
        seekBarSaturation = (SeekBar) findViewById(R.id.seekBarSaturation);
        seekBarLum = (SeekBar) findViewById(R.id.seekBarLum);

        seekBarHue.setOnSeekBarChangeListener(this);// 添加监听
        seekBarSaturation.setOnSeekBarChangeListener(this);
        seekBarLum.setOnSeekBarChangeListener(this);

        seekBarHue.setMax(MAX_VALUE);// 设置seekBar最大值
        seekBarSaturation.setMax(MAX_VALUE);
        seekBarLum.setMax(MAX_VALUE);

        seekBarHue.setProgress(MID_VALUE);// 设置初始值
        seekBarSaturation.setProgress(MID_VALUE);
        seekBarLum.setProgress(MID_VALUE);

        imageView.setImageBitmap(bitmap);// 程序初始化时显示原图
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        switch (seekBar.getId()) {
            case R.id.seekBarHue:
                mHue = (progress - MID_VALUE) * 1.0f / MID_VALUE * 180;// 色调变化公式(经验值,并非固定的)
                break;
            case R.id.seekBarSaturation:
                mSaturation = progress * 1.0f / MID_VALUE;// 饱和度变化公式
                break;
            case R.id.seekBarLum:
                mLum = progress * 1.0f / MID_VALUE;// 亮度变化公式
                break;
        }
        // 通过工具类ImageHelper处理图片,返回新的bitmap
      imageView.setImageBitmap(ImageHelper.handleImage(bitmap, mHue, mSaturation, mLum));
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}


在 MainActivity 中添加点击事件,跳转到 PrimaryColor,

[code]public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void primaryColorClick(View view) {
        startActivity(new Intent(this, PrimaryColor.class));
    }

}


最后,由于添加了新的 Activity ( PrimaryColor ),需要在配置文件 AndroidManifest.xml 中对其进行注册:

[code]<activity android:name=".PrimaryColor" />


接下来,运行程序,并点击 PrimaryColor 按钮,就跳转到如下界面:



拖动各个 SeekBar,就可以分别调节图片的色相、饱和度和亮度,看到图片的变化效果了。

2. 颜色矩阵

2.1 颜色矩阵知识

Android 中可以通过颜色矩阵(ColorMatrix 类)操作颜色。

颜色矩阵是一个 4*5 的矩阵(图1.1)。可以修改图片中RGBA各分量的值,颜色矩阵以一维数组的方式存储如下:

[ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ]

他通过 RGBA 四个通道来直接操作对应颜色,如果会使用 Photoshop 就会知道有时处理图片通过控制 RGBA 各颜色通道来做出特殊的效果。这个矩阵对颜色的作用计算方式如1.3示:



矩阵的运算规则:矩阵 A 的一行乘以矩阵 C 的一列作为矩阵 R 的一行。

矩阵 C 是图片中包含的 RGBA 信息,R 矩阵是用颜色矩阵应用于C之后的新的颜色分量,运算结果如下:

R’ = a*R + b*G + c*B + d*A + e;

G’ = f*R + g*G + h*B + i*A + j;

B’ = k*R + l*G + m*B + n*A + o;

A’ = p*R + q*G + r*B + s*A + t;

颜色矩阵并不是看上去那么深奥,其实需要使用的参数很少,而且很有规律:

1. 第一行决定红色;

2. 第二行决定绿色;

3. 第三行决定蓝色;

4. 第四行决定了透明度;

5. 第五列是颜色的偏移量。

下面是一个实际中使用的颜色矩阵:




如果把这个矩阵作用于各颜色分量的话,R=A*C,计算后会发现,各个颜色分量实际上没有任何的改变(R’=R, G’=G, B’=B, A’=A)。




图1.5所示矩阵计算后会发现红色分量增加100,绿色分量增加100,这样的效果就是图片偏黄,因为红色和绿色混合后得到***,***增加了100,图片当然就偏黄了。

改变各颜色分量不仅可以通过修改第5列的颜色偏移量也可像下面矩阵那样将对应的颜色值乘以一个倍数,直接放大。图1.6就是将绿色分量乘以2变为原来的2倍。



2.2 实际应用

下面用一个例子来应用前面的知识点。

首先新建一个布局文件 color_matrix.xml,用以显示要修改的图片和 4*5 矩阵,代码如下:

[code]<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_marginTop="20dp"
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_weight="3"
        android:layout_height="0dp" />

    <!-- 4行5列矩阵 -->
    <GridLayout
        android:id="@+id/matrix"
        android:layout_width="match_parent"
        android:layout_weight="2"
        android:layout_height="0dp"
        android:rowCount="4"
        android:columnCount="5" >
    </GridLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        <Button
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="Change"
            android:onClick="changeClick"/>
        <Button
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:text="Reset"
            android:onClick="resetClick"/>
    </LinearLayout>

</LinearLayout>


添加逻辑处理类 ColorMatrix,

[code]public class ColorMatrix extends Activity {

    private ImageView imageView;
    private GridLayout matrix;
    private Bitmap bitmap;
    private int mWidth, mHeight;// 控件宽高
    private EditText[] editTexts = new EditText[20];// 保存这20个EditText
    private float[] mColorMatrix = new float[20];// 保存20个EditText的值

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.color_matrix);

        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.p1);
        imageView = (ImageView) findViewById(R.id.image_view);
        matrix = (GridLayout) findViewById(R.id.matrix);
        imageView.setImageBitmap(bitmap);// 初始展示原图片

        /**
         * 用post方法在控件绘制完后获取控件宽高,初始化每一个EditText宽高,显示20个EditText
         */
        matrix.post(new Runnable() {
            @Override
            public void run() {
                mWidth = matrix.getWidth() / 5;
                mHeight = matrix.getHeight() / 4;
                addText();
                initMatrix();// 初始化矩阵
            }
        });
    }

    /**
     * 动态添加 4*5 矩阵参数(20个EditText)
     */
    private void addText() {
        for (int i = 0; i < 20; i++) {
            EditText editText = new EditText(ColorMatrix.this);
            editTexts[i] = editText;// 初始化数组
            matrix.addView(editText, mWidth, mHeight);
        }
    }

    /**
     * 初始化矩阵:第0、6、12、18位设置为1,其他为0
     */
    private void initMatrix() {
        for (int i = 0; i < 20; i++) {
            if (i % 6 == 0) {
                editTexts[i].setText(String.valueOf(1));
            }else {
                editTexts[i].setText(String.valueOf(0));
            }
        }
    }

    public void changeClick(View view) {
        getMatrix();// 获取当前ColorMatrix里EditText所有的值
        setImageMatrix();// 应用新的颜色矩阵到bitmap
    }

    /**
     * 赋值
     */
    private void getMatrix() {
        for (int i = 0; i < 20; i++) {
            mColorMatrix[i] = Float.valueOf(editTexts[i].getText().toString());
        }
    }

    private void setImageMatrix() {
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);

        android.graphics.ColorMatrix colorMatrix = new android.graphics.ColorMatrix();
        colorMatrix.set(mColorMatrix);// 将一个数组传递进来,变为颜色矩阵

        Canvas canvas = new Canvas(bmp);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);// 抗锯齿
        paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
        canvas.drawBitmap(bitmap, 0, 0, paint);// 注意 bitmap 和 bmp别写错了

        imageView.setImageBitmap(bmp);// 处理后的图像
    }

    /**
     * 复位操作,回到初始状态
     */
    public void resetClick(View view) {
        initMatrix();// 数据回到初始化
        getMatrix();
        setImageMatrix();// 还原图像
    }


在主函数中添加点击跳转事件:

[code] public void colorMatrixClick(View view) {
       startActivity(new Intent(this, ColorMatrix.class));
 }


最后,别忘了在 AndroidManifest.xml 注册。

运行程序,并点击 ColorMatrix 按钮,跳转到如下界面:



根据前面的颜色矩阵变换知识,在 4*5 矩阵输入不同的数值,并点击 Change 按钮即可对图片进行相应的操作。此外,点击 Reset 按钮可回到初始的效果。

3. 一些效果展示

有了之前的工具类 ImageHelper,就可以在里面添加各种处理效果了。

这里添加三种处理效果为例,分别显示底片、怀旧和浮雕效果,代码如下:

[code]    /**
     * 底片效果,循环处理所有的像素点
     */
    public static Bitmap handleImageNegative(Bitmap bitmap) {

        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int color;
        int r, g, b, a;

        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

        int[] oldPx = new int[width * height];// 存储像素点的数组
        int[] newPx = new int[width * height];// 存储新像素点
        bitmap.getPixels(oldPx, 0, width, 0, 0, width, height);// 取出像素点

        for (int i = 0; i < width * height; i++) {
            color = oldPx[i];
            /**
             * 提取分离color中的r, g, b, a分量
             */
            r = Color.red(color);
            g = Color.green(color);
            b = Color.blue(color);
            a = Color.alpha(color);

            /**
             * 底片效果算法
             */
            r = 255 - r;
            g = 255 - g;
            b = 255 - b;

            // 判断是否越界
            if (r > 255) {
                r = 255;
            }else if (r < 0) {
                r = 0;
            }

            if (g > 255) {
                g = 255;
            }else if (g < 0) {
                g = 0;
            }

            if (b > 255) {
                b = 255;
            }else if (b < 0) {
                b = 0;
            }
            newPx[i] = Color.argb(a, r, g, b);// 合成新的颜色,赋给新的像素点
        }
        bmp.setPixels(newPx, 0, width, 0, 0, width, height);// 修改后的效果(注意方法为set)
        return bmp;
    }

    /**
     * 怀旧效果
     */
    public static Bitmap handleImagePixelsOldPhoto(Bitmap bitmap) {
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
                Bitmap.Config.ARGB_8888);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int color = 0;
        int r, g, b, a, r1, g1, b1;

        int[] oldPx = new int[width * height];
        int[] newPx = new int[width * height];

        bitmap.getPixels(oldPx, 0, bitmap.getWidth(), 0, 0, width, height);
        for (int i = 0; i < width * height; i++) {
            color = oldPx[i];
            a = Color.alpha(color);
            r = Color.red(color);
            g = Color.green(color);
            b = Color.blue(color);

            /**
             * 怀旧效果算法
             */
            r1 = (int) (0.393 * r + 0.769 * g + 0.189 * b);
            g1 = (int) (0.349 * r + 0.686 * g + 0.168 * b);
            b1 = (int) (0.272 * r + 0.534 * g + 0.131 * b);

            if (r1 > 255) {
                r1 = 255;
            }
            if (g1 > 255) {
                g1 = 255;
            }
            if (b1 > 255) {
                b1 = 255;
            }

            newPx[i] = Color.argb(a, r1, g1, b1);
        }
        bmp.setPixels(newPx, 0, width, 0, 0, width, height);
        return bmp;
    }

    /**
     * 浮雕效果(注意:此处从第一个像素开始,因此循环从1开始)
     */
    public static Bitmap handleImagePixelsRelief(Bitmap bitmap) {
        Bitmap bmp = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
                Bitmap.Config.ARGB_8888);
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        int color = 0, colorBefore = 0;
        int a, r, g, b;
        int r1, g1, b1;

        int[] oldPx = new int[width * height];
        int[] newPx = new int[width * height];

        bitmap.getPixels(oldPx, 0, bitmap.getWidth(), 0, 0, width, height);
        // 注意:此处循环从1开始
        for (int i = 1; i < width * height; i++) {
            colorBefore = oldPx[i - 1];
            a = Color.alpha(colorBefore);
            r = Color.red(colorBefore);
            g = Color.green(colorBefore);
            b = Color.blue(colorBefore);

            /**
             * 浮雕效果算法
             */
            color = oldPx[i];
            r1 = Color.red(color);
            g1 = Color.green(color);
            b1 = Color.blue(color);

            r = (r - r1 + 127);
            g = (g - g1 + 127);
            b = (b - b1 + 127);

            if (r > 255) {
                r = 255;
            }
            if (g > 255) {
                g = 255;
            }
            if (b > 255) {
                b = 255;
            }
            newPx[i] = Color.argb(a, r, g, b);
        }
        bmp.setPixels(newPx, 0, width, 0, 0, width, height);
        return bmp;
    }


接下来新建一个类 PixelsEffect,继承自Activity,把这几种效果显示出来,代码如下:

[code]imageView1.setImageBitmap(bitmap);// 显示原图  
imageView2.setImageBitmap(ImageHelper.handleImageNegative(bitmap));// 底片效果
imageView3.setImageBitmap(ImageHelper.handleImagePixelsOldPhoto(bitmap));// 怀旧效果      
imageView4.setImageBitmap(ImageHelper.handleImagePixelsRelief(bitmap));// 浮雕效果


最后别忘了在 AndroidManifest.xml 中注册。

运行程序,并点击 PixelsEffect 按钮,跳转到如下界面:四张图分别显示原图、底片效果、怀旧效果和浮雕效果。



这里只是一些最基本的处理,其他还有旋转、扭曲等许多操作需日后进一步学习。

PS: 个人感觉,学东西只有能用自己的话讲清楚怎么回事,才算学明白了。

现在还是囫囵吞枣,有待进一步消化理解……

主要内容整理总结自 慕课网

参考:http://www.cnblogs.com/leon19870907/articles/1978065.html

源码下载
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: