CSkin博客

标题: 【C#音乐播放器】用GDI+绘制环形频谱 [打印本页]

作者: xifengcanyue    时间: 2017-2-21 13:10
标题: 【C#音乐播放器】用GDI+绘制环形频谱
说明:
自制用GDI+绘制环形频谱

效果截图:
1.播放前。

2.播放时。



思路讲解:
整个程序是两个窗体,一个下层窗体负责绘制频谱,一个上层窗体负责播放控制等。
-->上层窗体是用的LayeredSkin界面库,因为要窗体判断鼠标位置实现淡入淡出效果,而一般窗体我不知道该怎么实现,只好用别人的。
      这个因为是用的别人的现成的,我就不多叙述了

-->下层窗体就是一般的Form窗体,使用UpdateLayeredWindow函数绘制透明窗体,
[C#] 纯文本查看 复制代码
public void SetBits(Bitmap bitmap)//调用UpdateLayeredWindow()方法。
        {
            if (!Bitmap.IsCanonicalPixelFormat(bitmap.PixelFormat) || !Bitmap.IsAlphaPixelFormat(bitmap.PixelFormat))
                throw new ApplicationException("图片必须是32位带Alhpa通道的图片。");
...
//发帖字数限制就不详细贴代码了,源码里有
        }


因为需要一张带透明通道的图片,我没有,但是可以创建一个
[C#] 纯文本查看 复制代码
private Bitmap GetBitmap()
        {
            Bitmap bmp = new Bitmap(this.Width, this.Height);//创建位图
            Graphics g = Graphics.FromImage(bmp);//创建画布
            SolidBrush sb = new SolidBrush(Color.FromArgb(0, 0, 0, 0));//创建一个带透明通道的笔刷,改变Alpha值可以调节窗体的透明度
            Rectangle r = new Rectangle(0, 0, this.Width, this.Height);
            g.FillRectangle(sb, r);//用这个笔刷填充整个位图,达到创建透明位图的效果

            return bmp;
        }


然后在窗体加载事件中直接调用SetBits()函数就能让窗体任意透明了
[C#] 纯文本查看 复制代码
Bitmap bmp = GetBitmap();
SetBits(bmp);


部分代码:
接下来就是在这张带透明通道的位图上绘制频谱了(频谱数据利用bass音频处理库获取)
有点乱,我也没怎么整理,就将就吧
[C#] 纯文本查看 复制代码

        #region 谱线的字段及属性
        Pen p;//内圈画笔
        Pen p2;//外圈画笔
        private float _DeviationAngle = 0;//偏移角度
        double offsetAngle = (double)360 / 90;//谱线间相隔角度(180/45)
        private PointF _CenterPointF;//频谱中心点
        private int _LineNum = 45;//谱线数目(一半,为了对称好看)
        private int _LineWidth = 3;//线宽
        private int _LineAlpha = 50;//线透明度
        private int _LineStopHeight = 5;//谱线平常高度
        private int _LineHeight = 0;//数据填充时谱线高度
        private int _LineR = 100;//谱线的RGB值
        private int _LineG = 100;
        private int _LineB = 100;
        private int _Speed = 100;//刷新频率
        private float[] fft;
        private int[] _LineAlphas = new int[45];//谱线的渐变透明度
        private int[] _OldH = new int[45];//外圈谱线上一次高度
        private int[] _OldH2 = new int[45];//内圈谱线上一次高度
        private string _NewTime = "00:00";//已播放的时间
        private string _EndTime = "00:00";//播放总时间
        private string _Title = "听音乐";//歌曲名
        private string _Artist = "心灵之音";//作家
        private int _FontAlpha = 200;//字体透明度

        #endregion

        #region 绘制频谱
        private void _Timer1_Tick(object sender, EventArgs e)
        {
            p = new Pen(Color.FromArgb(this._LineAlpha, this._LineG, this._LineR, this._LineB), _LineWidth);
            p2 = new Pen(Color.FromArgb(this._LineAlpha, this._LineG, this._LineR, this._LineB), _LineWidth + 1);
            _CenterPointF = new PointF(this.Width / 2, this.Height / 2);

            using (bmp = new Bitmap(this.Width, this.Height))
            {
                g = Graphics.FromImage(bmp);
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                g.FillRectangle(sb, r);
               
                int _LineHeight1 = 0;
                int _LineHeight2 = 0;

                for (int i = 0; i < _LineNum; i++)
                {
                    if (fft == null)
                    {
                        g.DrawLine(p2, GetPointF(_CenterPointF, 140, offsetAngle * i + _DeviationAngle), GetPointF(_CenterPointF, 140 + _LineStopHeight, offsetAngle * i + _DeviationAngle));
                        g.DrawLine(p2, GetPointF(_CenterPointF, 140, 360 - offsetAngle * (i + 1) + _DeviationAngle), GetPointF(_CenterPointF, 140 + _LineStopHeight, 360 - offsetAngle * (i + 1) + _DeviationAngle));
                        g.DrawLine(p, GetPointF(_CenterPointF, 136, offsetAngle * i - _DeviationAngle), GetPointF(_CenterPointF, 136, offsetAngle * i - _DeviationAngle));
                        g.DrawLine(p, GetPointF(_CenterPointF, 136, 360 - offsetAngle * (i + 1) - _DeviationAngle), GetPointF(_CenterPointF, 136, 360 - offsetAngle * (i + 1) - _DeviationAngle));
                    }
                    else
                    {
                        _LineHeight = (int)(fft* 6000 * 5000);
                        #region  外圈
                        _LineHeight1 = (int)Math.Pow(_LineHeight, 1 / 3f);//减小谱线间的差距
                        if (_LineHeight1 <= 45)
                        {
                            _LineHeight1 = _LineStopHeight;
                        }
                        else if (_LineHeight1 >= 170)
                        {
                            _LineHeight1 = 170;
                        }
                        if (_OldH > _LineHeight1)
                        {
                            _OldH -= 7;//回落速度
                            if (_LineAlphas > _LineAlpha)
                            {
                                _LineAlphas -= 18;
                            }
                            else { _LineAlphas = _LineAlpha; }
                        }
                        else
                        {
                            _OldH = _LineHeight1;
                            _LineAlphas = 255;
                        }

                        if (_OldH <= _LineStopHeight)
                        {
                            _OldH = _LineStopHeight;
                            _LineAlphas = _LineAlpha;
                        }
                        #endregion

                        #region 内圈
//同理
                        #endregion

                        p2.Color = Color.FromArgb(_LineAlphas, this._LineG, this._LineR, this._LineB);
                        p.Color = Color.FromArgb(_LineAlphas, this._LineG, this._LineR, this._LineB);
                        //为了频谱对称好看,所以采取对半绘制
                        g.DrawLine(p2, GetPointF(_CenterPointF, 140, offsetAngle * i + _DeviationAngle), GetPointF(_CenterPointF, 140 + _OldH, offsetAngle * i + _DeviationAngle));
                        g.DrawLine(p2, GetPointF(_CenterPointF, 140, 360 - offsetAngle * (i + 1) + _DeviationAngle), GetPointF(_CenterPointF, 140 + _OldH, 360 - offsetAngle * (i + 1) + _DeviationAngle));
                        g.DrawLine(p, GetPointF(_CenterPointF, 136, offsetAngle * i - _DeviationAngle), GetPointF(_CenterPointF, 136 - _OldH2, offsetAngle * i - _DeviationAngle));
                        g.DrawLine(p, GetPointF(_CenterPointF, 136, 360 - offsetAngle * (i + 1) - _DeviationAngle), GetPointF(_CenterPointF, 136 - _OldH2, 360 - offsetAngle * (i + 1) - _DeviationAngle));
                    }
                }

                StringFormat sf = new StringFormat();
                sf.LineAlignment = StringAlignment.Center;
                sf.Alignment = StringAlignment.Center;
                g.DrawString(_NewTime + "\n" + _EndTime, new Font("李旭科书法 v1.4", 50, FontStyle.Bold), new SolidBrush(Color.FromArgb(this._FontAlpha, this._LineG, this._LineR, this._LineB)), this.ClientRectangle, sf);
                g.DrawString(_Title + "\n" + _Artist, new Font("宋体", 15, FontStyle.Bold), new SolidBrush(Color.FromArgb(this._FontAlpha, this._LineG, this._LineR, this._LineB)), this.ClientRectangle, sf);
                _DeviationAngle = _DeviationAngle >= 360 ? 0 : _DeviationAngle + 0.4f;
                SetBits(bmp);
                bmp.Dispose();
                g.Dispose();
            }
            p.Dispose();
            p2.Dispose();
        }
        /// <summary>
        /// 根据中心点 半径 和角度,获取从中心点出发的线段终点
        /// </summary>
        private PointF GetPointF(PointF centerPointF, int r, double angle)
        {
            double A = Math.PI * angle / 180;//(angle/360)*2PI
            float xF = centerPointF.X + r * (float)Math.Cos(A);
            float yF = centerPointF.Y + r * (float)Math.Sin(A);

            return (new PointF(xF, yF));
        }

        #endregion


编后语:

源代码和demo下载:
MusicPlayerOfBeta.zip (1.68 MB, 下载次数: 672, 售价: 1 金钱)
字体下载:
李旭科书法1.4.zip (3.69 MB, 下载次数: 315)



作者: 乔克斯    时间: 2017-2-22 11:18
niconiconi~干巴爹
作者: xifengcanyue    时间: 2017-2-22 14:08
乔克斯 发表于 2017-2-22 11:18
niconiconi~干巴爹

然而能力有限,那个bug我还没能力修复,连原因都不知道在哪。伤心。。。
作者: 乔克斯    时间: 2017-2-24 15:45
@小红帽 你需要小红的帮助。
作者: 小红帽    时间: 2017-2-25 10:27
文字绘制设置抗锯齿,就不会有黑边了。那些图像也可以直接绘制到窗体上的,不需要绘制到位图了,有LayeredPaint事件,和普通窗体控件重绘一样。需要重绘的时候调用Invalidate。运行时间久了卡主,可能是哪里的事件重复注册了,没有取消绑定。或者有其他资源没有释放。
作者: xifengcanyue    时间: 2017-2-26 12:11
找到原因了,是屏幕取色模块的问题,把它注释掉就没毛病了,但是有什么其他办法可以获取屏幕壁纸颜色来改变线条颜色吗???屏幕取色模块就是为了让线条颜色随壁纸的更换而变色的。。
作者: 乔克斯    时间: 2017-2-28 15:20
屏幕取色一次怎么会出问题?。是不是频繁调用了。
作者: xifengcanyue    时间: 2017-3-1 12:07
乔克斯 发表于 2017-2-28 15:20
屏幕取色一次怎么会出问题?。是不是频繁调用了。

是的,每秒获取一次屏幕颜色,我在想有没有其他办法获取屏幕颜色
作者: aaa15808    时间: 2017-3-8 11:04
我就是来看看的
作者: 758132951    时间: 2017-3-9 22:06
谢谢楼主,共同学习
作者: 大美    时间: 2017-3-19 08:43
谢谢楼主分享
作者: IvanSun    时间: 2017-4-21 11:11
感谢分享,LZ辛苦了~
作者: ykn123    时间: 2017-12-8 18:20
感谢楼主分享111
作者: zouxinghai    时间: 2017-12-22 16:53
谢谢楼主分享,向楼主学习
作者: feiver    时间: 2018-1-16 13:38
下载看看
作者: ghosthh2    时间: 2018-12-18 15:36
这个牛逼了啊
作者: alwaysfirst    时间: 2019-1-10 19:33
好东西,看看,学习了
作者: UsingSystem    时间: 2019-1-11 21:51
代码太乱了,翻半天没看到你的draw函数在哪,无用的注释一堆,眼睛都翻绿了
作者: xiashen    时间: 2019-3-28 16:56
谢谢楼猪
作者: Kimino    时间: 2020-2-26 16:22
xifengcanyue 发表于 2017-3-1 12:07
是的,每秒获取一次屏幕颜色,我在想有没有其他办法获取屏幕颜色 ...

虽然不知道你解决没,取色器丢到初始化函数后面试试?
作者: ppuser    时间: 2020-5-27 18:16
挺好,就是怎么退出?怎么换音乐?还没有看代码只是运行了一下。




欢迎光临 CSkin博客 (http://bbs.cskin.net/) Powered by Discuz! X3.2