在Silverlight中我们有时候需要手工绘制线条或者直线等,在这里我们认识一下InkPresenter控件,它将支持用户使用鼠标、手写板等工具来绘制图形或者笔迹,用途为涂鸦、笔迹确认等等。

        InkPresenter是继承于Canvas控件的支持所有的Canvas属性,并且其内部还可以嵌套显示其他控件。InkPresenter控件的显 示分为三层:底层是InkPresenter的Background、中间层是InkPresenter的Children属性的控件、最后才是 Strokes属性中的笔画层。

        对于Strokes属性中的笔画Stroke我们可以设置它的颜色、粗细、外边框颜色等等属性以获得满意的笔画类型。下面我们来看看如何使用InkPresenter控件,首先我们来看Xaml代码如下:

 

  1. <Grid x:Name="LayoutRoot1" Background="White"
  2.     <Canvas> 
  3.         <Border BorderThickness="1" Margin="50 10 0 0" BorderBrush="CadetBlue"  
  4.                 HorizontalAlignment="Center" VerticalAlignment="Center"
  5.             <InkPresenter x:Name="iPresenter" Height="500" Width="500" 
  6.               MouseLeftButtonDown="iPresenter_MouseLeftButtonDown"  
  7.               LostMouseCapture="iPresenter_LostMouseCapture"  
  8.               MouseMove="iPresenter_MouseMove"  
  9.               Background="Transparent" Opacity="1" > 
  10.                 <TextBox Width="138" Canvas.Left="58" Canvas.Top="105"></TextBox> 
  11.             </InkPresenter> 
  12.         </Border> 
  13.         <Button Canvas.Left="560" Canvas.Top="11" Content="将涂鸦保存为图片" Height="23" 
  14.                 Name="button1" Width="104" Click="button1_Click" /> 
  15.         <Image Name="showIP" Width="400" Height="400" Canvas.Left="560" Canvas.Top="60"></Image> 
  16.     </Canvas> 
  17. </Grid> 

        然后我们来看看Xaml.cs代码如下:

 

  1. public partial class MainPage : UserControl 
  2.     public MainPage() 
  3.     { 
  4.         InitializeComponent(); 
  5.         SetPresenterClip(); 
  6.     } 
  7.     Stroke myStroke; 
  8.  
  9.     private void iPresenter_MouseLeftButtonDown(object sender, MouseEventArgs e) 
  10.     { 
  11.         //让鼠标捕获数据 
  12.         iPresenter.CaptureMouse(); 
  13.         //收集笔触数据点保存值StylusPointCollection集合中 
  14.         StylusPointCollection stylusPointCollection = new StylusPointCollection(); 
  15.         stylusPointCollection.Add(e.StylusDevice.GetStylusPoints(iPresenter)); 
  16.         //将数据点的结合保存为一个笔画 
  17.         myStroke = new Stroke(stylusPointCollection); 
  18.         //设置笔画的绘画效果,如颜色,大小等。 
  19.         myStroke.DrawingAttributes.Color = Colors.Gray; 
  20.         myStroke.DrawingAttributes.Width = 1; 
  21.         myStroke.DrawingAttributes.Height = 1; 
  22.         iPresenter.Strokes.Add(myStroke); 
  23.     } 
  24.  
  25.     private void iPresenter_MouseMove(object sender, MouseEventArgs e) 
  26.     { 
  27.         //在鼠标移动的过程中将数据点加入到笔画中去。 
  28.         if (myStroke != null
  29.             myStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(iPresenter)); 
  30.     } 
  31.  
  32.     private void iPresenter_LostMouseCapture(object sender, MouseEventArgs e) 
  33.     { 
  34.         //将笔画清空 
  35.         myStroke = null
  36.         iPresenter.ReleaseMouseCapture();//释放鼠标坐标 
  37.     } 
  38.  
  39.     /// <summary> 
  40.     /// 设置绘画区域为InkPresenter的大小 
  41.     /// </summary> 
  42.     private void SetPresenterClip() 
  43.     { 
  44.         RectangleGeometry MyRectangleGeometry = new RectangleGeometry(); 
  45.         MyRectangleGeometry.Rect = new Rect(0, 0, iPresenter.ActualWidth, iPresenter.ActualHeight); 
  46.         //设置获取绘画内容的有效区域 
  47.         iPresenter.Clip = MyRectangleGeometry; 
  48.     } 
  49.  
  50.     private void button1_Click(object sender, RoutedEventArgs e) 
  51.     { 
  52.         //保存InkPresenter涂鸦板内绘画的图 
  53.         WriteableBitmap _bitmap = new WriteableBitmap(iPresenter, null); 
  54.         this.showIP.Source = _bitmap; 
  55.         SaveFileDialog sfd = new SaveFileDialog(); 
  56.         sfd.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
  57.         sfd.DefaultExt = ".png"
  58.         sfd.FilterIndex = 1; 
  59.  
  60.         if ((bool)sfd.ShowDialog()) 
  61.         { 
  62.             using (Stream fs = sfd.OpenFile()) 
  63.             { 
  64.                 int width = _bitmap.PixelWidth; 
  65.                 int height = _bitmap.PixelHeight; 
  66.  
  67.                 EditableImage ei = new EditableImage(width, height); 
  68.  
  69.                 for (int i = 0; i < height; i++) 
  70.                 { 
  71.                     for (int j = 0; j < width; j++) 
  72.                     { 
  73.                         int pixel = _bitmap.Pixels[(i * width) + j]; 
  74.                         ei.SetPixel(j, i, 
  75.                                     (byte)((pixel >> 16) & 0xFF), 
  76.                                     (byte)((pixel >> 8) & 0xFF), 
  77.                                     (byte)(pixel & 0xFF), 
  78.                                     (byte)((pixel >> 24) & 0xFF) 
  79.                         ); 
  80.                     } 
  81.                 } 
  82.                 //获取流 
  83.                 Stream png = ei.GetStream(); 
  84.                 int len = (int)png.Length; 
  85.                 byte[] bytes = new byte[len]; 
  86.                 png.Read(bytes, 0, len); 
  87.                 fs.Write(bytes, 0, len); 
  88.                 MessageBox.Show("图片保存成功!"); 
  89.             } 
  90.         } 
  91.  
  92.     } 

        对于将InkPresenter中绘画出来的图片保存为Png图片得处理,我们在这里借鉴了园子中永恒的记忆兄弟的将元素转为Png图片的方法,在这里贴出两个辅助转Png格式的类。

 

  1. /// <summary> 
  2. /// 编辑图片 
  3. /// </summary> 
  4. public class EditableImage 
  5.     private int _width = 0; 
  6.     private int _height = 0; 
  7.     private bool _init = false
  8.     private byte[] _buffer; 
  9.     private int _rowLength; 
  10.  
  11.     /// <summary> 
  12.     /// 当图片错误时引发 
  13.     /// </summary> 
  14.     public event EventHandler<EditableImageErrorEventArgs> ImageError; 
  15.  
  16.     /// <summary> 
  17.     /// 实例化 
  18.     /// </summary> 
  19.     /// <param name="width"></param> 
  20.     /// <param name="height"></param> 
  21.     public EditableImage(int width, int height) 
  22.     { 
  23.         this.Width = width; 
  24.         this.Height = height; 
  25.     } 
  26.  
  27.     public int Width 
  28.     { 
  29.         get 
  30.         { 
  31.             return _width; 
  32.         } 
  33.         set 
  34.         { 
  35.             if (_init) 
  36.             { 
  37.                 OnImageError("错误: 图片初始化后不可以改变宽度"); 
  38.             } 
  39.             else if ((value <= 0) || (value > 2047)) 
  40.             { 
  41.                 OnImageError("错误: 宽度必须在 0 到 2047"); 
  42.             } 
  43.             else 
  44.             { 
  45.                 _width = value; 
  46.             } 
  47.         } 
  48.     } 
  49.  
  50.     public int Height 
  51.     { 
  52.         get 
  53.         { 
  54.             return _height; 
  55.         } 
  56.         set 
  57.         { 
  58.             if (_init) 
  59.             { 
  60.                 OnImageError("错误: 图片初始化后不可以改变高度"); 
  61.             } 
  62.             else if ((value <= 0) || (value > 2047)) 
  63.             { 
  64.                 OnImageError("错误: 高度必须在 0 到 2047"); 
  65.             } 
  66.             else 
  67.             { 
  68.                 _height = value; 
  69.             } 
  70.         } 
  71.     } 
  72.  
  73.     public void SetPixel(int col, int row, Color color) 
  74.     { 
  75.         SetPixel(col, row, color.R, color.G, color.B, color.A); 
  76.     } 
  77.  
  78.     public void SetPixel(int col, int row, byte red, byte green, byte blue, byte alpha) 
  79.     { 
  80.         if (!_init) 
  81.         { 
  82.             _rowLength = _width * 4 + 1; 
  83.             _buffer = new byte[_rowLength * _height]; 
  84.  
  85.             // Initialize 
  86.             for (int idx = 0; idx < _height; idx++) 
  87.             { 
  88.                 _buffer[idx * _rowLength] = 0;      // Filter bit 
  89.             } 
  90.  
  91.             _init = true
  92.         } 
  93.  
  94.         if ((col > _width) || (col < 0)) 
  95.         { 
  96.             OnImageError("Error: Column must be greater than 0 and less than the Width"); 
  97.         } 
  98.         else if ((row > _height) || (row < 0)) 
  99.         { 
  100.             OnImageError("Error: Row must be greater than 0 and less than the Height"); 
  101.         } 
  102.  
  103.         // Set the pixel 
  104.         int start = _rowLength * row + col * 4 + 1; 
  105.         _buffer[start] = red; 
  106.         _buffer[start + 1] = green; 
  107.         _buffer[start + 2] = blue; 
  108.         _buffer[start + 3] = alpha; 
  109.     } 
  110.  
  111.     public Color GetPixel(int col, int row) 
  112.     { 
  113.         if ((col > _width) || (col < 0)) 
  114.         { 
  115.             OnImageError("Error: Column must be greater than 0 and less than the Width"); 
  116.         } 
  117.         else if ((row > _height) || (row < 0)) 
  118.         { 
  119.             OnImageError("Error: Row must be greater than 0 and less than the Height"); 
  120.         } 
  121.  
  122.         Color color = new Color(); 
  123.         int _base = _rowLength * row + col + 1; 
  124.  
  125.         color.R = _buffer[_base]; 
  126.         color.G = _buffer[_base + 1]; 
  127.         color.B = _buffer[_base + 2]; 
  128.         color.A = _buffer[_base + 3]; 
  129.  
  130.         return color; 
  131.     } 
  132.  
  133.     public Stream GetStream() 
  134.     { 
  135.         Stream stream; 
  136.  
  137.         if (!_init) 
  138.         { 
  139.             OnImageError("Error: Image has not been initialized"); 
  140.             stream = null
  141.         } 
  142.         else 
  143.         { 
  144.             stream = PngEncoder.Encode(_buffer, _width, _height); 
  145.         } 
  146.  
  147.         return stream; 
  148.     } 
  149.  
  150.     private void OnImageError(string msg) 
  151.     { 
  152.         if (null != ImageError) 
  153.         { 
  154.             EditableImageErrorEventArgs args = new EditableImageErrorEventArgs(); 
  155.             args.ErrorMessage = msg; 
  156.             ImageError(this, args); 
  157.         } 
  158.     } 
  159.  
  160.     public class EditableImageErrorEventArgs : EventArgs 
  161.     { 
  162.         private string _errorMessage = string.Empty; 
  163.  
  164.         public string ErrorMessage 
  165.         { 
  166.             get { return _errorMessage; } 
  167.             set { _errorMessage = value; } 
  168.         } 
  169.     } 
  170.  

        这是Png操作类:

 

  1. /// <summary> 
  2. /// PNG格式操作类 
  3. /// </summary> 
  4. public class PngEncoder 
  5.     private const int _ADLER32_BASE = 65521; 
  6.     private const int _MAXBLOCK = 0xFFFF; 
  7.     private static byte[] _HEADER = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; 
  8.     private static byte[] _IHDR = { (byte)'I', (byte)'H', (byte)'D', (byte)'R' }; 
  9.     private static byte[] _GAMA = { (byte)'g', (byte)'A', (byte)'M', (byte)'A' }; 
  10.     private static byte[] _IDAT = { (byte)'I', (byte)'D', (byte)'A', (byte)'T' }; 
  11.     private static byte[] _IEND = { (byte)'I', (byte)'E', (byte)'N', (byte)'D' }; 
  12.     private static byte[] _4BYTEDATA = { 0, 0, 0, 0 }; 
  13.     private static byte[] _ARGB = { 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 0, 0, 0 }; 
  14.  
  15.     /// <summary> 
  16.     /// 编码 
  17.     /// </summary> 
  18.     /// <param name="data"></param> 
  19.     /// <param name="width"></param> 
  20.     /// <param name="height"></param> 
  21.     /// <returns></returns
  22.     public static Stream Encode(byte[] data, int width, int height) 
  23.     { 
  24.         MemoryStream ms = new MemoryStream(); 
  25.         byte[] size
  26.  
  27.         // Write PNG header 
  28.         ms.Write(_HEADER, 0, _HEADER.Length); 
  29.  
  30.         // Write IHDR 
  31.         //  Width:              4 bytes 
  32.         //  Height:             4 bytes 
  33.         //  Bit depth:          1 byte 
  34.         //  Color type:         1 byte 
  35.         //  Compression method: 1 byte 
  36.         //  Filter method:      1 byte 
  37.         //  Interlace method:   1 byte 
  38.  
  39.         size = BitConverter.GetBytes(width); 
  40.         _ARGB[0] = size[3]; _ARGB[1] = size[2]; _ARGB[2] = size[1]; _ARGB[3] = size[0]; 
  41.  
  42.         size = BitConverter.GetBytes(height); 
  43.         _ARGB[4] = size[3]; _ARGB[5] = size[2]; _ARGB[6] = size[1]; _ARGB[7] = size[0]; 
  44.  
  45.         // Write IHDR chunk 
  46.         WriteChunk(ms, _IHDR, _ARGB); 
  47.  
  48.         // Set gamma = 1 
  49.         size = BitConverter.GetBytes(1 * 100000); 
  50.         _4BYTEDATA[0] = size[3]; _4BYTEDATA[1] = size[2]; _4BYTEDATA[2] = size[1]; _4BYTEDATA[3] = size[0]; 
  51.  
  52.         // Write gAMA chunk 
  53.         WriteChunk(ms, _GAMA, _4BYTEDATA); 
  54.  
  55.         // Write IDAT chunk 
  56.         uint widthLength = (uint)(width * 4) + 1; 
  57.         uint dcSize = widthLength * (uint)height; 
  58.  
  59.         // First part of ZLIB header is 78 1101 1010 (DA) 0000 00001 (01) 
  60.         // ZLIB info 
  61.         // 
  62.         // CMF Byte: 78 
  63.         //  CINFO = 7 (32K window size
  64.         //  CM = 8 = (deflate compression) 
  65.         // FLG Byte: DA 
  66.         //  FLEVEL = 3 (bits 6 and 7 - ignored but signifies max compression) 
  67.         //  FDICT = 0 (bit 5, 0 - no preset dictionary) 
  68.         //  FCHCK = 26 (bits 0-4 - ensure CMF*256+FLG / 31 has no remainder) 
  69.         // Compressed data 
  70.         //  FLAGS: 0 or 1 
  71.         //    00000 00 (no compression) X (X=1 for last block, 0=not the last block) 
  72.         //    LEN = length in bytes (equal to ((width*4)+1)*height 
  73.         //    NLEN = one's compliment of LEN 
  74.         //    Example: 1111 1011 1111 1111 (FB), 0000 0100 0000 0000 (40) 
  75.         //    Data for each line: 0 [RGBA] [RGBA] [RGBA] ... 
  76.         //    ADLER32 
  77.  
  78.         uint adler = ComputeAdler32(data); 
  79.         MemoryStream comp = new MemoryStream(); 
  80.  
  81.         // 64K的块数计算 
  82.         uint rowsPerBlock = _MAXBLOCK / widthLength; 
  83.         uint blockSize = rowsPerBlock * widthLength; 
  84.         uint blockCount; 
  85.         ushort length; 
  86.         uint remainder = dcSize; 
  87.  
  88.         if ((dcSize % blockSize) == 0) 
  89.         { 
  90.             blockCount = dcSize / blockSize; 
  91.         } 
  92.         else 
  93.         { 
  94.             blockCount = (dcSize / blockSize) + 1; 
  95.         } 
  96.  
  97.         // 头部 
  98.         comp.WriteByte(0x78); 
  99.         comp.WriteByte(0xDA); 
  100.  
  101.         for (uint blocks = 0; blocks < blockCount; blocks++) 
  102.         { 
  103.             // 长度 
  104.             length = (ushort)((remainder < blockSize) ? remainder : blockSize); 
  105.  
  106.             if (length == remainder) 
  107.             { 
  108.                 comp.WriteByte(0x01); 
  109.             } 
  110.             else 
  111.             { 
  112.                 comp.WriteByte(0x00); 
  113.             } 
  114.  
  115.             comp.Write(BitConverter.GetBytes(length), 0, 2); 
  116.  
  117.             comp.Write(BitConverter.GetBytes((ushort)~length), 0, 2); 
  118.  
  119.             // Write 块 
  120.             comp.Write(data, (int)(blocks * blockSize), length); 
  121.  
  122.             //下一块 
  123.             remainder -= blockSize; 
  124.         } 
  125.  
  126.         WriteReversedBuffer(comp, BitConverter.GetBytes(adler)); 
  127.         comp.Seek(0, SeekOrigin.Begin); 
  128.  
  129.         byte[] dat = new byte[comp.Length]; 
  130.         comp.Read(dat, 0, (int)comp.Length); 
  131.  
  132.         WriteChunk(ms, _IDAT, dat); 
  133.  
  134.         // Write IEND chunk 
  135.         WriteChunk(ms, _IEND, new byte[0]); 
  136.  
  137.         // Reset stream 
  138.         ms.Seek(0, SeekOrigin.Begin); 
  139.  
  140.         return ms; 
  141.     } 
  142.  
  143.     private static void WriteReversedBuffer(Stream stream, byte[] data) 
  144.     { 
  145.         int size = data.Length; 
  146.         byte[] reorder = new byte[size]; 
  147.  
  148.         for (int idx = 0; idx < size; idx++) 
  149.         { 
  150.             reorder[idx] = data[size - idx - 1]; 
  151.         } 
  152.         stream.Write(reorder, 0, size); 
  153.     } 
  154.  
  155.     private static void WriteChunk(Stream stream, byte[] type, byte[] data) 
  156.     { 
  157.         int idx; 
  158.         int size = type.Length; 
  159.         byte[] buffer = new byte[type.Length + data.Length]; 
  160.  
  161.         // 初始化缓冲 
  162.         for (idx = 0; idx < type.Length; idx++) 
  163.         { 
  164.             buffer[idx] = type[idx]; 
  165.         } 
  166.  
  167.         for (idx = 0; idx < data.Length; idx++) 
  168.         { 
  169.             buffer[idx + size] = data[idx]; 
  170.         } 
  171.  
  172.         WriteReversedBuffer(stream, BitConverter.GetBytes(data.Length)); 
  173.  
  174.         // Write 类型和数据 
  175.         stream.Write(buffer, 0, buffer.Length);   // Should always be 4 bytes 
  176.  
  177.         // 计算和书写的CRC 
  178.  
  179.         WriteReversedBuffer(stream, BitConverter.GetBytes(GetCRC(buffer))); 
  180.     } 
  181.  
  182.     private static uint[] _crcTable = new uint[256]; 
  183.     private static bool _crcTableComputed = false
  184.  
  185.     private static void MakeCRCTable() 
  186.     { 
  187.         uint c; 
  188.  
  189.         for (int n = 0; n < 256; n++) 
  190.         { 
  191.             c = (uint)n; 
  192.             for (int k = 0; k < 8; k++) 
  193.             { 
  194.                 if ((c & (0x00000001)) > 0) 
  195.                     c = 0xEDB88320 ^ (c >> 1); 
  196.                 else 
  197.                     c = c >> 1; 
  198.             } 
  199.             _crcTable[n] = c; 
  200.         } 
  201.  
  202.         _crcTableComputed = true
  203.     } 
  204.  
  205.     private static uint UpdateCRC(uint crc, byte[] buf, int len) 
  206.     { 
  207.         uint c = crc; 
  208.  
  209.         if (!_crcTableComputed) 
  210.         { 
  211.             MakeCRCTable(); 
  212.         } 
  213.  
  214.         for (int n = 0; n < len; n++) 
  215.         { 
  216.             c = _crcTable[(c ^ buf[n]) & 0xFF] ^ (c >> 8); 
  217.         } 
  218.  
  219.         return c; 
  220.     } 
  221.  
  222.     //返回的字节的CRC缓冲区 
  223.     private static uint GetCRC(byte[] buf) 
  224.     { 
  225.         return UpdateCRC(0xFFFFFFFF, buf, buf.Length) ^ 0xFFFFFFFF; 
  226.     } 
  227.  
  228.     private static uint ComputeAdler32(byte[] buf) 
  229.     { 
  230.         uint s1 = 1; 
  231.         uint s2 = 0; 
  232.         int length = buf.Length; 
  233.  
  234.         for (int idx = 0; idx < length; idx++) 
  235.         { 
  236.             s1 = (s1 + (uint)buf[idx]) % _ADLER32_BASE; 
  237.             s2 = (s2 + s1) % _ADLER32_BASE; 
  238.         } 
  239.  
  240.         return (s2 << 16) + s1; 
  241.     } 

        最后我们来看看运行的效果如下,如需源码请点击 SLInkPresenter.zip下载: