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

posted @ 2011-07-28 10:18  程兴亮  阅读(3835)  评论(7编辑  收藏