Android 图像处理入门(上)——图像色彩变换

android:layout_margin=“10dp”

android:layout_below=“@id/seekbar_saturation”/>

然后编辑PrimaryColorActivity。这里我们定义了一个最大值及中间值,可以让它从中间值开始变化,然后通过一系列方法来分别获取色相、饱和度、亮度的值。

public class PrimaryColorActivity extends AppCompatActivity implements SeekBar.OnSeekBarChangeListener{

private ImageView mImageView;

private SeekBar mSeekbarHue,mSeekbarSaturation,mSeekbarLum;

private final static int MAX_VALUE = 255; //最大值

private final static int MID_VALUE = 127; //中间值

private float mHue,mSaturation,mLum;

private Bitmap bitmap;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_primary_color);

//获取图片

bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test1);

mImageView = (ImageView)findViewById(R.id.image_view);

mImageView.setImageBitmap(bitmap);

mSeekbarHue = (SeekBar)findViewById(R.id.seekbar_hue);

mSeekbarSaturation = (SeekBar)findViewById(R.id.seekbar_saturation);

mSeekbarLum = (SeekBar)findViewById(R.id.seekbar_lum);

mSeekbarHue.setOnSeekBarChangeListener(this);

mSeekbarSaturation.setOnSeekBarChangeListener(this);

mSeekbarLum.setOnSeekBarChangeListener(this);

mSeekbarHue.setMax(MAX_VALUE);

mSeekbarSaturation.setMax(MAX_VALUE);

mSeekbarLum.setMax(MAX_VALUE);

mSeekbarHue.setProgress(MID_VALUE);

mSeekbarSaturation.setProgress(MID_VALUE);

mSeekbarLum.setProgress(MID_VALUE);

}

@Override

public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

switch (seekBar.getId()){

case R.id.seekbar_hue:

//将0-255的值转换为色调值

mHue = (progress - MID_VALUE)1.0F/MID_VALUE180;

break;

case R.id.seekbar_saturation:

//将0-255值转换为饱和度值

mSaturation = progress*1.0F/MID_VALUE;

break;

case R.id.seekbar_lum:

//将0-255的值转换为亮度值

mLum = progress*1.0F/MID_VALUE;

break;

}

mImageView.setImageBitmap(ImageUtils.handleImageEffect(bitmap,

mHue,mSaturation,mLum));

}

@Override

public void onStartTrackingTouch(SeekBar seekBar) {

}

@Override

public void onStopTrackingTouch(SeekBar seekBar) {

}

}

运行后,拖动Seekbar,可以发现,我们成功地改变了图片的各项属性


原理分析——矩阵变换

我们之前用到了ColorMatrix类,众所周知,Matrix是矩阵的意思,所以这里实际上我们是通过操作矩阵来处理图像。Android中提供了如图的颜色矩阵,来帮助我们进行图像效果的处理, 图像中的每一个点都是一个矩阵分量,是由R、G、B、A和1组成:

我们用这个颜色矩阵乘上像素点对应的颜色矩阵分量,就能得到一个新的矩阵R1G1B1A1。如图:

这样,我们就把一个像素点通过颜色矩阵变换成了新的像素点(颜色调整后的效果)。

我们把如图的矩阵称为初始化矩阵,因为它乘上原来的像素点后,仍不变。

下面我们看一下这个矩阵,它在原来矩阵的基础上把两个地方的0变为了100,导致的后果就是原来的R G的值变为了它们加上100的值,也就是每个像素点的R G值都增加了100:

同样的,我们可以看一下这样的一个矩阵,它在原来的初始化矩阵的基础上,把矩阵G上的一个1变为了2,带入后可发现,G变为了原来的两倍,效果就是将整个图像的Green增加了两倍

我们可以发现,颜色矩阵的四行分别控制着像素点的R G B A四个属性,而颜色矩阵的第五列,我们称它为颜色偏移量,它不会直接改变某个颜色的系数,而是在原来的基础上调整整个颜色。

我们要改变一个颜色,不仅仅可以改变偏移量,还可以改变颜色的系数。

用矩阵变换来调整图像效果

我们基于原来的工程,增加一个Activity,并让MainActivity的第二个按钮跳转到此Activity。

首先是布局,我们制作如下的布局,准备在GridLayout中添加20个EditText来代表矩阵,用按钮来应用矩阵。

<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:id=“@+id/image_view”

android:layout_width=“match_parent”

android:layout_height=“0dp”

android:layout_weight=“2”/>

<GridLayout

android:id=“@+id/group”

android:layout_width=“match_parent”

android:layout_height=“0dp”

