在Silverlight中我们有时候需要手工绘制线条或者直线等,在这里我们认识一下InkPresenter控件,它将支持用户使用鼠标、手写板等工具来绘制图形或者笔迹,用途为涂鸦、笔迹确认等等。
InkPresenter是继承于Canvas控件的支持所有的Canvas属性,并且其内部还可以嵌套显示其他控件。InkPresenter控件的显 示分为三层:底层是InkPresenter的Background、中间层是InkPresenter的Children属性的控件、最后才是 Strokes属性中的笔画层。
对于Strokes属性中的笔画Stroke我们可以设置它的颜色、粗细、外边框颜色等等属性以获得满意的笔画类型。下面我们来看看如何使用InkPresenter控件,首先我们来看Xaml代码如下:
- <Grid x:Name="LayoutRoot1" Background="White">
- <Canvas>
- <Border BorderThickness="1" Margin="50 10 0 0" BorderBrush="CadetBlue"
- HorizontalAlignment="Center" VerticalAlignment="Center">
- <InkPresenter x:Name="iPresenter" Height="500" Width="500"
- MouseLeftButtonDown="iPresenter_MouseLeftButtonDown"
- LostMouseCapture="iPresenter_LostMouseCapture"
- MouseMove="iPresenter_MouseMove"
- Background="Transparent" Opacity="1" >
- <TextBox Width="138" Canvas.Left="58" Canvas.Top="105"></TextBox>
- </InkPresenter>
- </Border>
- <Button Canvas.Left="560" Canvas.Top="11" Content="将涂鸦保存为图片" Height="23"
- Name="button1" Width="104" Click="button1_Click" />
- <Image Name="showIP" Width="400" Height="400" Canvas.Left="560" Canvas.Top="60"></Image>
- </Canvas>
- </Grid>
然后我们来看看Xaml.cs代码如下:
- public partial class MainPage : UserControl
- {
- public MainPage()
- {
- InitializeComponent();
- SetPresenterClip();
- }
- Stroke myStroke;
- private void iPresenter_MouseLeftButtonDown(object sender, MouseEventArgs e)
- {
- //让鼠标捕获数据
- iPresenter.CaptureMouse();
- //收集笔触数据点保存值StylusPointCollection集合中
- StylusPointCollection stylusPointCollection = new StylusPointCollection();
- stylusPointCollection.Add(e.StylusDevice.GetStylusPoints(iPresenter));
- //将数据点的结合保存为一个笔画
- myStroke = new Stroke(stylusPointCollection);
- //设置笔画的绘画效果,如颜色,大小等。
- myStroke.DrawingAttributes.Color = Colors.Gray;
- myStroke.DrawingAttributes.Width = 1;
- myStroke.DrawingAttributes.Height = 1;
- iPresenter.Strokes.Add(myStroke);
- }
- private void iPresenter_MouseMove(object sender, MouseEventArgs e)
- {
- //在鼠标移动的过程中将数据点加入到笔画中去。
- if (myStroke != null)
- myStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(iPresenter));
- }
- private void iPresenter_LostMouseCapture(object sender, MouseEventArgs e)
- {
- //将笔画清空
- myStroke = null;
- iPresenter.ReleaseMouseCapture();//释放鼠标坐标
- }
- /// <summary>
- /// 设置绘画区域为InkPresenter的大小
- /// </summary>
- private void SetPresenterClip()
- {
- RectangleGeometry MyRectangleGeometry = new RectangleGeometry();
- MyRectangleGeometry.Rect = new Rect(0, 0, iPresenter.ActualWidth, iPresenter.ActualHeight);
- //设置获取绘画内容的有效区域
- iPresenter.Clip = MyRectangleGeometry;
- }
- private void button1_Click(object sender, RoutedEventArgs e)
- {
- //保存InkPresenter涂鸦板内绘画的图
- WriteableBitmap _bitmap = new WriteableBitmap(iPresenter, null);
- this.showIP.Source = _bitmap;
- SaveFileDialog sfd = new SaveFileDialog();
- sfd.Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*";
- sfd.DefaultExt = ".png";
- sfd.FilterIndex = 1;
- if ((bool)sfd.ShowDialog())
- {
- using (Stream fs = sfd.OpenFile())
- {
- int width = _bitmap.PixelWidth;
- int height = _bitmap.PixelHeight;
- EditableImage ei = new EditableImage(width, height);
- for (int i = 0; i < height; i++)
- {
- for (int j = 0; j < width; j++)
- {
- int pixel = _bitmap.Pixels[(i * width) + j];
- ei.SetPixel(j, i,
- (byte)((pixel >> 16) & 0xFF),
- (byte)((pixel >> 8) & 0xFF),
- (byte)(pixel & 0xFF),
- (byte)((pixel >> 24) & 0xFF)
- );
- }
- }
- //获取流
- Stream png = ei.GetStream();
- int len = (int)png.Length;
- byte[] bytes = new byte[len];
- png.Read(bytes, 0, len);
- fs.Write(bytes, 0, len);
- MessageBox.Show("图片保存成功!");
- }
- }
- }
- }
对于将InkPresenter中绘画出来的图片保存为Png图片得处理,我们在这里借鉴了园子中永恒的记忆兄弟的将元素转为Png图片的方法,在这里贴出两个辅助转Png格式的类。
- /// <summary>
- /// 编辑图片
- /// </summary>
- public class EditableImage
- {
- private int _width = 0;
- private int _height = 0;
- private bool _init = false;
- private byte[] _buffer;
- private int _rowLength;
- /// <summary>
- /// 当图片错误时引发
- /// </summary>
- public event EventHandler<EditableImageErrorEventArgs> ImageError;
- /// <summary>
- /// 实例化
- /// </summary>
- /// <param name="width"></param>
- /// <param name="height"></param>
- public EditableImage(int width, int height)
- {
- this.Width = width;
- this.Height = height;
- }
- public int Width
- {
- get
- {
- return _width;
- }
- set
- {
- if (_init)
- {
- OnImageError("错误: 图片初始化后不可以改变宽度");
- }
- else if ((value <= 0) || (value > 2047))
- {
- OnImageError("错误: 宽度必须在 0 到 2047");
- }
- else
- {
- _width = value;
- }
- }
- }
- public int Height
- {
- get
- {
- return _height;
- }
- set
- {
- if (_init)
- {
- OnImageError("错误: 图片初始化后不可以改变高度");
- }
- else if ((value <= 0) || (value > 2047))
- {
- OnImageError("错误: 高度必须在 0 到 2047");
- }
- else
- {
- _height = value;
- }
- }
- }
- public void SetPixel(int col, int row, Color color)
- {
- SetPixel(col, row, color.R, color.G, color.B, color.A);
- }
- public void SetPixel(int col, int row, byte red, byte green, byte blue, byte alpha)
- {
- if (!_init)
- {
- _rowLength = _width * 4 + 1;
- _buffer = new byte[_rowLength * _height];
- // Initialize
- for (int idx = 0; idx < _height; idx++)
- {
- _buffer[idx * _rowLength] = 0; // Filter bit
- }
- _init = true;
- }
- if ((col > _width) || (col < 0))
- {
- OnImageError("Error: Column must be greater than 0 and less than the Width");
- }
- else if ((row > _height) || (row < 0))
- {
- OnImageError("Error: Row must be greater than 0 and less than the Height");
- }
- // Set the pixel
- int start = _rowLength * row + col * 4 + 1;
- _buffer[start] = red;
- _buffer[start + 1] = green;
- _buffer[start + 2] = blue;
- _buffer[start + 3] = alpha;
- }
- public Color GetPixel(int col, int row)
- {
- if ((col > _width) || (col < 0))
- {
- OnImageError("Error: Column must be greater than 0 and less than the Width");
- }
- else if ((row > _height) || (row < 0))
- {
- OnImageError("Error: Row must be greater than 0 and less than the Height");
- }
- Color color = new Color();
- int _base = _rowLength * row + col + 1;
- color.R = _buffer[_base];
- color.G = _buffer[_base + 1];
- color.B = _buffer[_base + 2];
- color.A = _buffer[_base + 3];
- return color;
- }
- public Stream GetStream()
- {
- Stream stream;
- if (!_init)
- {
- OnImageError("Error: Image has not been initialized");
- stream = null;
- }
- else
- {
- stream = PngEncoder.Encode(_buffer, _width, _height);
- }
- return stream;
- }
- private void OnImageError(string msg)
- {
- if (null != ImageError)
- {
- EditableImageErrorEventArgs args = new EditableImageErrorEventArgs();
- args.ErrorMessage = msg;
- ImageError(this, args);
- }
- }
- public class EditableImageErrorEventArgs : EventArgs
- {
- private string _errorMessage = string.Empty;
- public string ErrorMessage
- {
- get { return _errorMessage; }
- set { _errorMessage = value; }
- }
- }
- }
这是Png操作类:
- /// <summary>
- /// PNG格式操作类
- /// </summary>
- public class PngEncoder
- {
- private const int _ADLER32_BASE = 65521;
- private const int _MAXBLOCK = 0xFFFF;
- private static byte[] _HEADER = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
- private static byte[] _IHDR = { (byte)'I', (byte)'H', (byte)'D', (byte)'R' };
- private static byte[] _GAMA = { (byte)'g', (byte)'A', (byte)'M', (byte)'A' };
- private static byte[] _IDAT = { (byte)'I', (byte)'D', (byte)'A', (byte)'T' };
- private static byte[] _IEND = { (byte)'I', (byte)'E', (byte)'N', (byte)'D' };
- private static byte[] _4BYTEDATA = { 0, 0, 0, 0 };
- private static byte[] _ARGB = { 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 0, 0, 0 };
- /// <summary>
- /// 编码
- /// </summary>
- /// <param name="data"></param>
- /// <param name="width"></param>
- /// <param name="height"></param>
- /// <returns></returns>
- public static Stream Encode(byte[] data, int width, int height)
- {
- MemoryStream ms = new MemoryStream();
- byte[] size;
- // Write PNG header
- ms.Write(_HEADER, 0, _HEADER.Length);
- // Write IHDR
- // Width: 4 bytes
- // Height: 4 bytes
- // Bit depth: 1 byte
- // Color type: 1 byte
- // Compression method: 1 byte
- // Filter method: 1 byte
- // Interlace method: 1 byte
- size = BitConverter.GetBytes(width);
- _ARGB[0] = size[3]; _ARGB[1] = size[2]; _ARGB[2] = size[1]; _ARGB[3] = size[0];
- size = BitConverter.GetBytes(height);
- _ARGB[4] = size[3]; _ARGB[5] = size[2]; _ARGB[6] = size[1]; _ARGB[7] = size[0];
- // Write IHDR chunk
- WriteChunk(ms, _IHDR, _ARGB);
- // Set gamma = 1
- size = BitConverter.GetBytes(1 * 100000);
- _4BYTEDATA[0] = size[3]; _4BYTEDATA[1] = size[2]; _4BYTEDATA[2] = size[1]; _4BYTEDATA[3] = size[0];
- // Write gAMA chunk
- WriteChunk(ms, _GAMA, _4BYTEDATA);
- // Write IDAT chunk
- uint widthLength = (uint)(width * 4) + 1;
- uint dcSize = widthLength * (uint)height;
- // First part of ZLIB header is 78 1101 1010 (DA) 0000 00001 (01)
- // ZLIB info
- //
- // CMF Byte: 78
- // CINFO = 7 (32K window size)
- // CM = 8 = (deflate compression)
- // FLG Byte: DA
- // FLEVEL = 3 (bits 6 and 7 - ignored but signifies max compression)
- // FDICT = 0 (bit 5, 0 - no preset dictionary)
- // FCHCK = 26 (bits 0-4 - ensure CMF*256+FLG / 31 has no remainder)
- // Compressed data
- // FLAGS: 0 or 1
- // 00000 00 (no compression) X (X=1 for last block, 0=not the last block)
- // LEN = length in bytes (equal to ((width*4)+1)*height
- // NLEN = one's compliment of LEN
- // Example: 1111 1011 1111 1111 (FB), 0000 0100 0000 0000 (40)
- // Data for each line: 0 [RGBA] [RGBA] [RGBA] ...
- // ADLER32
- uint adler = ComputeAdler32(data);
- MemoryStream comp = new MemoryStream();
- // 64K的块数计算
- uint rowsPerBlock = _MAXBLOCK / widthLength;
- uint blockSize = rowsPerBlock * widthLength;
- uint blockCount;
- ushort length;
- uint remainder = dcSize;
- if ((dcSize % blockSize) == 0)
- {
- blockCount = dcSize / blockSize;
- }
- else
- {
- blockCount = (dcSize / blockSize) + 1;
- }
- // 头部
- comp.WriteByte(0x78);
- comp.WriteByte(0xDA);
- for (uint blocks = 0; blocks < blockCount; blocks++)
- {
- // 长度
- length = (ushort)((remainder < blockSize) ? remainder : blockSize);
- if (length == remainder)
- {
- comp.WriteByte(0x01);
- }
- else
- {
- comp.WriteByte(0x00);
- }
- comp.Write(BitConverter.GetBytes(length), 0, 2);
- comp.Write(BitConverter.GetBytes((ushort)~length), 0, 2);
- // Write 块
- comp.Write(data, (int)(blocks * blockSize), length);
- //下一块
- remainder -= blockSize;
- }
- WriteReversedBuffer(comp, BitConverter.GetBytes(adler));
- comp.Seek(0, SeekOrigin.Begin);
- byte[] dat = new byte[comp.Length];
- comp.Read(dat, 0, (int)comp.Length);
- WriteChunk(ms, _IDAT, dat);
- // Write IEND chunk
- WriteChunk(ms, _IEND, new byte[0]);
- // Reset stream
- ms.Seek(0, SeekOrigin.Begin);
- return ms;
- }
- private static void WriteReversedBuffer(Stream stream, byte[] data)
- {
- int size = data.Length;
- byte[] reorder = new byte[size];
- for (int idx = 0; idx < size; idx++)
- {
- reorder[idx] = data[size - idx - 1];
- }
- stream.Write(reorder, 0, size);
- }
- private static void WriteChunk(Stream stream, byte[] type, byte[] data)
- {
- int idx;
- int size = type.Length;
- byte[] buffer = new byte[type.Length + data.Length];
- // 初始化缓冲
- for (idx = 0; idx < type.Length; idx++)
- {
- buffer[idx] = type[idx];
- }
- for (idx = 0; idx < data.Length; idx++)
- {
- buffer[idx + size] = data[idx];
- }
- WriteReversedBuffer(stream, BitConverter.GetBytes(data.Length));
- // Write 类型和数据
- stream.Write(buffer, 0, buffer.Length); // Should always be 4 bytes
- // 计算和书写的CRC
- WriteReversedBuffer(stream, BitConverter.GetBytes(GetCRC(buffer)));
- }
- private static uint[] _crcTable = new uint[256];
- private static bool _crcTableComputed = false;
- private static void MakeCRCTable()
- {
- uint c;
- for (int n = 0; n < 256; n++)
- {
- c = (uint)n;
- for (int k = 0; k < 8; k++)
- {
- if ((c & (0x00000001)) > 0)
- c = 0xEDB88320 ^ (c >> 1);
- else
- c = c >> 1;
- }
- _crcTable[n] = c;
- }
- _crcTableComputed = true;
- }
- private static uint UpdateCRC(uint crc, byte[] buf, int len)
- {
- uint c = crc;
- if (!_crcTableComputed)
- {
- MakeCRCTable();
- }
- for (int n = 0; n < len; n++)
- {
- c = _crcTable[(c ^ buf[n]) & 0xFF] ^ (c >> 8);
- }
- return c;
- }
- //返回的字节的CRC缓冲区
- private static uint GetCRC(byte[] buf)
- {
- return UpdateCRC(0xFFFFFFFF, buf, buf.Length) ^ 0xFFFFFFFF;
- }
- private static uint ComputeAdler32(byte[] buf)
- {
- uint s1 = 1;
- uint s2 = 0;
- int length = buf.Length;
- for (int idx = 0; idx < length; idx++)
- {
- s1 = (s1 + (uint)buf[idx]) % _ADLER32_BASE;
- s2 = (s2 + s1) % _ADLER32_BASE;
- }
- return (s2 << 16) + s1;
- }
- }
最后我们来看看运行的效果如下,如需源码请点击 SLInkPresenter.zip下载:
本文转自程兴亮 51CTO博客,原文链接:http://blog.51cto.com/chengxingliang/826757