这个是我做的效果:
源码地址(CSDN)http://download.csdn.net/detail/crystal_lz/5734621
到今天为止 有一年多几天了、、、其实当时是在bilibili看到一个任务管理器版本的bad apple然后自己无聊也捣鼓了一下、、发现还是比较好玩的 下面这个是当时看到的任务管理器的版本(不过很明显 水准比我的高哪去了):
来说说思路吧、、其实我做的这个很简单的没啥难度、、找到句柄绘制就是了、、
首先是收集资源 也就是图片、、当时我是随便下载的一个视频 然后直接在写一个程序以20帧的平率截屏、、然后播放视屏的时候 把图全部抓下来了 几千张图片就有了 然后就是背景音乐了 找一个wav的音频文件用win32的api播放或者.Net库里面自带的那个System.Media.SoundPlayer()来播放就是了
先来说说最简单的播放、、首先得到目标窗体的句柄 然后得到他的DC或者.Net里面直接Graphics.FromHwnd()然后播放音乐的同时 以20帧的频率再绘制就是了、、
在视频中看到 我的可以播放线条画、、当时是我自己想的一个算法来实现把图片线条画的 当然有专门的边缘检测的算法来 比如Canny 弄成线条画、、但是在找算法的时候 我脑子里面自己已经有了一个思路如何来把原来的影绘画变成线条画、、于是就果断试了一下虽然效果没有Canny好 但是速度还算不错、、要知道速度是20帧的频率绘制 如果生成每一张图的时间超过了50毫秒、、那么播放的图像速度就会跟不上音频的节奏、、
还有在上面 可以看到 能够在子控件上播放、、就像在计算器上面的那个效果、、其实功能上有偷懒、、但是偷懒的结果是在计算器上能正常运行、、其实说思路的话 就是把一张图分成几个区域 然后绘制到相应的控件上去 但是我并没有用那些子控件的句柄 而是依然用父窗口的句柄 然后把图像分块绘制到父窗口上 这样看起来就想象是绘制到子控件上去了 其实并没有用子控件的句柄、、
using (Graphics g = Graphics.FromHwnd(m_hWnd)) {//父窗口句柄 if (chbox_bPlayOnSubCtrl.Checked) { //如果选中了在子控件绘制 for (int i = 0, len = m_lstRectDraw.Count; i < len; i++)//将图分块绘制到父窗口 g.DrawImage(bmpReSize, m_lstRectDraw[i], m_lstRectDraw[i], GraphicsUnit.Pixel); } else g.DrawImage(bmpReSize, 0, 0); }
如果切换到子控件进行绘制的话 那就是在循环里面执行Graphics.FromHwnd()了、、要知道 一张图你只有50毫秒的时间给你捣腾能节约就节约、、如果在其他程序上 你选择 在子控件上播放 你看不到任何效果的、、不是没有绘制 而是绘制到控件的背后去了、、至于为什么 按层次来说 子控件是在父窗口上面的 会挡住父窗口 而绘制用的是父窗口的句柄、、所以 就只有悲剧了、、至于为什么计算器上能够正常 我也不知道 那是个意外、、当时也只是想着先做出效果再说 所以也就没有去研究为啥计算器上可以正常使用、、
程序开始的时候 首先将资源反序列化出来以便需要:
[Serializable] public class BadAppleRes { private ListlstImgByte; public List LstImgByte { get { return lstImgByte; } set { lstImgByte = value; } } } //将资源反序列化出来 List 里面全部是每一张图的二进制数据 BadAppleResource = (BadAppleRes)ByteToObject(System.IO.File.ReadAllBytes("BadAppleRes.r")); //使用的时候 using (Stream s = new MemoryStream(BadAppleResource.LstImgByte[index])) { using (Bitmap bmpSrc = new Bitmap(s)) { //这里使用 } }
如果 说是直接绘制原生的影绘图就可以直接用这张图绘制了 大小什么的自己定义 如果要子控件上播放 就想上面的那样 分块绘制
然后是处理成线条、、其实我的思路很简单、、就是先将图二值化、、然后整个图只有黑白二色、、、这个时候 很明显 如果说相邻的两个像素点颜色不一样 那里必然是一个分界点、、那么标记 继续扫描看看是否有相邻的两个像素点颜色不一样的 如果有不一样的那么就标记 下面是整个过程的代码、、
public static Bitmap MixLineImage (Bitmap bmpSrc, Bitmap bmpBack, Color backColor, Color lineColor) { Bitmap bmp = new Bitmap(bmpBack.Width, bmpBack.Height, PixelFormat.Format32bppArgb); using (Bitmap bmpTemp = new Bitmap (bmpSrc.Width, bmpSrc.Height, PixelFormat.Format32bppArgb)) { using (SolidBrush sb = new SolidBrush(backColor)) { using (Graphics g = Graphics.FromImage(bmpTemp)) { g.FillRectangle(sb, 0, 0, bmpTemp.Width, bmpTemp.Height); }//在bmpTemp上刷上用户设置的背景色 } BitmapData bmpDataSrc = bmpSrc.LockBits( new Rectangle(0, 0, bmpTemp.Width, bmpTemp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); byte[] byColorInfoSrc = new byte[bmpTemp.Height * bmpDataSrc.Stride]; Marshal.Copy(bmpDataSrc.Scan0, byColorInfoSrc, 0, byColorInfoSrc.Length); BitmapData bmpData = bmpTemp.LockBits( new Rectangle(0, 0, bmpTemp.Width, bmpTemp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); byte[] byColorInfo = new byte[byColorInfoSrc.Length]; Marshal.Copy(bmpData.Scan0, byColorInfo, 0, byColorInfo.Length); for (int x = 0, xLen = bmpTemp.Width - 1; x < xLen; x++) {//开始遍历像素 for (int y = 0, yLen = bmpTemp.Height - 1; y < yLen; y++) { int indexB = y * bmpDataSrc.Stride + x * 4; int indexG = y * bmpDataSrc.Stride + x * 4 + 1; int indexR = y * bmpDataSrc.Stride + x * 4 + 2; byte byV = (byte)(GetAvg(//当前像素点二值后颜色 byColorInfoSrc[indexB], byColorInfoSrc[indexG], byColorInfoSrc[indexR]) > 95 ? 255 : 0); byte byVR = (byte)(GetAvg(//右边一个像素点二值后颜色 byColorInfoSrc[indexB + 4], byColorInfoSrc[indexG + 4], byColorInfoSrc[indexR + 4]) > 95 ? 255 : 0); byte byVB = (byte)(GetAvg(//下边一个点的二值后颜色 byColorInfoSrc[indexB + bmpDataSrc.Stride], byColorInfoSrc[indexG + bmpDataSrc.Stride], byColorInfoSrc[indexR + bmpDataSrc.Stride]) > 95 ? 255 : 0); if (byV != byVR) {//当前点和右边颜色不一样 则是边界 byColorInfo[indexB] = lineColor.B; byColorInfo[indexG] = lineColor.G; byColorInfo[indexR] = lineColor.R; byColorInfo[indexR + 1] = lineColor.A; } if (byV != byVB) {//当前点和下边颜色不一样 则是边界 byColorInfo[indexB] = lineColor.B; byColorInfo[indexG] = lineColor.G; byColorInfo[indexR] = lineColor.R; byColorInfo[indexR + 1] = lineColor.A; } } } //Marshal.Copy(byColorInfoSrc, 0, bmpDataSrc.Scan0, byColorInfoSrc.Length); bmpSrc.UnlockBits(bmpDataSrc);//位图数据解锁 Marshal.Copy(byColorInfo, 0, bmpData.Scan0, byColorInfo.Length); bmpTemp.UnlockBits(bmpData); using (Graphics g = Graphics.FromImage(bmp)) { g.DrawImage(bmpBack, 0, 0);//将背景图画上(线条画后面的图) g.DrawImage(bmpTemp, new Rectangle(Point.Empty, bmpBack.Size),//叠加线条画 new Rectangle(Point.Empty, bmpTemp.Size), GraphicsUnit.Pixel); } } return bmp; } private static byte GetAvg(byte b, byte g, byte r) { return (byte)((r + g + b) >> 2);//本来应该 /3 没关系 反正影绘图接近二值化 配合上面判断就行 }代码基本就这些 其他没啥说的 这东西也不是好难、、有思路谁都可以搞定、、、