android:layout_weight=“3”

android:rowCount=“4”

android:columnCount=“5”

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“wrap_content”

android:orientation=“horizontal”>

<Button

android:id=“@+id/btn_change”

android:layout_width=“0dp”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:text=“改变”/>

<Button

android:id=“@+id/btn_reset”

android:layout_width=“0dp”

android:layout_height=“wrap_content”

android:layout_weight=“1”

android:text=“重置”/>

<

然后,我们修改Activity的代码,动态将EditText添加到GridText,来映射我们的ColorMatrix。通过改变EditText来改变我们的ColorMatrix,使图片效果变化。

public class ColorMatrixActivity extends AppCompatActivity {

private ImageView mImageView;

private GridLayout mGroup;

private Bitmap bitmap;

private int mEtWidth,mEtHeight;

private EditText[] mEditTexts = new EditText[20];

private float[] mColorMatrix = new float[20]; //对应矩阵

private Button changeButton;

private Button resetButton;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_color_matrix);

bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.test1);

mImageView = (ImageView) findViewById(R.id.image_view);

mImageView.setImageBitmap(bitmap);

mGroup = (GridLayout) findViewById(R.id.group);

//动态创建EditText,填充GridLayout

//由于在onCreate中,mGroup还没有创建完成,无法获取宽高

//所以通过post方法,在控件绘制完毕后,执行Runnable的具体方法

mGroup.post(new Runnable() {

@Override

public void run() {

mEtWidth = mGroup.getWidth()/5;

mEtHeight = mGroup.getHeight()/4;

addEditText();

initMatrix();

}

});

changeButton = (Button)findViewById(R.id.btn_change);

changeButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

changeButtonEvent();

}

});

resetButton = (Button)findViewById(R.id.btn_reset);

resetButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

resetButtonEvent();

}

});

}

private void addEditText(){

for (int i=0;i<20;i++){

EditText editText = new EditText(ColorMatrixActivity.this);

mEditTexts[i] = editText;

mGroup.addView(editText,mEtWidth,mEtHeight);

}

}

private void initMatrix(){

for (int i=0;i<20;i++){

if (i%6 == 0){

//i为第0、6、12、18位时

mColorMatrix[i]=1;

mEditTexts[i].setText(String.valueOf(1));

}else{

mColorMatrix[i]=0;

mEditTexts[i].setText(String.valueOf(0));

}

}

}

private void getMatrix(){

for (int i=0;i<20;i++){

mColorMatrix[i] = Float.valueOf(mEditTexts[i].getText().toString());

}

}

private void setImageMatrix(){

Bitmap currentBitmap = Bitmap.createBitmap(bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);

ColorMatrix colorMatrix = new ColorMatrix();

colorMatrix.set(mColorMatrix);

Canvas canvas = new Canvas(currentBitmap);

Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));

canvas.drawBitmap(bitmap,0,0,paint);

mImageView.setImageBitmap(currentBitmap);

}

public void changeButtonEvent(){

getMatrix();

setImageMatrix();

}

public void resetButtonEvent(){

initMatrix();

setImageMatrix();

}

}

可以看到,成功改变了图片效果,效果如图

学到这里,我们便可以解答之前为什么要用这样的参数来改变色相、饱和度和亮度了。以亮度为例,我们来看看setScale方法的源码。可以发现,ColorMatrix在内部也是使用这样一个颜色数组,同时将6的倍数位设置为相应的值,以此来改变亮度,说明我们如果在颜色矩阵中想要改变亮度,只需要将每个颜色的值同时提高即可。

/**

  • Set this colormatrix to scale by the specified values.

*/

public void setScale(float rScale, float gScale, float bScale,float aScale) {

final float[] a = mArray;

for (int i = 19; i > 0; --i) {

a[i] = 0;

}

a[0] = rScale;

a[6] = gScale;

a[12] = bScale;

a[18] = aScale;

}

通过这些研究,我们可以这样总结:

图像处理,实际上就是研究不同颜色矩阵对图像的处理效果

比如我们在一些图像处理app中常见的怀旧效果,通过下图的设置方法即可得到


通过像素点进行图像处理

图像经过放大后,会呈现一个个点阵,每一个点实际上就是一个像素点。通过RGB的颜色配比,就可以显示出不同的颜色。

下面是一些对像素点处理形成图像特效的例子:

底片效果

对于ABC三个像素点,求B点的底片效果的算法如下。实际上就是对每个坐标点计算它的反色,即可得到

B.r = 255 - B.r;

B.g = 255 - B.g;

B.b = 255 - B.b;

老照片效果

