零, 前言
1. 粒子效果的核心有三个点: 收集粒子, 更改粒子, 显示粒子
2.Bitmap 的可以获取像素, 从而得到每个像素的颜色值
3. 可以通过粒子拼合一张图片, 并对粒子操作完成很多意想不到的效果
4. 本项目源码见文尾捷文规范第一条, 文件为 BitmapSplitView.java
一, 初识
1. 什么是 Bitmap 像素级的操作:
相信大家都知道一张 jpg 或 PNG 放大后会是一个个小格子, 称为一个像素 (px), 而且一个小格子是一种颜色
也就是一张 jpg 或 PNG 图片就是很多颜色的合集, 而这些合集信息都被封装到了 Bitmap 类中
你可以使用 Bitmap 获取任意像素点, 并修改它, 对与某像素点而言, 颜色信息是其主要的部分
2. 获取第一个像素
- Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.iv_200x200);
- int color_0_0 = bitmap.getPixel(0, 0);// 获取第 1 行, 第 1 个像素颜色
使用该颜色画一个圆测试一下:
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 就是压缩到一个输出流里, 写成文件
- /**
- * 保存 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:0
- printColor("pixel_1_0", pixel_1_0);// 红: a:255, r:255, g:0, b:0
- printColor("pixel_0_1", pixel_0_1);// 白: a:255, r:255, g:255, b:255
- printColor("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. 初始化粒子:
- 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. 正方形粒子的绘制 (原图复刻):
- @Override
- protected 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);
5. 圆形复刻
这里的 4 中的图片已经不是 bitmap 了, 而是由一个个小正方形堆积成的东西, 这些小正方形拥有自己的颜色
然而我们可以利用它实现一些好玩的东西, 比如不画正方形, 画个圆怎么样?
现在你明白像素级操作什么了吧. 也许你会感叹, 还能怎么玩, 先把下巴收回去, 后面还有更惊叹的呢.
- for (Ball ball : mBalls) {
- mPaint.setColor(ball.color);
- canvas.drawCircle(ball.x, ball.y, d/2, mPaint);
- }
6. 其他形状复刻
你可以用任意的图形更换粒子单元, 或者各种形状的粒子混合适用, 全凭你的操作
像素单元都在你的手上了, 还有什么不能做.
- 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 篇 https://www.jianshu.com/p/41862437cd04 中详细介绍了使用 ColorFilter 和 ColorMatrix 改变图片颜色
这里既然拿到了小球颜色, 那么统一改变一下小球的颜色则何如?
注: 以下属于改变颜色的旁门左道, 主要为了演示. 正规还是用 ColorFilter 和 ColorMatrix 吧
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 的运动:
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
- // 初始化时间流 ValueAnimator
- mAnimator = ValueAnimator.ofFloat(0, 1);
- mAnimator.setRepeatCount(-1);
- mAnimator.setDuration(2000);
- mAnimator.setInterpolator(new LinearInterpolator());
- mAnimator.addUpdateListener(animation -> {
- updateBall();// 更新小球位置
- invalidate();
- });
4. 点击开启 ValueAnimator
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- mRunTime = System.currentTimeMillis();// 记录点击时间
- mAnimator.start();
- break;
- }
- return true;
- }
好了, 本篇就到这里, 你是不是更清楚 Bitmap 是什么了?
希望本文可以给你一点灵感, 你能做出更酷炫的东西.
来源: https://juejin.im/post/5bf09ae96fb9a049c43d40b3