2010年9月28日

Jigsaw Puzzle by Silverlight

我打算使用 Silverlight 來寫拼圖遊戲,並以網友自己的照片,來做為圖案。為了要完成這項工作,需要將複雜的過程,分成幾個步驟,並找到對應的解決方法。
  1. 首先,我們可以想像將照片切分成大小相同的矩形。
  2. 接著將矩形的四個邊,轉換成拼圖所特有的曲線。
  3. 由於曲線的兩端固定在矩形的頂點上,利用亂數改變曲線的線條,便可以產生每個形狀都有些微差異的拼塊了。

執行上列的步驟,我們需要了解 Path 的語法,以及切割照片的方法。

Cubic Bezier Curve

利用 Cubic Beizer Curver 可以很容易設計出我們所要的曲線。上面的解說圖,說明 Cubic Bezier Curve 的語法,如果是直接寫在 xaml 上,結果如下:
<Path Data="Mx1,y1 Cx2,y2 x3,y3 x4,y4 C....", ..>

如果用程式來產生,程式碼如下:
            Path path = new Path();
            PathGeometry pathGeometry = new PathGeometry(); 

            path.Data = pathGeometry; 
            pathGeometry.Figures = new PathFigureCollection(); 

            PathFigure figur = new PathFigure(); 
            pathGeometry.Figures.Add(figur); 
            figur.StartPoint = new Point(x1, y1);

            BezierSegment seg1 = new BezierSegment();
            seg1.Point1 = new Point(x2, y2);
            seg1.Point2 = new Point(x3, y3);
            seg1.Point3 = new Point(x4, y4);
            figur.Segments.Add(seg1); 
            :            
            :            
            path.Stroke = new SolidColorBrush(Colors.Blue);
            path.StrokeThickness = 3.0;
            this.LayoutRoot.Children.Add(path);


Piece Sharp

接下來,可以利用 Expression Blend 設計一段邊線的模板 (PathNodes),並調整數值,使左右能夠對稱,並利用旋轉、鏡射,產出完整的拼塊圖案。
        
Point[] PathNodes = new Point[]
        {
            new Point( 33,8),  // C1-1
            new Point(69,9),   // C1-2
            new Point(77,-8),   // C1-3

            new Point( 85,-23),  // C2-1
            new Point(54,-39),   // C2-2 
            new Point(65,-58),   // C2-3

            new Point( 77,-76),          // C3-1
            new Point(200-77,-76),    // C3-2
            new Point(200-65,-58),   // C3-3

            new Point(200-54,-39),   // C4-1
            new Point(200-85,-23),   // C4-2
            new Point(200-77,-8),     // C4-3

            new Point(200-69,9),  // C5-1 
            new Point(200-33,8),  // C5-2
            new Point(200,0)        // C5-3
        };

        private enum EdgeOrientation
        {
            Horizontal = 0,
            Vertical = 1
        }

       private PathGeometry CreateJigsawPuzzlePathGrometry(Point startPoint, Size pieceSize, EdgeOrientation orientation)
        {            
            PathGeometry pathGeometry = new PathGeometry();
            pathGeometry.Figures = new PathFigureCollection();

            PathFigure figur = new PathFigure();
            figur.StartPoint = startPoint;
            pathGeometry.Figures.Add(figur);

            Point lastPoint = startPoint;

            for (int edge = 0; edge < 4; edge++)
            {
                TransformGroup group = new TransformGroup();
                double scaleX = (edge % 2 == 0) ? pieceSize.Width / 200 : pieceSize.Height / 200;
                double scaleY = (edge % 2 == 0) ? pieceSize.Height / 200 : pieceSize.Width / 200;
                scaleY = (edge % 2 == (int)orientation) ? -scaleY : scaleY;

                ScaleTransform st = new ScaleTransform() { ScaleX = scaleX, ScaleY = scaleY };
                group.Children.Add(st);

                TranslateTransform tt = new TranslateTransform() { X = lastPoint.X, Y = lastPoint.Y };
                group.Children.Add(tt);

                RotateTransform rt = new RotateTransform() { Angle = edge * 90, CenterX = lastPoint.X, CenterY = lastPoint.Y };
                group.Children.Add(rt);

                for (int i = 0; i < PathNodes.Length / 3; i++)
                {
                    BezierSegment seg = new BezierSegment();
                    seg.Point1 = group.Transform(PathNodes[i * 3]);
                    seg.Point2 = group.Transform(PathNodes[i * 3 + 1]);
                    seg.Point3 = group.Transform(PathNodes[i * 3 + 2]);

                    figur.Segments.Add(seg);
                    lastPoint = seg.Point3;
                }
            }
            return pathGeometry;
        }