求老照片效果对像素点的算法如下,其中pixR就是当前像素点的R值,以此类推。

newR = (int)(0.393 * pixR + 0.769 * pixG + 0.189 * pixB);

newG = (int)(0.349 * pixR + 0.686 * pixG + 0.168 * pixB);

newB = (int)(0.272 * pixR + 0.534 * pixG + 0.131 * pixB);

浮雕效果

对于ABC三个点,求B点的浮雕效果的算法如下。

B.r = C.r - B.r + 127;

B.g = C.g - B.g + 127;

B.b = C.b - B.b + 127;

下面,我们就通过对像素点的修改来改变图像的显示效果。

先新建一个新的Activity,并在MainActivity的第三个按钮中加上跳转到它的方法。

<?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”>

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“0dp”

android:layout_weight=“1”>

<ImageView

android:id=“@+id/image_view1”

android:layout_width=“0dp”

android:layout_height=“match_parent”

android:layout_weight=“1”/>

<ImageView

android:id=“@+id/image_view2”

android:layout_width=“0dp”

android:layout_height=“match_parent”

android:layout_weight=“1”/>

<LinearLayout

android:layout_width=“match_parent”

android:layout_height=“0dp”

android:layout_weight=“1”>

<ImageView

android:id=“@+id/image_view3”

android:layout_width=“0dp”

android:layout_height=“match_parent”

android:layout_weight=“1”/>

<ImageView

android:id=“@+id/image_view4”

android:layout_width=“0dp”

android:layout_height=“match_parent”

android:layout_weight=“1”/>

加入相应特效方法

我们在ImageUtils中加入几个新方法,分别做不同的处理

反色效果

代码如下,我们在里面新建了一个对应图片的像素的数组,然后通过getPixels方法获取所有像素。

getPixels的第二个参数是代表起点的偏移量,第三个参数是控制读取数组时的行距,一般使用width,后面两个参数代表第一次读取像素点的坐标,倒数第二个参数代表我们从bitmap中读取的宽度,最后一个是读取的高度。

然后我们分别对每个像素点通过Color类的red green blue alpha方法来获取r g b a四个值,并通过算法改变它的rgb值,并通过Color的argb方法转换为新的像素数组。需要注意的是,改变它的rgb值时,需要判断一下有没有超过0-255的限制,有的话则赋值为255或0

Bitmap currentBitmap = 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];

r = Color.red(color);

g = Color.green(color);

b = Color.blue(color);

a = Color.alpha(color);

//通过算法计算新的rgb值

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);

}

currentBitmap.setPixels(newPx,0,width,0,0,width,height);

return currentBitmap;

}

老照片效果

其他代码基本与之前的一样,只是算法稍作变动,并且不能在原来的rgb基础上改动:

public static Bitmap handleImageOldpicture(Bitmap bitmap){

int width = bitmap.getWidth();

int height = bitmap.getHeight();

int color;

int r,g,b,a;

Bitmap currentBitmap = 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];

r = Color.red(color);

g = Color.green(color);

b = Color.blue(color);

a = Color.alpha(color);

int r1,g1,b1;

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;

else if (r1 < 0) r1=0;

if (g1 > 255) g1=255;

else if (g1 < 0) g1=0;

if (b1 > 255) b1=255;

else if (b1 < 0) b1=0;

newPx[i] = Color.argb(a,r1,g1,b1);

}

currentBitmap.setPixels(newPx,0,width,0,0,width,height);

return currentBitmap;

}

浮雕效果

与之前的差不多,唯一需要注意的是,我们需要用到前一个像素点的颜色,所以需要从1开始循环,然后通过相应算法,获取图片

public static Bitmap handleImagePixelsRelief(Bitmap bitmap){

int width = bitmap.getWidth();

int height = bitmap.getHeight();

int color,colorBefore;

int r,g,b,a;

int r1,g1,b1;

Bitmap currentBitmap = 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);

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-j0EuebNn-1713017976759)]

[外链图片转存中…(img-75Ofq9Ra-1713017976760)]

[外链图片转存中…(img-DtUDC2ZH-1713017976761)]

[外链图片转存中…(img-kx7Wzk21-1713017976761)]

[外链图片转存中…(img-N5fawvNZ-1713017976761)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持)

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

[外链图片转存中…(img-FpI2el0d-1713017976761)]

[外链图片转存中…(img-ZuozOHoa-1713017976762)]

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

[外链图片转存中…(img-Hvsww8qi-1713017976762)]

  • Android BAT大厂面试题(有解析)

[外链图片转存中…(img-cCm5EcYw-1713017976762)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值