Silverlight实用窍门系列:50.InkPresenter涂鸦板的基本使用,以及将效果保存为Png图片【附带源码实例】
2023-09-14 08:56:52 时间
在Silverlight中我们有时候需要手工绘制线条或者直线等,在这里我们认识一下InkPresenter控件,它将支持用户使用鼠标、手写板等工具来绘制图形或者笔迹,用途为涂鸦、笔迹确认等等。
InkPresenter是继承于Canvas控件的支持所有的Canvas属性,并且其内部还可以嵌套显示其他控件。InkPresenter控件的显示分为三层:底层是InkPresenter的Background、中间层是InkPresenter的Children属性的控件、最后才是Strokes属性中的笔画层。
对于Strokes属性中的笔画Stroke我们可以设置它的颜色、粗细、外边框颜色等等属性以获得满意的笔画类型。下面我们来看看如何使用InkPresenter控件,首先我们来看Xaml代码如下:
然后我们来看看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 return _width; if (_init) OnImageError("错误: 图片初始化后不可以改变宽度"); else if ((value = 0) || (value 2047)) OnImageError("错误: 宽度必须在 0 到 2047"); else _width = value; public int Height return _height; 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 = ones 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 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下载:
本文提供全流程,中文翻译。 Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 —— 高分辨率用户请根据需求调整网页缩放比例)
煦风满裳 程兴亮,专注于C#方面的开发工作,喜欢钻研Silverlight,CIL等方面的知识。平时喜欢看书、写技术博客、摄影、旅游和运动。
相关文章
- Canny算法解析,opencv源码实现及实例[通俗易懂]
- 【08】Spring源码-分析篇-Bean的实例化
- IOS—静态方法(类方法)和实例方法详解手机开发
- MySQL编程实例:25个精选实践经验分享(mysql编程实例)
- MySQL三范式详解及实例解析(mysql三个范式举例)
- jsp源码实例1(输出)
- jsp源码实例5(cookie)
- jsp源码实例2(获取表单参数)
- PHP中实现汉字转区位码应用源码实例解析
- JavaScript对象和字串之间的转换实例探讨
- Android中实现多行、水平滚动的分页的Gridview实例源码
- php预定义变量使用帮助(带实例)
- php根据身份证号码计算年龄的实例代码
- php中使用getimagesize获取图片、flash等文件的尺寸信息实例
- C#获取网页HTML源码实例
- 用队列模拟jquery的动画算法实例
- javascript中sort()的用法实例分析