CreateJigsawPuzzlePathGrometry 有三個參數:
startPoint - 我們將一張圖,以矩形劃分,每個矩形的左上角,距離圖片左上角的位置,即是每一片小拼塊開始進行切割的 startPoint
pieceSize - 指劃分圖片矩形大小
orientation - 由於鄰近的小拼塊,凹凸位置剛好相反,orientation 應交互變換



Clip Image

我們利用上列方法所產生的 PathGrometry 來進行圖片切割的工作,並讓切割後的圖案成為獨立的 element,並有一致的原始點位置,方便使用者移動與操作。
Silverlight 並沒有 sprite 的功能,ImageBrush.ImageSource 也沒辦法設定位移與範圍,所以我利用了 WriteableBitmap 來達成這項工作。

   
        private Canvas CreatePiece(BitmapImage bitmap, Point startPoint, Size pieceSize, EdgeOrientation orientation)
        {
            Image imageTemp = new Image();
            imageTemp.Source = bitmap;
            imageTemp.Clip = CreateJigsawPuzzlePathGrometry(startPoint, pieceSize, orientation);

            Size panelSize = new Size(pieceSize.Width * 2, pieceSize.Height * 2);
            WriteableBitmap wb = new WriteableBitmap((int)panelSize.Width, (int)panelSize.Height);
            TranslateTransform translate = new TranslateTransform() { X = -startPoint.X + pieceSize.Width / 2, Y = -startPoint.Y + pieceSize.Height / 2 };
            wb.Render(imageTemp, translate);
            wb.Invalidate();

            Image image = new Image();
            image.Source = wb;
            Canvas.SetLeft(image, -pieceSize.Width / 2);
            Canvas.SetTop(image, -pieceSize.Height / 2);

            Canvas canvas = new Canvas();
            canvas.Children.Add(image);

            Path path = new Path() { StrokeThickness = 2, Stroke = new SolidColorBrush(Colors.Black) };
            path.Data = CreateJigsawPuzzlePathGrometry(startPoint, pieceSize, orientation);
            Canvas.SetLeft(path, -startPoint.X);
            Canvas.SetTop(path, -startPoint.Y);
            canvas.Children.Add(path);
            
            return canvas;
        }
     


不過,後續的事情還很多,只是技術上困難的部分大概都搞定了。切割時,還要考慮最外圍邊緣必須改為直線,而操作介面則是另一項複雜的學問,但我就不再談下去了。

2010年9月27日

Enumerate HierarchicalDataSource

ASP.NET 提供了很好的 SiteMap 機制,不過想以二維的方式來呈現 SiteMap,例如下列兩組 menu,分別以章節及索引不同的方式來條列,便需要利用到巡覽 DataSource 的技術了。

Home
Chapter One
Topic 1-1
Topic 1-2
Topic 1-3
Chapter Two
Topic 2-1
Topic 3-2
Index
A
Topic 1-2
C
Topic 1-1
Topic 2-2
N
Topic 1-3
Topic 2-1


我幫 Web.sitmap 增加了 index 的屬性,使巡覽時,可以依照 index 的第一個字母來分類。


    
      
      
        
        
        
        
      

      
        
        
      

    



