文章出處

想要的效果

如上是silverlight版本。原理是設定一個調色板,為256的漸變色(存在一個png文件中,寬度為256,高度為1),然后針對要處理的距離矩陣圖形,取圖片中每個像素的Alpha值作為索引,對應到調色板的顏色。每個像素處理之后,則形成上面的熱度圖。該圖主要表達了一個數據分布的密度。

 

網絡上有一個Gildor.HeatmapDemos工程,我主要參考了SL版本。要注意elipseRadius,如果過小,即每個圓彼此不相交,則看不到熱度效果,所以代碼設置初始值為100。(上圖的數值初始化部分,對應代碼如下)

private List<Point> plist = new List<Point>();
        private void drawHeatMap ()
        {
            plist.Clear();
            plist.Add(new Point(0.15*map.ActualWidth, 0.49*map.ActualHeight));
            plist.Add(new Point(0.20 * map.ActualWidth, 0.25 * map.ActualHeight));
            plist.Add(new Point(0.10 * map.ActualWidth, 0.15 * map.ActualHeight));
            plist.Add(new Point(0.12 * map.ActualWidth, 0.05 * map.ActualHeight));
            plist.Add(new Point(0.17 * map.ActualWidth, 0.34 * map.ActualHeight));
            plist.Add(new Point(0.17 * map.ActualWidth, 0.33 * map.ActualHeight));
            plist.Add(new Point(0.16 * map.ActualWidth, 0.33 * map.ActualHeight));
            
            heatMap.Source = _heatMapGen.GenerateHeatMap (
                plist,
                new Size (map.ActualWidth, map.ActualHeight));
        }
View Code

 

我需要在windows 8.1的RT版本中實現類似功能。

1、讀取調色板文件

            Uri uri = new Uri("ms-appx:///assets/bookpage/Palette.bmp");
            RandomAccessStreamReference streamRef = RandomAccessStreamReference.CreateFromUri(uri);
            
            
            using (IRandomAccessStreamWithContentType fileStream = await streamRef.OpenReadAsync())
            {
                BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
                BitmapFrame frame = await decoder.GetFrameAsync(0);

                PixelDataProvider pixelProvider = await frame.GetPixelDataAsync();
                this.palette = pixelProvider.DetachPixelData();
            }
View Code

2、把UIElement轉換為圖形

Windows 8.1之前,沒有RenderTargetBitmap類。最開始我采用了codeplex上的WriteableBitmapExtensions類,后來發現8.1中已經增加了這個類。

            RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
            await renderTargetBitmap.RenderAsync(canvasSpitList);//, (int)pdfBorder.Width, (int)pdfBorder.Height);            
View Code

第二行就會把UIElement及所有子element寫入到bitmap中。

關于RenderTargetBitmap有無數坑,msdn如下:

There are a few scenarios for XAML-composed visual content that you can't capture to a RenderTargetBitmap:

  • Video content in a MediaElement or CaptureElement can't be captured using RenderTargetBitmap. That includes capturing      frames from within video content.
  • Custom DirectX content (your      own swap chain) inside a SwapChainBackgroundPanel or SwapChainPanel can't be captured using RenderTargetBitmap.
  • Content that's in the tree but      with its Visibility set to Collapsed won't be captured.
  • Content that's not directly      connected to the XAML visual tree and the content of the main window won't      be captured. This includes Popup content, which is considered      to be like a sub-window.

Applies to Windows Phone

Note  Windows Phone: The contents of a WebView control can't be rendered into a RenderTargetBitmap.

  • Content that can't be captured      will appear as blank in the captured image, but other content in the same      visual tree can still be captured and will render (the presence of content      that can't be captured won't invalidate the entire capture of that XAML composition).
  • Content that's in the XAML      visual tree but offscreen can be captured, so long as it's not Visibility=Collapsed or in the other restricted      cases.

 

From <https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.imaging.rendertargetbitmap.aspx>

 3、RadialGradientBrush在Windows RT 8.1中沒有!只有LinearGradientBrush。MSDN說法這里

GradientBrush is the parent class for LinearGradientBrush. The Windows Runtime XAML vocabulary doesn't support RadialGradientBrush.

 

