• Silverlight InkPresenter 实现路径回放的探索


    从Silverlight从1.0版本开始,就提供了InkPresenter控件。很多人在第一次了解到这个控件时非常兴奋,纷纷打算做“手写识别”“网络共享白板”等应用程序。可惜Silverlight的InkPresenter只是WPF中InkPresenter的阉割版,想要扩展它不是说不可以,而是一件相当伤脑筋的麻烦事——至少,到目前为止,我都没有见到过真正的Silverlight网络共享白板。

    博客园的webabcd的在4个月前的一篇文章的评论中,也说到了打算做Silverlight 网络白板的问题。

    WXWinter(冬)在3个月前用Silverlight+WCF完成了一个“近似”的“网络共享白板”。(围观连接:http://www.cnblogs.com/foundation/archive/2008/12/02/1345506.html

    为什么我说他是“近似”的呢?

    这得先从InkPresenter的原理说起:用户用鼠标画在InkPresenter上的笔迹,都被保存为

    StrokeCollection类型的inkPresenter.Strokes里。顾名思义,StrokeCollectionStroke
    的集合。Stroke可以通俗地理解成“一笔”。而这“一笔”是“一条线”,它是包由很多“点”构成的,Stroke把关键的点(有转折的点)保存在Stroke.StylusPoints里,StylusPoint则是具体点每个点。

    可惜的是,StylusPoint里除了X和Y坐标外,几乎没有提供任何可供编程的接口和方法,连Visible这种属性都没有提供。Stroke稍好一些,但也提供得不多。

    WXWinter(冬)的“网络共享白板”是以Stroke为单位的。当用户画完“一整笔”后,Silverlight程序将描述这”一整笔”的Stroke通过WCF发送到服务器,同时通过Timer定时取得服务器上最新版本的所有Stroke

    用过基于socket的“网络白板”的人都知道,WXWinter(冬)的方法只是一个近似的方法。第一,它没有真正的“实时”,这个问题不大,就算是不直接使用socket,Shareach也示范了使用WCF的解决方式第二,它的数据是以“线”为单位的,在实际使用时,对方看不到你画线的过程,只是会突然发现自己的屏幕上出现一条别人画的线,这是一件比较囧的事。

    说了这么多,终于进入正题了:我最近一直在思考以上提到的第二个问题,如何直播或回放用户画线的过程,而不是让那些笔迹一整条一整条地跳出来呢?我认为,首先要把“点”从“线”中分离出来,对“点”编程而不是操作“线”;其次记录用户画每个点的准确时间;第三,使用动画。本文展示一个回放用户在InkPresenter上涂鸦过程的Demo。

    效果图:

    无标题

     

    现场Demo  (需要安装Silverlight 3.0控件,在这里安装:http://download.microsoft.com/download/0/D/7/0D76C405-E0E5-43CC-89D3-18243A4FCA86/Silverlight.3.0_Developer.exe )

    【使用说明】
    1.等待数据加载完,
    2.点击“开始录制”,
    3.音乐响起,你可以随便涂写.
    4.画完后点击“停止录制”.
    5.点击”回放预览”查看你的杰作。

    (如果你看不到下面的Silverlight对象,可以到这里查看:http://azuredrive.blob.core.windows.net/netdrive1/file_98a1cf05-94e2-4918-a6ef-29e791c8e327.html
    Get Microsoft Silverlight  

    实现步骤草图:

    1.InkPresenter的XAML代码及基本操作

      <InkPresenter Name='inkPresenter' Canvas.Left='10' Canvas.Top='10' 
                    MouseLeftButtonDown
    ='onInkPresenterDown' MouseMove='onInkPresenterMove' MouseLeftButtonUp='onInkPresenterUp'>

           
    <InkPresenter.Resources>
               
    <Storyboard Duration="0:0:0" Completed="onStrokePlaybackTimerTick" x:Name="strokePlaybackTimer" />
           
    </InkPresenter.Resources>
           
    <MediaElement Name='mediaElement' Source="http://azuredrive.blob.core.windows.net/netdrive1/file_c6184705-b9f7-49e9-a2e9-1e76a01a4565.wmv" Width='720' Height='480' 
                      AutoPlay
    ='False'   MediaEnded="onMediaEnded"/>

       
    </InkPresenter>


    InkPresenter的基本操作请参考webabcd的这篇文章

    2.用视频(或音频)的时间轴来作为涂鸦事件的时间轴,记录每一笔的开始时间
    仔细看看上文的InkPresenter的XAML代码。它的Resources里是动画信息,它的内容仅仅是一个WMV媒体文件。我们之前提到了要保存每一个笔画的时间,就可以直接使用这个媒体文件的时间轴。

    具体操作是这样的:

       if (isRecording)
               
    {
                   
    //捕获鼠标
                    inkPresenter.CaptureMouse();

                    newStroke
    = new Stroke();
                   
    //设置该笔画的属性。本Demo中全部使用默认属性。
                    newStroke.DrawingAttributes = defaultDrawingAttributes;
                   
    //记录该笔的第一个点的信息
                    newStroke.StylusPoints.Add(e.StylusDevice.GetStylusPoints(inkPresenter));
                    inkPresenter.Strokes.Add(newStroke);
                   
    //记录该笔第一个点画下的时间
                    strokeStartTimes.Add(mediaElement.Position.Seconds);
                }

    3.考虑到可能与服务器或其他网络用户的交互,我们用单一的string保存“点”的信息,
    用int数组保存时间信息

      //用于保存每笔每个点的信息,分别用';'和','隔开
            string inkStringForPlayback = null;
           
    //用于保存每笔的开始时间

            List<int> strokeStartTimes = new List<int>();

    同时提供string与Strokes互相转换的两个函数:

    private string ConvertInkToString(StrokeCollection strokes)
            {
               
               
    string serializedStylusPoints = "";
                if (strokes != null)
                {
                   
    int strokeCount = strokes.Count;

                    for (int i = 0; i < strokeCount; i++)
                    {
                        Stroke stroke
    = strokes[i];
                        
                        int packetCount = stroke.StylusPoints.Count;
                        for (int j = 0; j < packetCount; j++)
                        {
                            StylusPoint stylusPoint
    = stroke.StylusPoints[j];
                            serializedStylusPoints += stylusPoint.X.ToString();
                            serializedStylusPoints += ",";
                            serializedStylusPoints += stylusPoint.Y.ToString();
                            if (j != packetCount - 1)
                            {
                                serializedStylusPoints
    += ",";
                            }
                           
    else
                           
    {
                                serializedStylusPoints
    += ";";
                            }

                        }

                    }

                }

               
    return serializedStylusPoints;
            }


         
      private StrokeCollection ConvertStringToInk(string serializedStylusPoints)
            {
                StrokeCollection strokes
    = new StrokeCollection();
                string[] strokeStrings = serializedStylusPoints.Split(';');
                for (var i = 0; i < strokeStrings.Length - 1; i++)
                {
                    Stroke stroke
    = new Stroke();
                    stroke.DrawingAttributes = defaultDrawingAttributes;
                    string[] stylusPoints = strokeStrings[i].Split(',');
                    for (var j = 0; j < stylusPoints.Length / 2; j++)
                    {
                        StylusPoint stylusPoint
    = new StylusPoint();
     
                        stylusPoint.X = double.Parse(stylusPoints[2 * j]);
                        stylusPoint.Y = double.Parse(stylusPoints[2 * j + 1]);
                        stroke.StylusPoints.Add(stylusPoint);
                    }
                    strokes.Add(stroke);
                }

               
    return strokes;
            }


    4.根据时间轴,动态画出每一笔、每个点。

    private void onStrokePlaybackTimerTick(object sender,  EventArgs e)
           
    {
               
    if (strokesForPlayback.Count == 0) return;
                Stroke currentStroke
    = strokesForPlayback[playbackStrokeIndex];
               
    if (playbackPointIndex == 0)
               
    {
                   
    if (mediaElement.Position.Seconds < strokeStartTimes[playbackStrokeIndex])
                   
    {
                        strokePlaybackTimer.Begin();
                       
    return;
                    }

                    strokeToPlayback
    = new Stroke();
                    inkPresenter.Strokes.Add(strokeToPlayback);
                    strokeToPlayback.DrawingAttributes
    = currentStroke.DrawingAttributes;
                }

                strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
                playbackPointIndex
    ++;
               
    if (playbackPointIndex < currentStroke.StylusPoints.Count)
               
    {
                   
                    strokeToPlayback.StylusPoints.Add(currentStroke.StylusPoints[playbackPointIndex]);
                    playbackPointIndex
    ++;
                }

               
    if (playbackPointIndex == currentStroke.StylusPoints.Count)
               
    {
                    playbackPointIndex
    = 0;
                    playbackStrokeIndex
    ++;
                   
    if (playbackStrokeIndex == strokesForPlayback.Count)
                   
    {
                       
    return;
                    }

                }

                strokePlaybackTimer.Begin();
            }
  • 相关阅读:
    android 源码
    android 保护
    电池信息 显示
    RGB、HSB、HSL 互相转换算法
    网页美工
    css 设计标准
    js 封闭 小结
    格式转换工具
    网页设计规范
    瀑布流分析
  • 原文地址:https://www.cnblogs.com/azure/p/1425400.html
Copyright © 2020-2023  润新知