下列程式,即是巡覽 XmlDataSource 的程式片段。
        public IEnumerable〈IGrouping〈char, XmlElement〉〉 CreateGroupingList(IDataSource dataSource)
        {
            List〈XmlElement〉 elementList = new List〈XmlElement〉();

            IHierarchicalDataSource hSource = dataSource as IHierarchicalDataSource;
            if (hSource != null)
            {
                HierarchicalDataSourceView hView = hSource.GetHierarchicalView(String.Empty);
                if (hView != null)
                {
                    IHierarchicalEnumerable hEnumerable = hView.Select();
                    ReadRecursive(hEnumerable, elementList);
                }
            }

            return elementList.OrderBy(p => p.Attributes["index"].Value).GroupBy(p => p.Attributes["index"].Value[0]);
        }


        private void ReadRecursive(IHierarchicalEnumerable enumerable, List〈XmlElement〉 elementList)
        {
            foreach (IHierarchyData item in enumerable)
            {
                if (item.Item is XmlElement)
                {
                    XmlElement element = item.Item as XmlElement;
                    if (element.Attributes["title"] != null && element.Attributes["index"] != null &&
                        element.Attributes["index"].Value.Length > 0)
                        elementList.Add(item.Item as XmlElement);
                }

                if (item.HasChildren)
                    ReadRecursive(item.GetChildren(), elementList);
            }
        }

2010年9月26日

Best Practices for ASP.NET MVC

這是微軟 msdn 上的一篇文章,分別就 model, view, controller, routing, extensibility, testability, security, localization and globalization, performance, 建議應採取的設計方式。內容對於 mode, view, controller 的分工方式, 有很好的說明, 相信對於採用 MVC 的設計師, 會有很大的幫助。

以下,我簡單列出一些要點:

Model: 用來定義商業、資料、驗證與記錄使用者狀態 session 等邏輯,對於大型的系統,可以拆成不同的 assembly。

View: 不要把商業邏輯寫在 View 中,要有可讀性。可以利用 partial HTML view(.ascx)來建立階層結構。要利用 ViewData 方式讀取資料,以 server 端註解 <%-- --%>增加安全性,採用 HTML Helper 產生 form、checkbox、textbox..。

Controller: 針對 routing system、HTML resuest 來回應。使用 model binding 取代手動,以 Post/Redirect/Get 處理 submitting form。並要記得管理 HandleUnknownAction 與 HandleError等狀況。

[Reference]
Best Practices for ASP.NET MVC

2010年9月21日

Google AdSense 申請獲准

這個部落格才剛開始時,就馬上申請 AdSense,但是沒有通過。 記得回覆信件中提要,希望我能多增加一些內容,再來送件。 好長一段時間後,前兩天管理網站時,看到先前未獲准的訊息, 隨手再送出申請,沒想到過了! 只是以目前的流量,不知道十年後能不能領到第一張Google的支票。

2010年9月20日

Free Website Templates

這真的是造福大眾的網站,Free Website Templates 提供了免費的樣板,
包含圖檔、HTML 與 CSS,想設計網頁的人一定不能錯過。

CSS PX to EM Conversion

CSS 文字大小的設定方式有 px, em 與 % 三種,
px 較常見,不過是絕對單位,經常被建議不要使用。
em, % 是相對單位,相對於父物件之大小,使用時需要特別注意。

下列網站提供試算工具,可以幫助網友安排字體大小:PXtoEM.com

2010年9月16日

Embed HTML in Silverlight 的問題

將 silverlight 設定為 windowless 雖然可以順利以 floating 方式顯示 HTML 區塊,卻會造成中文輸入法失效的問題。由於公司入選台灣微軟BizsparkCamp 雲端/ Silverlight創新徵選會,很榮幸能就此問題向章立民老師請教。
章老師提到,使用 windowless 不是一個恰當的做法,可能衍生的不只是中文輸入的問題,應避免使用。文件內容以資料庫或 xml 存放,並依照 HTML 或 Silverlight 的不同,來採取最恰當的顯示方法,才是最佳的處理方式。
章老師的答案,讓我覺得寫程式真的偷懶不得!!
前一篇 Embed HTML in Silverlight

CSS Horizontal List

如果要將列表改為橫向,可以使用下列的語法:
HTML
 

CSS
 
#navlist li
{
display: inline;
list-style-type: none;
padding-right: 20px;
}
上列範例,節錄自:maxdesign

Deploying Vue & .NET with Google OAuth on GCP Cloud Run

Deploying Vue & .NET with Google OAuth on GCP Cloud Run Deploying Vue & .NET with Google OAuth on GCP Cloud Run...