WPF中实现九宫格绘图
1、问题的起源。
首先我们看一下QQ2009的面板,普通状态下是这样的:
当我们最大化或是拉伸界面后,界面会变成这个样子:
我们可以看到,经过拉伸后的界面背景并没有被等比例拉伸,通过分析我们发现,其实界面是用了一张背景图片,原始图如下:
通过对比我们可以看到,界面在帖图时采取了四角固定,中间部分拉伸的方法来保证背景在拉伸时不变形。按照一般的绘制方法,我们首先会将
背景图片使用工具来切成9张图片,示意图如下:
如果上图中红线所示,我们通过一个“井”字把整张图片分成了9块,从上到下,从左到右我们将他编号为1-9号。其中第一、三、七、九块为固定大小。
二、八、四、六为水平或是上下拉伸,第五块为双向拉伸。
按照我们传统的帧图方法,我们会采用这样的方式来操作:
第一步:帖一号块,即左上块。
第二步:根据当前窗口宽度帖第二块和第八块,平铺或是拉伸都可以。
第三步:帖第三号块,即上右部分。
第四步:帖第四、六块,即二边。
第五步:帖七号块,即左下部分。
第六步:帖九号块,即右下部分。
第七步:中间部分拉伸。
通过以上七步,我们基本上达到了上面的效果。
但是如果美工修改了背景图片,导致切片大小变化,那我们只能重新修改上述七步中的代码来重新计算每一块的位置。重新帖图。
2、九宫帖图法。
通过上述七步我们可以发现,对于采用整张背景图片做界面背景时,我们的“井”字其实只是确定了图片拉伸区域与固定区域的位置而已。通过进一步观察我们
可以发现,其实只需要我们确定完中间的四边型(第五块)后,整个图片的分割就基本已经确定。于是有人使用了下面的帖图方法:
第一步:设置背景图片的裁剪中心区域,以一个Rect或是Padding的方式来描述。分别对应固定部分对应于背景图片的距离,如本例中为65,,12,65,65。
第二步:根据设置的有参数对整张图片进行局部帖图,帧图步骤类似传统方法。
3、解决办法。
系统自带的图片对象是不具备九宫格绘图功能的,我们可以通过继承系统的Image对象,增加九宫格绘图功能。
首先,我们使用BitmapSource.CopyPixels方法,将要用于绘制的图片进行剪裁,代码如下:
1 public static ImageSource SplitImage(BitmapSource source, Int32Rect clipRect) 2 { 3 ImageSource results = null; 4 var stride = clipRect.Width * ((source.Format.BitsPerPixel + 7) / 8); 5 var pixelsCount = clipRect.Width * clipRect.Height;//tileWidth * tileHeight; 6 var tileRect = new Int32Rect(0, 0,clipRect.Width, clipRect.Height); 7 8 var pixels = new int[pixelsCount]; 9 //var copyRect = new Int32Rect(col * tileWidth, row * tileHeight, tileWidth, tileHeight); 10 source.CopyPixels(clipRect, pixels, stride, 0); 11 var wb = new WriteableBitmap( 12 clipRect.Width, 13 clipRect.Height, 14 source.DpiX, 15 source.DpiY, 16 source.Format, 17 source.Palette); 18 wb.Lock(); 19 wb.WritePixels(tileRect, pixels, stride, 0); 20 wb.Unlock(); 21 22 results = wb; 23 return results; 24 }
第二步,根据定义的“井”字中间部分RECT,将源图片分成九块,代码如下:
public static ImageSource[] Get9CellImageSource(BitmapSource source, Int32Rect clipRect) { ImageSource[] results = new ImageSource[9]; Int32Rect rect = Int32Rect.Empty; int rightSideWidth = (int)(source.PixelWidth - clipRect.X - clipRect.Width); //top-left rect.Width = clipRect.X; rect.Height = clipRect.Y; results[0] = SplitImage(source, rect); //return results; //top-middle rect.X += rect.Width; rect.Width = clipRect.Width; results[1] = SplitImage(source, rect); //top-right rect.X += rect.Width; rect.Width = rightSideWidth; results[2] = SplitImage(source, rect); //left side rect = Int32Rect.Empty; rect.Y = clipRect.Y; rect.Width = clipRect.X; rect.Height = clipRect.Height; results[3] = SplitImage(source, rect); //middle rect.X += rect.Width; rect.Width = clipRect.Width; results[4] = SplitImage(source, rect); //right side rect.X += rect.Width; rect.Width = rightSideWidth; results[5] = SplitImage(source, rect); //bottom-left rect = Int32Rect.Empty; rect.Y = clipRect.Y + clipRect.Height; // rect.X = clipRect.X; rect.Height = source.PixelHeight - clipRect.Height - clipRect.Y; rect.Width = clipRect.X; results[6] = SplitImage(source, rect); //bottom-middle rect.X += rect.Width; rect.Width = clipRect.Width; results[7] = SplitImage(source, rect); //bottom-right rect.X += rect.Width; rect.Width = rightSideWidth; results[8] = SplitImage(source, rect); return results; }
第三步:重写Image对象的OnRender方法,使之具备九宫绘图功能,代码如下:
1 protected override void OnRender(System.Windows.Media.DrawingContext dc) 2 { 3 4 if (ClipRect != Int32Rect.Empty) 5 { 6 //剪裁绘图 7 DrawBitblt(dc); 8 return; 9 } 10 if (DrawImageWith9Cells == false) 11 { 12 13 base.OnRender(dc); 14 return; 15 } 16 RenderWith9Cells(dc); 17 } 18 19 private void DrawBitblt(DrawingContext dc) 20 { 21 22 //增加剪裁后九宫 23 if (DrawImageWith9Cells == false) 24 { 25 ImageSource source = GetImageSource(); 26 Rect rect = new Rect(new Point(0, 0), new Size(ActualWidth, ActualHeight)); 27 dc.DrawImage(source, rect); 28 } 29 else 30 { 31 RenderWith9Cells(dc); 32 } 33 } 34 35 /// <summary> 36 /// 9格绘图 37 /// </summary> 38 /// <param name="dc"></param> 39 private void RenderWith9Cells(System.Windows.Media.DrawingContext dc) 40 { 41 if (Source == null) return; 42 if (ClipPadding.Right == 0 || ClipPadding.Bottom == 0) return; 43 44 ImageSource source = Source; 45 Uri u =new Uri(source.ToString()); 46 BitmapSource image = new BitmapImage(u); 47 if (ClipRect != Int32Rect.Empty) 48 { 49 //预剪裁 50 image = ImageClip.SplitImage(image, ClipRect) as BitmapSource; 51 } 52 double contentWidth = image.PixelWidth - ClipPadding.Left - ClipPadding.Right; 53 double contentHeight = image.PixelHeight - ClipPadding.Top - ClipPadding.Bottom; 54 Int32Rect contentRect = new Int32Rect((int)ClipPadding.Left, (int)ClipPadding.Top 55 , (int)contentWidth, (int)contentHeight); 56 57 // Rect r = new Rect(new Point(),RenderSize); 58 // dc.DrawImage(image,r); 59 //return; 60 // image.BeginInit(); 61 // image.EndInit(); 62 ImageSource[] images = ImageClip.Get9CellImageSource(image, contentRect); 63 if (images == null || images.Length != 9) 64 { 65 base.OnRender(dc); 66 return; 67 } 68 DrawFrame(dc, images, contentRect); 69 // DrawContent(contentRect, dc); 70 } 71 72 73 74 /// <summary> 75 /// 绘制边框 76 /// </summary> 77 /// <param name="dc"></param> 78 private void DrawFrame(System.Windows.Media.DrawingContext drawingContext, ImageSource[] images, Int32Rect contentRect) 79 { 80 Rect drawRect = new Rect(new Point(), new Size(contentRect.X, contentRect.Y)); 81 double drawWidth = ActualWidth - ClipPadding.Left - ClipPadding.Right; 82 double drawHeight = ActualHeight - ClipPadding.Top - ClipPadding.Bottom; 83 drawingContext.DrawImage(images[0], drawRect); 84 85 drawRect.X += drawRect.Width; 86 drawRect.Width = drawWidth; 87 drawingContext.DrawImage(images[1], drawRect); 88 //for (int i = (int)drawRect.X; i < ActualWidth - contentRect.Width - contentRect.X; i += contentRect.Width) 89 //{ 90 // drawRect.X += drawRect.Width; 91 // drawRect.Width = contentRect.Width; 92 // drawingContext.DrawImage(images[1], drawRect); 93 //} 94 drawRect.X += drawRect.Width; 95 drawRect.Width = ClipPadding.Right; 96 drawingContext.DrawImage(images[2], drawRect); 97 //中间 98 99 drawRect.X = 0; 100 drawRect.Y = contentRect.Y; 101 drawRect.Width = contentRect.X; 102 drawRect.Height = drawHeight; 103 drawingContext.DrawImage(images[3], drawRect); 104 105 drawRect.X += drawRect.Width; 106 drawRect.Width = drawWidth; 107 drawingContext.DrawImage(images[4], drawRect); 108 109 drawRect.X += drawRect.Width; 110 drawRect.Width = ClipPadding.Right; 111 drawingContext.DrawImage(images[5], drawRect); 112 113 //下边 114 drawRect.X = 0; 115 drawRect.Y = ActualHeight - ClipPadding.Bottom; 116 drawRect.Width = ClipPadding.Left; 117 drawRect.Height = ClipPadding.Bottom; 118 drawingContext.DrawImage(images[6], drawRect); 119 120 drawRect.X += drawRect.Width; 121 drawRect.Width = drawWidth; 122 drawingContext.DrawImage(images[7], drawRect); 123 124 drawRect.X += drawRect.Width; 125 drawRect.Width = ClipPadding.Right; 126 drawingContext.DrawImage(images[8], drawRect); 127 128 129 } 130 131 private void DrawClip(Rect clipRect, System.Windows.Media.DrawingContext drawingContext, Rect targetRect) 132 { 133 Rect rect = targetRect;// new Rect(new Point(0, 0), targetRect); 134 RectangleGeometry rectangleGeometry = new RectangleGeometry(clipRect); 135 rectangleGeometry.Freeze(); 136 drawingContext.PushClip(rectangleGeometry); 137 drawingContext.DrawImage(Source, rect); 138 drawingContext.Pop(); 139 }
这样,我们的Image扩展控制就具备了如下功能:
1、原系统中Image控制的功能,即绘制整张图片。
2、绘制大图中指定区域部分。
3、九宫格绘图。
在XAML中使用:
首先增加本地解决方案命名空间,此处名为l。
<l:ImageEx Name="equipmentback2" DrawImageWith9Cells="True" Source="Images\QQBack.png" ClipPadding="8,58,8,16" Stretch="Fill"></l:ImageEx>
注:
搞了半天不知道怎么发附件,需要完整源代码的朋友可以EMAIL给我。