0.
之前是做java语言安卓开发,看到了图片的粒子化破碎效果,一直没时间好好研究。
这次在c#语言中做窗体应用开发,终于研究出这个效果了。
文章是借鉴Android的,不过原理都差不多。
学习网址如下:
https://www.jianshu.com/p/12184d861646
1.
先看看图片的像素级操作的代码,很简单
//初识
//创建一个2X2的图片,每个像素占24位,rgb各通道占一个字节
Bitmap cbmp = new Bitmap(2, 2, PixelFormat.Format24bppRgb);
//给每个像素上色
cbmp.SetPixel(0, 0, Color.Black);
cbmp.SetPixel(1, 0, Color.Red);
cbmp.SetPixel(0, 1, Color.White);
cbmp.SetPixel(1, 1, Color.Blue);
//保存图片,其实图片就是很多个像素的组成,每个像素是一个颜色
cbmp.Save(@"G:\新建文件夹\cbmp.png", ImageFormat.Png);
//得到图片某个像素的颜色
Color pixel_0_0 = cbmp.GetPixel(0, 0);
Color pixel_1_0 = cbmp.GetPixel(1, 0);
Color pixel_0_1 = cbmp.GetPixel(0, 1);
Color pixel_1_1 = cbmp.GetPixel(1, 1);
//得到颜色各通道的值
int r = pixel_0_0.R;
int g = pixel_0_0.G;
int b = pixel_0_0.B;
//全部像素
Color[,] colorArray = new Color[height, width];
for (int j = 0; j<height; j++) {
for (int i = 0; i<width; i++) {
colorArray[j, i] = bmp.GetPixel(i, j);
}
2.
第一次尝试粒子化 图片 (2是失败,成功见3)
既然是对像素级粒子操作,是不是应该定义一个粒子对象。
Ball.cs
class Ball{
public float x;//点位X
public float y;//点位Y
public Color color;//颜色
}
获取原图的 粒子集合 如下:
// d 代表每个粒子的大小
//把所有粒子组成图片,则大小为原图bitmap的 (bitmap.width*d , bitmap.height*d)
private List<Ball> initBall(Bitmap bitmap) {
List<Ball> mBalls = new List<Ball>();//粒子集合
for (int i = 0; i < bitmap.Width; i++) { //一列列的来
for (int j = 0; j < bitmap.Height; 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;
}
这样我们就有了一个图片的所有粒子,那么所有的操作,你都可以做了。
我们只要定一个定时器,不断的改变的每个粒子的位置,定时器中不断的重绘PictureBox就可以了。
绘制方法如下: (可以放到 PictureBox 的 OnPaint()方法中重绘)
//gp 是Graphics
private void dwawBalls(List<Ball> mBalls) {
foreach (Ball ball in mBalls) {
gp.DrawEllipse(new Pen(ball.color), new Rectangle((int)(ball.x - d / 2), (int)(ball.y - d / 2), d, d));
//gp.DrawRectangle(new Pen(ball.color) , new Rectangle((int)(ball.x - d / 2), (int)(ball.y - d / 2) , d, d)); //矩形
//可自定义其它图形代表粒子
}
}
理论上上面的代码已经解决所有问题,但是实际上,用上面的方法似乎并不能达到想要的效果。
第一,粒子组成图片背景会变化,对于有一些图片会变的很暗。
第二,粒子组成图片尺寸改变了,变成了原图的 d 倍。
第三,不利于绘制在固定大小的pictureBox控件中
所以我想到了下面的另一种达到粒子化效果的思路。
3.
因为在上面0步提供的学习网址中,似乎它的图片粒子化后组成和原图一样大,可以显示在控件中,且可以变化。那如果我用2中的方法是绝无可能的,因为粒子有大小,再组成图片肯定尺寸会变大 d 倍。
所以我不想用像素级去解决粒子化效果,就很普通的思考就有以下结果:
我们可以把一张图片分成很多个小块,不就行了吗。
当小块很多的时候,就达到了伪像素级。
所以我们定义每个小块对象如下:
MyBitmap.cs
using System.Drawing;
namespace DiyBmp {
class MyBitmap {
public Bitmap bmp;
public Point point;
public int size;
}
}
界面内容介绍如下:
- 一个 PictureBox控件画图,占满窗体。
- 3个按钮(选图 , 开启粒子破碎 , 停止破碎)
- 一个定时器 timer1
逻辑代码如下:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
namespace DiyBmp {
//在以下注释中 粒子和块表示同一个意思
public partial class Form1 : Form {
int pw; //pictureBox显示大小
int ph;
Graphics gp; //pictureBox显示的画板
int firstx = 200; //初始位置
int firsty = 100;
int dv = 100; //行 个数(粒子密度)
int dsize = 0; //根据行 个数计算出来的 块的大小(粒子的大小)
List<MyBitmap> bs = new List<MyBitmap>(); //粒子集合
public Form1() {
InitializeComponent();
pw = this.pictureBox1.Width;
ph = this.pictureBox1.Height;
}
//选图
private void button1_Click(object sender, EventArgs e) {
Bitmap exampleBmp = new Bitmap(200,200);
//不管原图多大,缩放原图大小为200,200
Bitmap sourceBmp = new Bitmap(@"G:\新建文件夹\my.png");
Graphics.FromImage(exampleBmp).DrawImage(sourceBmp,0,0,200,200); //图片缩放
exampleBmp.Save(@"G:\新建文件夹\my_200.png");
dsize = exampleBmp.Width / dv;
//在 (firstx,firsty)显示200X200的图
gp = this.pictureBox1.CreateGraphics();
gp.Clear(Color.White);
gp.DrawImage(exampleBmp , new Point(firstx , firsty));
//获取所有块
bs.Clear();
for (int x = 0; x< exampleBmp.Width; x+=dsize) {
for (int y = 0; y< exampleBmp.Height; y+=dsize) {
MyBitmap mb = new MyBitmap();
Bitmap oneb = new Bitmap(dsize,dsize);
Graphics.FromImage(oneb).Clear(Color.White);
//依次截取部分区域存储 从(原图exampleBmp x,y处截取dsize大小的图)给oneb
Graphics.FromImage(oneb).DrawImage(exampleBmp ,0 , 0, new Rectangle(x,y,dsize,dsize) , GraphicsUnit.Pixel);//图片部分截取
mb.bmp = oneb;
mb.size = dsize;
mb.point = new Point(x+firstx,y+firsty); //修改在画板中的坐标值(如果不让其默认在左上角)
bs.Add(mb);
}
}
//在 (firstx,firsty)显示200X200的图 的中心位置,决定粒子散开的路径
cx = (firstx + dv / 2 * dsize);
cy = (firsty + dv /2 * dsize);
}
//一秒定时器触发事件
private void timer1_Tick(object sender, EventArgs e) {
if (bs.Count == 0) {
this.timer1.Enabled = false;
}
//更新粒子位置
UpdateMyBitmap(bs);
this.pictureBox1.Invalidate(); //使用重绘,达到粒子体验
//drawMyBitmap(bs); //该方法显示不连续,毫无粒子体验。
}
//gp = this.pictureBox1.CreateGraphics(); //该方法毫无粒子体验
private void drawMyBitmap(List<MyBitmap> bs) {
gp.Clear(Color.White); //清除上一次位置
for (int i = 0; i < bs.Count; i++) {
gp.DrawImage(bs[i].bmp , bs[i].point);
}
}
int cx;
int cy;
long starttime = 0;
Random m = new Random();
private void UpdateMyBitmap(List<MyBitmap> b) {
for (int i = 0 ; i < b.Count; i++) {
MyBitmap mybtmp = b[i];
if (DateTime.Now.Ticks/1000 - starttime/1000 > 8000)
b.RemoveAt(i);
int x = mybtmp.point.X;
int y = mybtmp.point.Y;
//根据中心,向四个象限散去
if (x>cx && y < cy) {
x = x + 5 + m.Next(-20,20);
y = y - 5 + m.Next(-20, 20);
} else if (x<cx && y<cy) {
x = x - 5 + m.Next(-20, 20);
y = y - 5 + m.Next(-20, 20);
} else if (x < cx && y > cy) {
x = x - 5 + m.Next(-20, 20);
y = y + 5 + m.Next(-20, 20);
}
else {
x = x + 5 + m.Next(-20, 20);
y = y + 5 + m.Next(-20, 20);
}
mybtmp.point = new Point(x,y);
}
}
//开启粒子效果 定时
private void button2_Click(object sender, EventArgs e) {
starttime = DateTime.Now.Ticks;
timer1.Enabled = true;
timer1.Interval = 100;
}
//关闭粒子效果 定时
private void button3_Click(object sender, EventArgs e) {
timer1.Enabled = false;
}
//重绘
private void pictureBox1_Paint(object sender, PaintEventArgs e) {
//e.Graphics.Clear(Color.White); //清除上一次位置 。重绘已经清除了上次的
e.Graphics.Clear(Color.White);
for (int i = 0; i < bs.Count; i++) {
e.Graphics.DrawImage(bs[i].bmp, bs[i].point);
}
}
}
}
效果如下:
完!