Heatmap style gradients in .NET

2019-01-15 13:43发布

问题:

I am trying to create a heat map with gradients that look similar to this:

This image shows three points and the gradients blend nicely together.

Here is what I am currently doing in my drawing function:

public void DrawGradient(int x, int y, Graphics g) {
    using (var ellipsePath = new GraphicsPath()) {

    var bounds = new Rectangle(x, y, 100, 100);
    ellipsePath.AddEllipse(bounds);
    var brush = new PathGradientBrush(ellipsePath);
    Color[] colors = { 
           Color.FromArgb(64, 0, 0, 255), 
           Color.FromArgb(140, 0, 255, 0), 
           Color.FromArgb(216, 255, 255, 0), 
           Color.FromArgb(255, 255, 0, 0) 
       };
    float[] relativePositions = {0f,0.25f,0.5f, 1.0f};
    ColorBlend colorBlend = new ColorBlend();
    colorBlend.Colors = colors;
    colorBlend.Positions = relativePositions;
    brush.InterpolationColors = colorBlend;

    g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
    g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
    g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
    g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
    g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;

    g.FillEllipse(brush, bounds);
  }
}

Which creates an image like this for two overlapping points:

It does not blend. How can I create a gradient blending effect in a .NET drawing?

回答1:

Yes this is possible with < 100 lines of .NET and GDI+ drawing code..:

You need to change these things in your code:

  • You need to fade the point areas to transparent, so you can add them
  • You need to use a grayscale gradient so you don't create wrong, maybe muddy colors
  • As a consequence you need to remap the colors.

Here are the details:

You need two gradients:

  • The grayscale one goes from Black (255,0,0,0) to transparent Black (0,0,0,0). You can influence the smoothness by making the transparent end fade out faster or slower.

  • The color gradient enumerates your heatmap colors.

Next you need one or more bitmaps with the gray circles. If the point areas are all the same, one will do..

Now you can draw the bitmaps onto the target bitmap. Make sure to set the Graphics.CompositingMode = CompositingMode.SourceOver; This results in something like the left screenshot.

Finally you need to Remap the colors:

For this you need to set up the ColorMaps:

  • create two color lists, one going over the grayscale colors in 256 steps, one with the same number of steps going over your heatmap colors. Note that the lists must have the same number of elements; the gradients however can each have as many or few in the ColorBlend.Colors as you like.

The color lists can be created by filling a 256x1 pixel Bitmap with a LinearGradientBrush that uses the same two respective ColorBlends and then using GetPixel to pull out the colors..

After doing a SetRemapTable we can finally draw the heatmap, and voila, the right screenshot is rather similar, even without any tweaking to get real close to your heatmap.

Your code

Here are a few remarks on the flaws in your code:

  • You need to fade-in from fully transparent. Your colors start with an alpha of 64, which is far from 'almost transparent'. Our eyes are very sensitive on this end of the scale!

Here is the grayscale gradient I used, with a photoshop-style checkerboard background, so you can see the transparency:

  • Another problem with your colors is that they start with blue; they really need to start with white!

  • The most fundamental problem is the blending of colors. For a 'heat map', (or a 'profile map' or a 'displacement map') we need to add up the values, not mix them. For this the color channels are useless. Colors are circular and they do not naturally lend themselves to a list of ascending values. If we use the alpha channel only, we can in a simple way limit the map to a 'brightness map', which we can then color in any way we like..

Note that I have assumed that the points are to be maxima. If they are not, you need to scale them down to a lower alpha..

Also note, that for really important applications, like medical or scientific imaging, you should go all the way to calculate the values, so you have full control of the mapping!