Android粒子篇之Bitmap畫素級操作

張風捷特烈發表於2018-11-18

零、前言

1.粒子效果的核心有三個點:收集粒子、更改粒子、顯示粒子
2.Bitmap的可以獲取畫素,從而得到每個畫素的顏色值
3.可以通過粒子拼合一張圖片,並對粒子操作完成很多意想不到的效果
4.本專案原始碼見文尾捷文規範第一條,檔案為BitmapSplitView.java


一、初識

1.什麼是Bitmap畫素級的操作:

相信大家都知道一張jpg或png放大後會是一個個小格子,稱為一個畫素(px),而且一個小格子是一種顏色
也就是一張jpg或png圖片就是很多顏色的合集,而這些合集資訊都被封裝到了Bitmap類中
你可以使用Bitmap獲取任意畫素點,並修改它,對與某畫素點而言,顏色資訊是其主要的部分

畫素.png

2.獲取第一個畫素
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.iv_200x200);
int color_0_0 = bitmap.getPixel(0, 0);
//獲取第1行,第1個畫素顏色複製程式碼

使用該顏色畫一個圓測試一下:

第一點.png

3.獲取所有點畫素顏色值

這裡i代表列數,j代表行數,mColArr[i][j]代表是一個圖片第i列,第j行的畫素顏色值

 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.iv_200x200);
mColArr = new int[bitmap.getWidth()][bitmap.getHeight()];
for (int i = 0;
i <
bitmap.getWidth();
i++) {
for (int j = 0;
j <
bitmap.getHeight();
j++) {
mColArr[i][j] = bitmap.getPixel(i, j);

}
}複製程式碼

二、分析:

1.首先看一下如何建立一個Bitmap物件

新建一個2*2的ARGB_8888圖片:顏色分別是黑(0,0)、紅(1,0)、白(0,1)、藍(1,1)

Bitmap bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888);
bitmap.setPixel(0, 0, Color.BLACK);
bitmap.setPixel(1, 0, Color.RED);
bitmap.setPixel(0, 1, Color.WHITE);
bitmap.setPixel(1, 1, Color.BLUE);
複製程式碼

2.儲存bitmap成為圖片到本地

儲存bitmap就是壓縮到一個輸出流裡,寫成檔案

輸出結果.png
    /**     * 儲存bitmap到本地     *     * @param path    路徑     * @param mBitmap 圖片     * @return 路徑     */    public static String saveBitmap(String path, Bitmap mBitmap) { 
File filePic = FileHelper.get().createFile(path + ".png");
try {
FileOutputStream fos = new FileOutputStream(filePic);
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
fos.close();

} catch (IOException e) {
e.printStackTrace();
return null;

} return filePic.getAbsolutePath();

}複製程式碼

3.bitmap.getPixel到底說了什麼?
int pixel_0_0 = bitmap.getPixel(0, 0);
int pixel_1_0 = bitmap.getPixel(1, 0);
int pixel_0_1 = bitmap.getPixel(0, 1);
int pixel_1_1 = bitmap.getPixel(1, 1);
黑:pixel_0_0:-16777216 紅:pixel_1_0:-65536 白:pixel_0_1:-1 藍:pixel_1_1:-16776961 複製程式碼

都是負數,感覺不怎麼好懂:其實那就是顏色值
Color類中有幾個方法可以方便獲取argb分別對應的值,下面測試一下你就明白了
其實就是將int進行了位運算,分離出argb四個通道的值

printColor("pixel_0_0", pixel_0_0);
//黑:a:255, r:0, g:0, b:0printColor("pixel_1_0", pixel_1_0);
//紅:a:255, r:255, g:0, b:0printColor("pixel_0_1", pixel_0_1);
//白:a:255, r:255, g:255, b:255printColor("pixel_1_1", pixel_1_1);
//藍:a:255, r:0, g:0, b:255/** * 將顏色從int 拆分成argb,並列印出來 * @param msg * @param color */private void printColor(String msg, int color) {
int a = Color.alpha(color);
int r = Color.red(color);
int g = Color.green(color);
int b = Color.blue(color);
L.d(msg + "----a:" + a + ", r:" + r + ", g:" + g + ", b:" + b + L.l());

}複製程式碼

