顯示具有 Path 標籤的文章。 顯示所有文章
顯示具有 Path 標籤的文章。 顯示所有文章

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


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

2009年10月25日

Silverlight String to Path

由於 Silverlight 並沒有中文字型,所以要顯示中文字便比較麻煩。 如果文字的內容是固定,可以用圖形或將文字轉成 path 的方式處理, 若文字是變動性的,甚至是由 user 來輸入, 就必須建立 WCF 由 server 端來處理了。 以下是 Server 端, 將 string 轉成 path 並傳回的方法:
public string GetFont
  (string str, string fontFamily, int nSize)
{
   Typeface typeface = 
       new Typeface(new FontFamily(fontFamily),
       FontStyles.Normal, FontWeights.Normal, 
       FontStretches.Normal);

   FormattedText text = new FormattedText(str,
       new System.Globalization.CultureInfo("zh-tw"),
       FlowDirection.LeftToRight, typeface, nSize,
       Brushes.Black);

   Geometry geo = text.BuildGeometry(new Point(0, 0));
      
   PathGeometry path = geo.GetFlattenedPathGeometry();
   string data = path.ToString();

   string xamlTriangle = "<Path xmlns='http://schemas.microsoft.com/" + 
          "winfx/2006/xaml/presentation' " +
     "xmlns:x='http://schemas.microsoft.com/" +
          "winfx/2006/xaml' " +
     "Data='" + data + "' Fill='LightBlue' " + 
     "Stretch='Fill'/>";

   return xamlTriangle;
}
而 Client 的程式如下:
void client_StringToPathCompleted(object sender,
 DoWorkCompletedEventArgs e)
{

 Path newPath = (Path)
  System.Windows.Markup.XamlReader.Load(e.Result);
 this.LayoutRoot.Children.Add(newPath);

}
[Reference]
以 WPF + AJAX 在執行期間將文字轉成 Path
In SL3.0, can you not just use XamlReader.Load ?

2009年6月20日

Silverlight BezierSegment

最近打算將照片製作成拼圖,其中拼圖中的切割區線,必須使用到 Bezier 曲線。下面這個網頁,可以得到 BezierSegment 的使用範例。
順便一提,這個範例蠻有趣,以逐漸上升的水量,以及波浪動畫來呈現百分比,這讓我聯想到 loading 或遊戲中生命值的表現方式。

BezierSegment 以前一個 segment 為起點,以 Point3 為終點,而 Point1 與 Point2 則為曲度的控制點。

[Reference]
Animating A Silverlight BezierSegment: http://kemmis.info/blog/archive/2009/05/16/animating-a-silverlight-beziersegment.aspx

2009年5月3日

Create a Path at Rutime

我需要利用 Silverlight 設計裁切照片的工具,
例如,讓 user 框選照片中的大頭,裁切下來,並加以應用。
因此,在 runtime 建立一組 path,
並隨著 user 的 click 新增 node,
是一項重要的流程。

以下是我利用 path 畫出一個正方形的範例,
並以 canvas 為其父物件。為了讓範例簡化,
click、clip、image 等部分的處理,並沒有加入範例中。

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(0, 0);

LineSegment seg1 = new LineSegment();
seg1.Point = new Point(100, 0);
figur.Segments.Add(seg1);

LineSegment seg2 = new LineSegment();
seg2.Point = new Point(100, 100);
figur.Segments.Add(seg2);

LineSegment seg3 = new LineSegment();
seg3.Point = new Point(0, 100);
figur.Segments.Add(seg3);

path.Fill = new SolidColorBrush(Colors.Blue);
Canvas.SetTop(path, 200);
Canvas.SetLeft(path, 300);

Canvas canvas = new Canvas();
canvas.Background = new SolidColorBrush(Colors.DarkGray);
canvas.Width = 50;
canvas.Height = 50;
canvas.HorizontalAlignment = HorizontalAlignment.Left;
canvas.VerticalAlignment = VerticalAlignment.Top;
canvas.Children.Add(path);

this.LayoutRoot.Children.Add(canvas);
[refernece]
How to draw a path at runtime

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