- 首先,我們可以想像將照片切分成大小相同的矩形。
- 接著將矩形的四個邊,轉換成拼圖所特有的曲線。
- 由於曲線的兩端固定在矩形的頂點上,利用亂數改變曲線的線條,便可以產生每個形狀都有些微差異的拼塊了。
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;
}
不過,後續的事情還很多,只是技術上困難的部分大概都搞定了。切割時,還要考慮最外圍邊緣必須改為直線,而操作介面則是另一項複雜的學問,但我就不再談下去了。




沒有留言:
張貼留言