二、bitmap的花樣復刻:

既然畫素顏色資訊拿到手了,不就等於天下我有了嗎?

1.bitmap復刻的粒子載體
public class Ball implements Cloneable { 
public float aX;
//加速度 public float aY;
//加速度Y public float vX;
//速度X public float vY;
//速度Y public float x;
//點位X public float y;
//點位Y public int color;
//顏色 public float r;
//半徑 public long born;
//誕生時間 public Ball clone() {
Ball clone = null;
try {
clone = (Ball) super.clone();

} catch (CloneNotSupportedException e) {
e.printStackTrace();

} return clone;

}
}複製程式碼

2.初始化粒子:
座標分析.png
private int d = 50;
//復刻的畫素邊長private List<
Ball>
mBalls = new ArrayList<
>
();
//粒子集合複製程式碼
/** * 根畫素初始化粒子 * @param bitmap * @return */private List<
Ball>
initBall(Bitmap bitmap) {
for (int i = 0;
i <
bitmap.getHeight();
i++) {
for (int j = 0;
j <
bitmap.getWidth();
j++) {
Ball ball = new Ball();
ball.x = i * d + d / 2;
ball.y = j * d + d / 2;
ball.color = bitmap.getPixel(i, j);
mBalls.add(ball);

}
} return mBalls;

}複製程式碼

3.正方形粒子的繪製(原圖復刻):
原圖復刻.png
@Overrideprotected void onDraw(Canvas canvas) { 
super.onDraw(canvas);
canvas.save();
canvas.translate(mCoo.x, mCoo.y);
for (Ball ball : mBalls) {
mPaint.setColor(ball.color);
canvas.drawRect( ball.x - d / 2, ball.y - d / 2, ball.x + d / 2, ball.y + d / 2, mPaint);

} canvas.restore();
HelpDraw.draw(canvas, mGridPicture, mCooPicture);

}複製程式碼

4.復刻其他圖片資原始檔

只要是bitmap就行了,我們可以decode圖片變成Bitmap
注意:此處只是演示,畫一張bitmap還是用原生的好,畫素級的粒子化由它的優勢,比如運動

//載入圖片陣列mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.iv_200x200);
initBall(mBitmap);
複製程式碼
原圖復刻圖片資源.png

5.圓形復刻

這裡的4中的圖片已經不是bitmap了,而是由一個個小正方形堆積成的東西,這些小正方形擁有自己的顏色
然而我們可以利用它實現一些好玩的東西,比如不畫正方形,畫個圓怎麼樣?
現在你明白畫素級操作什麼了吧。也許你會感嘆,還能怎麼玩,先把下巴收回去,後面還有更驚歎的呢。

圓形復刻圖片資源.png
 for (Ball ball : mBalls) { 
mPaint.setColor(ball.color);
canvas.drawCircle(ball.x, ball.y, d/2, mPaint);

}複製程式碼

6.其他形狀復刻

你可以用任意的圖形更換粒子單元,或者各種形狀的粒子混合適用,全憑你的操作
畫素單元都在你的手上了,還有什麼不能做。

五角星復刻圖片資源.png
for (int i = 0;
i <
mBalls.size();
i++) {
canvas.save();
int line = i % mBitmap.getHeight();
int row = i / mBitmap.getWidth();
canvas.translate(row * 2 * d, line * 2 * d);
mPaint.setColor(mBalls.get(i).color);
canvas.drawPath(CommonPath.nStarPath(5, d, d / 2), mPaint);
canvas.restore();

}複製程式碼

三、關於顏色操作:

Color篇中詳細介紹了使用ColorFilterColorMatrix改變圖片顏色
這裡既然拿到了小球顏色,那麼統一改變一下小球的顏色則何如?
注:以下屬於改變顏色的旁門左道,主要為了演示。正規還是用ColorFilterColorMatrix

顏色處理.png
1.下面是圖片黑白化的演算法

以前在JavaScript用過,在Python用過,現在終於用到java上了,不免感慨,語言無界限,真理永恆
所以大家最好收集一下相關的真理,不管何時都不會過時,也不會錯誤,就像1+1=2

