C#热图生成(一)——with .NET 2.0

时间:2024-05-21 20:23:05

介绍

热图(Heat Map)现在已经成为一种算不上一种时髦的应用,基本上涉及到地理信息的应用都会包含热图。热图可以以一种非常直观的形式来呈现密度信息,带来非常棒的用户体验。下面是idvsolutions出售的地图套件的两个热图实例:

C#热图生成(一)——with .NET 2.0

http://vfdemo.idvsolutions.com/collisions/

C#热图生成(一)——with .NET 2.0

http://vfdemo.idvsolutions.com/piracy/

虽然说,热图本质上不过是三维数据的二维呈现,但显然上面两个显示效果要比下图那样的好的多。

 

C#热图生成(一)——with .NET 2.0

生成

如何生成前面那种热图呢?可以看出,存在一个预设的调色板(如下图),而热图是由许多个椭圆(圆)叠加而成,叠得越多,颜色就朝调色板的一端靠近。

C#热图生成(一)——with .NET 2.0 C#热图生成(一)——with .NET 2.0

 

注意观察一些单点,我们发现即使是单个椭圆也是有渐变的,颜色大约是从调色板的中部开始,这样在椭圆叠加后就产生了渐变的边缘。然而,我们并不能用这些颜色直接画椭圆,因为这些颜色叠加后,并不会向调色板上设计好的一端渐变,从而得不到我们想要的效果。

Dylan Vester在他的文章中提出了一种可行的方法。由于灰色在叠加时,颜色会逐渐变浓最后变成黑色,于是可以先绘制灰色椭圆(圆),然后将整个画布看做位图,将图上256级灰度映射到一个256色的调色板上的颜色,以生成热图,效果如下:

C#热图生成(一)——with .NET 2.0

不过,仍有一些缺憾,比如生成的热图没有渐变的透明度,因而无法直接覆盖在地图一类的背景上。在默认的实现中,浓度为0的区域用灰色覆盖,即使将此区域的着色在色彩化后手动修改为透明,也存在边缘太硬的问题。同样,原灰度图的绘制也有透明度不可调的问题。

因此,我修改了原灰度图绘制方式,重写了色彩化的方法。做出了以下几点改进:

  • 改原来的灰度叠加模式为透明度叠加模式,用Alpha通道值来映射调色板;
  • 支持32位ARGB调色板,也就是说,生成的热图可以包含渐变的透明度;
  • 绘制椭圆时直接绘制Ellipse,而不是绘制一个360边形,可以更好地支持各种尺寸的热点(不知为何原作者说.NET 2.0不支持渐变的radial gradient就采用了变通方式,其实可以用PathGradientBrush来实现);
  • 直接支持从图片加载调色板,并可调节热点绘制的参数,可手动指定热点(其实就是做成了一个调色板效果预览工具,用于设计Heat Map调色板)

当前版本最终效果:

C#热图生成(一)——with .NET 2.0

换了一个调色板的效果:

C#热图生成(一)——with .NET 2.0

实现细节

(如果您对此节没有兴趣,可以跳到最后去下载源码和可执行程序。)

灰度图的笔刷混色

private ColorBlend getColorBlend ()
{
    ColorBlend colors = new ColorBlend (3);

    // Set brush stops.
    colors.Positions = new float[3] { 0, _brushStop, 1 };

    // The intensity value adjusts alpha of gradient colors.
    colors.Colors = new Color[3]
    {
        Color.FromArgb(0, Color.White),
        // The following colors can be any color - Only the alpha  value is used.
        Color.FromArgb(_intensity, Color.Black),
        Color.FromArgb(_intensity, Color.Black)
    };
    return colors;
}

 

其中,_brushStop和_intensity分别是界面上由用户指定的笔刷变化点和单点中心浓度。这就相当于是WPF/SL中的GradientStops,只不过是分别指定位置和颜色。

绘制热点的灰度图

首先创建原图大小的空白位图,必须为ARGB格式:

// Create new memory bitmap the same size as the picture box.
// Set its format to 32bit argb to support transparency.
Bitmap bmp = new Bitmap (pictureBox1.Width, pictureBox1.Height,
    PixelFormat.Format32bppArgb);

在位图上创建一个Graphics Surface:

// Create new graphics surface from the bitmap.
Graphics surface = Graphics.FromImage (bmp);

对每个指定的热点位置heatPoint,首先画出椭圆路径:

// Create the ellipse path.
var ellipsePath = new GraphicsPath ();
ellipsePath.AddEllipse (heatPoint.X - radius, heatPoint.Y - radius,
    radius * 2, radius * 2);

然后构造一个PathGradientBrush来给它着色:

// Create the brush.
PathGradientBrush brush = new PathGradientBrush (ellipsePath);
ColorBlend gradientSpecifications = colors;
brush.InterpolationColors = gradientSpecifications;

// Use the brush to fill the ellipse.
surface.FillEllipse (brush, heatPoint.X - radius,
    heatPoint.Y - radius, radius * 2, radius * 2);

如此便得到一幅热点的灰度图,而在此处,地图是作为PictureBox的背景载入的。

加载调色板

直接使用Bitmap类读取文件,然后依次取出每个像素的ARGB值即可:

int[] palette = new int[256];
Bitmap paletteImage = (Bitmap)Bitmap.FromFile (txtPaletteFileName.Text);
for (int i = 0; i < palette.Length - 1; i++)
{
    palette[i] = paletteImage.GetPixel (i, 0).ToArgb ();
}

注意最后一个颜色必须设为透明以保证没有热点的区域保持原状(当然有需要的话你也可以调整):

// Set the last color to 0x00000000 to make sure areas 
// with no heat point remain original.
palette[palette.Length - 1] = 0;

色彩化 (Colorize)

载入调色板后,首先创建一个相同大小的ARGB输出位图:

// Create an empty bitmap for output.
Bitmap output = new Bitmap (originalMask.Width, originalMask.Height,
    PixelFormat.Format32bppArgb);

遍历灰度图的每个像素,使用其Alpha通道值(高8位)取反作为索引,从调色板中取出相应颜色来着色输出位图的对应点:

for (int y = 0; y < originalMask.Height; y++)
{
    for (int x = 0; x < originalMask.Width; x++)
    {
        // Calucate the pixel of output image according to the original pixel and palette.
        output.SetPixel (x, y, Color.FromArgb (
            palette[(byte)~(((uint)(originalMask.GetPixel (x, y).ToArgb ())) >> 24)]));
    }
}

最后将图像输出到PictureBox中刷新即可。

------------

本文介绍了如何在.NET 2.0桌面环境下生成热图,其性能尚可,在特定情况下还可以进行一定优化(比如直接跳过透明点的取色与着色),因此同样也可以用于服务器端预先生成热图,然后以压缩图片格式在网页上显示的场景。

对于Silverlight应用,同样可以采用上述方案。然而,用户往往更希望看到对其操作的实时反馈,而不是慢慢地等待图片的载入,而且服务端预生成图片,也不利于一些自定义视图的呈现(很遗憾,即使是开头IDV的那两个例子,也是采用的这种方式)。其实Silverlight完全有能力自行在客户端生成热图,我将在下一篇blog中介绍。

(本文编写的WinForm程序其实是在项目开发过程中为了配合下文Silverlight产品开发而衍生的工具。)

http://www.cnblogs.com/Gildor/archive/2010/05/13/1734649.html

相关文章