zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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等方面的知识。平时喜欢看书、写技术博客、摄影、旅游和运动。