Silverlight – InkPresenter Doodle Pad Basic Usage and Save Image as Png

Sometimes, we need to draw lines by handing in Silverlight. InkPresenter can help us. It supports users to use mouse, writing pad and other tools to draw graph or handwriting.

InkPresenter inherits Canvas control and supports all the Canvas properties. Also, the other controls can be nested inside it. There are three levels of InkPresenter: bottom is background of InkPresenter; Middle is control with Children property of InkPresenter; the last is stroke of Strokes property.

We can set color, thinkness, border color and other properties for Stroke to get wonderful type. The following XAML code shows how to use InkPresenter.

    <Gridx:Name=”LayoutRoot1″Background=”White”>

<Canvas>

<BorderBorderThickness=”1″Margin=”50 10 0 0″BorderBrush=”CadetBlue”HorizontalAlignment=”Center”VerticalAlignment=”Center”>

<InkPresenterx:Name=”iPresenter”Height=”500″Width=”500″MouseLeftButtonDown=”iPresenter_MouseLeftButtonDown”LostMouseCapture=”iPresenter_LostMouseCapture”MouseMove=”iPresenter_MouseMove”Background=”Transparent”Opacity=”1″ >

<TextBoxWidth=”138″Canvas.Left=”58″Canvas.Top=”105″></TextBox>

</InkPresenter>

</Border>

<ButtonCanvas.Left=”560″Canvas.Top=”11″Content=”Save Graffiti as Image”Height=”23″Name=”button1″Width=”104″Click=”button1_Click” />

<ImageName=”showIP”Width=”400″Height=”400″Canvas.Left=”560″Canvas.Top=”60″></Image>

</Canvas>

</Grid>

Xaml.cs code:

    public partial class MainPage : UserControl

    {

        public MainPage()

        {

            InitializeComponent();

            SetPresenterClip();

        }

        Stroke myStroke;

        private void iPresenter_MouseLeftButtonDown(object sender, MouseEventArgs e)

        {

            //Use Mouse to Catch Data

            iPresenter.CaptureMouse();

            //Collect Data Point and Save Value to StylusPointCollection

            StylusPointCollection stylusPointCollection = new StylusPointCollection();

            stylusPointCollection.Add(e.StylusDevice.GetStylusPoints(iPresenter));

            //Save Data Point Collection as Stroke

            myStroke = new Stroke(stylusPointCollection);

            //Set Drawing Effect of Stork as Color, Size and so on

            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)

        {

            //Add Data Point to Stroke when Moving Mouse

            if (myStroke != null)

                myStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(iPresenter));

        }

        private void iPresenter_LostMouseCapture(object sender, MouseEventArgs e)

        {

            //Clear Stroke

            myStroke = null;

            iPresenter.ReleaseMouseCapture();//Release Mouse Location

        }

        ///<summary>

        ///Set Drawing Range as Size of InkPresente

        ///</summary>

        private void SetPresenterClip()

        {

            RectangleGeometry MyRectangleGeometry = new RectangleGeometry();

            MyRectangleGeometry.Rect = new Rect(0, 0, iPresenter.ActualWidth, iPresenter.ActualHeight);

            //Set Available Range to Get Drawing Content

            iPresenter.Clip = MyRectangleGeometry;

        }

        private void button1_Click(object sender, RoutedEventArgs e)

        {

            //Save Image which is Drawing in InkPresenter Doodle Pad

            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)

                                );

                        }

                    }

                    //Get Stream

                    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(“Succeed to Save Image”);

                }

            }

        }

}

If we want to save the image which is drawn in InkPresenter as png, we need to use two classes which is assisted to convert image to png.

    ///<summary>

    ///Edit Image

    ///</summary>

    public class EditableImage

    {

        private int _width = 0;

        private int _height = 0;

        private bool _init = false;

        private byte[] _buffer;

        private int _rowLength;

        ///<summary>

        ///Trigger when Having Image Error

        ///</summary>

        public event EventHandler<EditableImageErrorEventArgs> ImageError;

        ///<summary>

        ///Instantiate

        ///</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(“Error: Width cannot be changed after initializing image.”);

                }

                else if ((value <= 0) || (value > 2047))

                {

                    OnImageError(“Error: Width must be between 0 and 2047”);

                }

                else

                {

                    _width = value;

                }

            }

        }

        public int Height

        {

            get

            {

                return _height;

            }

            set

            {

                if (_init)

                {

                    OnImageError(“Error: Height cannot be changed after initializing image.”);

                }

                else if ((value <= 0) || (value > 2047))

                {

                    OnImageError(“Error: Height must be between 0 and 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;     

                }

                _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 Operation Class:

    ///<summary>

    ///PNG Format Operation Class

    ///</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>

        ///Coding

        ///</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 Counting

            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;

                // Header

                comp.WriteByte(0x78);

                comp.WriteByte(0xDA);

                for (uint blocks = 0; blocks < blockCount; blocks++)

                {

                    // Length

                    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 Block

                    comp.Write(data, (int)(blocks * blockSize), length);

                    //Next One

                    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];

            // Initialization Load

            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 Type and Data

            stream.Write(buffer, 0, buffer.Length);  

            // Calculate and Write 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;

        }

        //Return Byte CRC Buffer Zone

        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;

        }

    }

Two Components Recommend:

Spire.Doc, is used to operate MS Word document for .Net and Silverlight, including basic manipulations (generate, open, edit documents), mail merge and other Word functions manipulations.

Spire.XLS, is used to operate MS Excel for .NET and Silverlight, including basic manipulations (generate, open, edit files), chart creation and data exporting.

Advertisements

One thought on “Silverlight – InkPresenter Doodle Pad Basic Usage and Save Image as Png

  1. ms130r gz ms130b bn says:

    You are so cool! I do not suppose I have read something like that before.
    So good to find someone with unique thoughts on this topic.
    Seriously.. thanks for starting this up. This web site is one thing that is needed on the internet, someone
    with a little originality!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s