作者: xifengcanyue
查看: 22834|回复: 20
打印 上一主题 下一主题

[源码] 【C#音乐播放器】用GDI+绘制环形频谱

[复制链接]
跳转到指定楼层
楼主
查看: 22834|回复: 20
说明:
自制用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


编后语:
  • 绘制部分就搞定了,但是有个很严重的bug,就是随着程序的运行,系统会越来越卡,直到一定时间后程序自动崩溃或者手动退出,系统才会恢复原样
  • 播放列表还没绘制,接触编程的时间还是太短了,很多东西不了解,没能力优化,把它分享出来其实主要是想请大牛们帮忙看看,是不是哪里出问题了。帮忙优化一下。
  • 还有就是希望大家帮忙提点意见,可以在什么地方改进一下。小弟不胜感激

源代码和demo下载:

字体下载:



评分

参与人数 1金钱 +3 收起 理由
乔克斯 + 3 感谢分享,LZ辛苦了~

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏6 转播转播
回复 论坛版权

使用道具 举报

沙发
乔克斯 发表于 2017-2-22 11:18:40 | 只看该作者
niconiconi~干巴爹
板凳
 楼主| xifengcanyue 发表于 2017-2-22 14:08:24 | 只看该作者

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

是的,每秒获取一次屏幕颜色,我在想有没有其他办法获取屏幕颜色
9#
aaa15808 发表于 2017-3-8 11:04:30 | 只看该作者
我就是来看看的
10#
758132951 发表于 2017-3-9 22:06:10 | 只看该作者
谢谢楼主,共同学习
您需要登录后才可以回帖 登录 | 加入CSkin博客

本版积分规则

QQ|申请友链|小黑屋|手机版|Archiver|CSkin ( 粤ICP备13070794号

Powered by Discuz! X3.2  © 2001-2013 Comsenz Inc.  Designed by ARTERY.cn
GMT+8, 2024-11-16 07:00, Processed in 0.594205 second(s), 33 queries , Gzip On.

快速回复 返回顶部 返回列表