/** * 根畫素初始化粒子 * * @param bitmap * @return */private List<
Ball>
initBall(Bitmap bitmap) {
for (int i = 0;
i <
bitmap.getHeight();
i++) {
for (int j = 0;
j <
bitmap.getWidth();
j++) {
Ball ball = new Ball();
ball.x = i * d + d / 2;
ball.y = j * d + d / 2;
//獲取畫素點的a、r、g、b int color_i_j = bitmap.getPixel(i, j);
int a = Color.alpha(color_i_j);
int r = Color.red(color_i_j);
int g = Color.green(color_i_j);
int b = Color.blue(color_i_j);
ball.color = blackAndWhite(a, r, g, b);
mBalls.add(ball);

}
} return mBalls;

}/** * 配湊黑白顏色 */private int blackAndWhite(int a, int r, int g, int b) {
//拼湊出新的顏色 int grey = (int) (r * 0.3 + g * 0.59 + b * 0.11);
if (grey >
255 / 2) {
grey = 255;

} else {
grey = 0;

} return Color.argb(a, grey, grey, grey);

}複製程式碼
2.灰色處理函式
/** * 配湊灰顏色 */private int grey(int a, int r, int g, int b) { 
//拼湊出新的顏色 int grey = (int) (r * 0.3 + g * 0.59 + b * 0.11);
return Color.argb(a, grey, grey, grey);

}複製程式碼
3.顏色反轉
//顏色反轉private int reverse(int a, int r, int g, int b) { 
//拼湊出新的顏色 return Color.argb(a, 255-r, 255-g, 255-b);

}複製程式碼

四、粒子運動

到了最重要的地方了,讓小球動起來的核心分析見Android原生繪圖之讓你瞭解View的運動:

重力擴散.gif
1.將一個圖片粒子化的方法

這裡速度x方向是正負等概率隨機數值,所以粒子會呈現左右運動趨勢
有一定的y方向速度,但加速度aY向下,導致粒子向下運動,綜合效果就是兩邊四散加墜落
要改變粒子的運動方式,只要改變粒子的這些引數就行了。

/** * 根畫素初始化粒子 * * @param bitmap * @return */private void initBall(Bitmap bitmap) { 
for (int i = 0;
i <
bitmap.getHeight();
i++) {
for (int j = 0;
j <
bitmap.getWidth();
j++) {
Ball ball = new Ball();
//產生粒子---每個粒子擁有隨機的一些屬性資訊 ball.x = i * d + d / 2;
ball.y = j * d + d / 2;
ball.vX = (float) (Math.pow(-1, Math.ceil(Math.random() * 1000)) * 20 * Math.random());
ball.vY = rangeInt(-15, 35);
ball.aY = 0.98f;
ball.color = bitmap.getPixel(i, j);
ball.born = System.currentTimeMillis();
mBalls.add(ball);

}
}
}複製程式碼
2.更新小球
/** * 更新小球 */private void updateBall() { 
for (int i = 0;
i <
mBalls.size();
i++) {
Ball ball = mBalls.get(i);
if (System.currentTimeMillis() - mRunTime >
2000) {
mBalls.remove(i);

} ball.x += ball.vX;
ball.y += ball.vY;
ball.vY += ball.aY;
ball.vX += ball.aX;

}
}複製程式碼
3.初始化時間流:ValueAnimator
//初始化時間流ValueAnimatormAnimator = ValueAnimator.ofFloat(0, 1);
mAnimator.setRepeatCount(-1);
mAnimator.setDuration(2000);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(animation ->
{
updateBall();
//更新小球位置 invalidate();

});
複製程式碼
4.點選開啟ValueAnimator
@Overridepublic boolean onTouchEvent(MotionEvent event) { 
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: mRunTime = System.currentTimeMillis();
//記錄點選時間 mAnimator.start();
break;

} return true;

}複製程式碼

好了,本篇就到這裡,你是不是更清楚Bitmap是什麼了?
希望本文可以給你一點靈感,你能做出更酷炫的東西。


後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1–github 2018-11-17 Android粒子篇之Bitmap畫素級操作
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1—-本文由張風捷特烈原創,轉載請註明
2—-歡迎廣大程式設計愛好者共同交流
3—-個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4—-看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

來源:https://juejin.im/post/5bf09ae96fb9a049c43d40b3

相關文章