From <https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.gradientbrush.aspx>

我在SL中用Linear模式畫的圖如下:

4、讀取要處理圖形的每個像素

int width = renderTargetBitmap.PixelWidth;
int height = renderTargetBitmap.PixelHeight;

var buf = await renderTargetBitmap.GetPixelsAsync();


var stream = buf.AsStream();
byte[] srcPixels = new byte[stream.Length];
stream.Read(srcPixels, 0, (int)stream.Length);
View Code

舍棄R/G/B值,只保留A,然后讀取對應的調色板顏色,進行替換。 

var dstPixels = new byte[4 * width * height];

            for (int i = 0; i < srcPixels.Length; i += 4)
            {
                //int pixelIndex = ((srcPixels[i + 3] << 24) + (srcPixels[i + 2] << 16) + (srcPixels[i + 1] << 8) + (srcPixels[i + 0]));
                byte pixelIndex = srcPixels[i + 3];//只取Alpha通道的值

                if ((srcPixels[i + 0] == 0) && (srcPixels[i + 1] == 0) && (srcPixels[i + 2] == 0) && (srcPixels[i + 3] == 0)) continue;

                //winform中,pixelProvider.DetachPixelData,顏色順序從低到高字節為:A,R,G,B,包括開始的palette取到的也是A,R,G,B
                //metro中,renderTargetBitmap.GetPixelsAsync,顏色順序從低到高字節為:B,G,R,A
                dstPixels[i + 0] = this.palette[(byte)(~(4 * pixelIndex + 3))];//B<->A
                dstPixels[i + 1] = this.palette[(byte)(~(4 * pixelIndex + 2))];//G<->R
                dstPixels[i + 2] = this.palette[(byte)(~(4 * pixelIndex + 1))];//R<->G
                dstPixels[i + 3] = this.palette[(byte)(~(4 * pixelIndex + 0))];//A<->B
            }

            var bmp = new WriteableBitmap(width, height);//(container, null);
            WriteableBitmapExtensions.FromByteArray(bmp, dstPixels);
View Code

5、悲催的地方
通過上面第二部分讀到的像素值,是A/R/G/B順序,用winform讀取,也是一樣的結果

private void button1_Click(object sender, EventArgs e)
        {
            StringBuilder sb = new StringBuilder();


            using (Bitmap bmp = new Bitmap(@"c:\1.png"))
            {
                using (Graphics g = this.CreateGraphics())
                {
                    for (int i = 0; i < 256; i++)
                    {
                        var col = bmp.GetPixel(i, 0);
                        sb.AppendFormat("{0:X8},", col.ToArgb());

                        using (SolidBrush brush = new System.Drawing.SolidBrush(col))
                        {
                            g.FillRectangle(brush, new Rectangle(5 * i, 10, 3, 3));
                        }
                    }
                }
            }


            MessageBox.Show(sb.ToString());
        }
View Code

得到的像素順序:

 

比如第一個亮黃顏色,在這里與palette部分讀到都是A/R/G/B順序,但是在上面第4部分,讀到的確是B/G/R/A部分。所以第4部分中,對像素顏色進行了對調。

但是,得到的是這么一個悲催的圖像:

顏色不對!熱度效果也沒有!

 

求指點,請幫助!

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

6、仔細檢查了一下,第2部分的palette取到的是R/G/B/A順序,所以第4部分的調色板代碼修改如下:

                //winform中,顏色順序從低到高字節為:A,R,G,B
                //palette中,pixelProvider.DetachPixelData取到的卻是R,G,B,A
                //metro中,renderTargetBitmap.GetPixelsAsync,顏色順序從低到高字節為:B,G,R,A
                dstPixels[i + 0] = this.palette[(byte)((4 * pixelIndex + 2))];//B<->A
                dstPixels[i + 1] = this.palette[(byte)((4 * pixelIndex + 1))];//G<->R
                dstPixels[i + 2] = this.palette[(byte)((4 * pixelIndex + 0))];//R<->G
                dstPixels[i + 3] = this.palette[(byte)((4 * pixelIndex + 3))];//A<->B
View Code

 

但是熱度效果依然不對,難道是因為LinearGradientBrush緣故?

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()