zoukankan      html  css  js  c++  java
  • Silverlight实用窍门系列:50.InkPresenter涂鸦板的基本使用,以及将效果保存为Png图片【附带源码实例】

            在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下载:

  • 相关阅读:
    LeetCode 227. Basic Calculator II
    LeetCode 224. Basic Calculator
    LeetCode 103. Binary Tree Zigzag Level Order Traversal
    LeetCode 102. Binary Tree Level Order Traversal
    LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal
    LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal
    LeetCode 169. Majority Element
    LeetCode 145. Binary Tree Postorder Traversal
    LeetCode 94. Binary Tree Inorder Traversal
    LeetCode 144. Binary Tree Preorder Traversal
  • 原文地址:https://www.cnblogs.com/chengxingliang/p/2118404.html
Copyright © 2011-2022 走看看