Windows8.1畫熱度圖 - 坑

想要的效果ios

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

 

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

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版本中實現相似功能。windows

一、讀取調色板文件網絡

            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

二、把UIElement轉換爲圖形app

Windows 8.1以前,沒有RenderTargetBitmap類。最開始我採用了codeplex上的WriteableBitmapExtensions類,後來發現8.1中已經增長了這個類。dom

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

第二行就會把UIElement及全部子element寫入到bitmap中。ide

關於RenderTargetBitmap有無數坑,msdn以下:ui

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

  • 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>

 三、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模式畫的圖以下:

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

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

五、悲催的地方
經過上面第二部分讀到的像素值,是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部分中,對像素顏色進行了對調。

可是,獲得的是這麼一個悲催的圖像:

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

 

求指點,請幫助!

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

六、仔細檢查了一下,第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緣故?

相關文章
相關標籤/搜索