[update] Perfect-freehand 更改
This commit is contained in:
parent
490f48221f
commit
bfa1936794
@ -3,13 +3,246 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using InkCanvasForClassX.Libraries;
|
||||
using InkCanvasForClassX.Libraries.Stroke;
|
||||
|
||||
namespace InkCanvasForClassX.Libraries
|
||||
{
|
||||
/// <summary>
|
||||
/// 提供對<c>steveruizok/perfect-freehand</c>的C#包裝
|
||||
/// 提供對JS庫<c>steveruizok/perfect-freehand</c>的C#包裝
|
||||
/// </summary>
|
||||
public class PerfectFreehand {
|
||||
|
||||
/// <summary>
|
||||
/// Get an array of points as objects with an adjusted point, pressure, vector, distance, and runningLength.
|
||||
/// </summary>
|
||||
/// <param name="points">原注釋: An array of points (as `[x, y, pressure]` or `{x, y, pressure}`). Pressure is optional in both cases. 請使用<c>StylusPointCollection</c></param>
|
||||
/// <param name="options">An object with options.</param>
|
||||
/// <returns></returns>
|
||||
public static StrokePoint[] GetStrokePoints(StylusPointCollection points,
|
||||
StrokeOptions options) {
|
||||
var streamline = options.Streamline ?? 0.5;
|
||||
var size = options.Size ?? 16;
|
||||
var isComplete = options.Last ?? false;
|
||||
|
||||
// If we don't have any points, return an empty array.
|
||||
if (points.Count == 0) return Array.Empty<StrokePoint>();
|
||||
|
||||
// Find the interpolation level between points.
|
||||
double t = 0.15 + (1 - streamline) * 0.85;
|
||||
|
||||
// Purify the StylusPointCollection
|
||||
var pts = new StylusPointCollection();
|
||||
foreach (var stylusPoint in points) {
|
||||
pts.Add(new StylusPoint(stylusPoint.X, stylusPoint.Y, stylusPoint.PressureFactor));
|
||||
}
|
||||
|
||||
// Add extra points between the two, to help avoid "dash" lines
|
||||
// for strokes with tapered start and ends. Don't mutate the
|
||||
// input array!
|
||||
if (points.Count == 2) {
|
||||
var last = pts[1];
|
||||
pts.RemoveAt(pts.Count - 1);
|
||||
for (var i = 1; i < 5; i++) {
|
||||
var _vec = Vector.InterpolateVectors(
|
||||
new Vector(pts[0].X, pts[0].Y),
|
||||
new Vector(last.X, last.Y),
|
||||
i / 4
|
||||
);
|
||||
pts.Add(new StylusPoint(_vec.X, _vec.Y));
|
||||
}
|
||||
}
|
||||
|
||||
// If there's only one point, add another point at a 1pt offset.
|
||||
// Don't mutate the input array!
|
||||
if (pts.Count == 1) {
|
||||
var onePt = new Vector(pts[0].X + 1, pts[0].Y + 1);
|
||||
pts.Add(new StylusPoint(onePt.X, onePt.Y, pts[0].PressureFactor));
|
||||
}
|
||||
|
||||
// The strokePoints array will hold the points for the stroke.
|
||||
// Start it out with the first point, which needs no adjustment.
|
||||
var strokePoints = new List<StrokePoint>() {
|
||||
new StrokePoint() {
|
||||
Point = new Vector(pts[0].X, pts[0].Y),
|
||||
Pressure = pts[0].PressureFactor >= 0 ? pts[0].PressureFactor : 0.25,
|
||||
Vector = new Vector(1, 1),
|
||||
Distance = 0,
|
||||
RunningLength = 0,
|
||||
}
|
||||
};
|
||||
|
||||
// A flag to see whether we've already reached out minimum length
|
||||
var hasReachedMinimumLength = false;
|
||||
|
||||
// We use the runningLength to keep track of the total distance
|
||||
double runningLength = 0;
|
||||
|
||||
// We're set this to the latest point, so we can use it to calculate
|
||||
// the distance and vector of the next point.
|
||||
var prev = strokePoints[0];
|
||||
|
||||
// const max = pts.length - 1
|
||||
var max = pts.Count - 1;
|
||||
|
||||
// Iterate through all of the points, creating StrokePoints.
|
||||
for (var i = 1; i < pts.Count; i++) {
|
||||
var point = isComplete && i == max
|
||||
? // If we're at the last point, and `options.last` is true,
|
||||
// then add the actual input point.
|
||||
new Vector(pts[i].X, pts[i].Y)
|
||||
: // Otherwise, using the t calculated from the streamline
|
||||
// option, interpolate a new point between the previous
|
||||
// point the current point.
|
||||
Vector.InterpolateVectors(prev.Point, new Vector(pts[i].X, pts[i].Y), t);
|
||||
|
||||
// If the new point is the same as the previous point, skip ahead.
|
||||
if (prev.Point.IsEqual(point)) continue;
|
||||
|
||||
// How far is the new point from the previous point?
|
||||
var distance = Vector.DistLengthVectors(point, prev.Point);
|
||||
|
||||
// Add this distance to the total "running length" of the line.
|
||||
runningLength += distance;
|
||||
|
||||
// At the start of the line, we wait until the new point is a
|
||||
// certain distance away from the original point, to avoid noise
|
||||
if (i < max && !hasReachedMinimumLength) {
|
||||
if (runningLength < size) continue;
|
||||
hasReachedMinimumLength = true;
|
||||
// TODO: Backfill the missing points so that tapering works correctly.
|
||||
}
|
||||
|
||||
// Create a new strokepoint (it will be the new "previous" one).
|
||||
prev = new StrokePoint() {
|
||||
// The adjusted point
|
||||
Point = point,
|
||||
// The input pressure (or .5 if not specified)
|
||||
Pressure = pts[i].PressureFactor >= 0 ? pts[i].PressureFactor : 0.5,
|
||||
// The vector from the current point to the previous point
|
||||
Vector = Vector.UnitVector(Vector.SubtractVectors(prev.Point, point)),
|
||||
// The distance between the current point and the previous point
|
||||
Distance = distance,
|
||||
// The total distance so far
|
||||
RunningLength = runningLength,
|
||||
};
|
||||
|
||||
// Push it to the strokePoints array.
|
||||
strokePoints.Add(prev);
|
||||
}
|
||||
|
||||
// Set the vector of the first point to be the same as the second point.
|
||||
strokePoints[0].Vector = strokePoints[1]?.Vector ?? new Vector(0, 0);
|
||||
|
||||
return strokePoints.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compute a radius based on the pressure.
|
||||
/// </summary>
|
||||
public static double GetStrokeRadius(
|
||||
double size,
|
||||
double thinning,
|
||||
double pressure,
|
||||
Func<double, double> easing = null)
|
||||
{
|
||||
if (easing == null) {
|
||||
easing = t => t; // 默認的 easing 函數
|
||||
}
|
||||
return size * easing(0.5 - thinning * (0.5 - pressure));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Stroke {
|
||||
public class StrokeOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The base size (diameter) of the stroke.
|
||||
/// </summary>
|
||||
public double? Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The effect of pressure on the stroke's size.
|
||||
/// </summary>
|
||||
public double? Thinning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// How much to soften the stroke's edges.
|
||||
/// </summary>
|
||||
public double? Smoothing { get; set; }
|
||||
public double? Streamline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An easing function to apply to each point's pressure.
|
||||
/// </summary>
|
||||
public Func<double, double> Easing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to simulate pressure based on velocity.
|
||||
/// </summary>
|
||||
public bool? SimulatePressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cap, taper and easing for the start of the line.
|
||||
/// </summary>
|
||||
public StrokeCapOptions Start { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cap, taper and easing for the end of the line.
|
||||
/// </summary>
|
||||
public StrokeCapOptions End { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to handle the points as a completed stroke.
|
||||
/// </summary>
|
||||
public bool? Last { get; set; }
|
||||
}
|
||||
|
||||
public class StrokeCapOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to apply a cap at the start/end of the line.
|
||||
/// </summary>
|
||||
public bool? Cap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The taper value at the start/end of the line.
|
||||
/// </summary>
|
||||
public double? Taper { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An easing function to apply to the taper.
|
||||
/// </summary>
|
||||
public Func<double, double> Easing { get; set; }
|
||||
}
|
||||
|
||||
public class StrokePoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The point coordinates as [x, y].
|
||||
/// </summary>
|
||||
public Vector Point { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The pressure at the point.
|
||||
/// </summary>
|
||||
public double Pressure { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The distance from the previous point.
|
||||
/// </summary>
|
||||
public double Distance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The vector at the point.
|
||||
/// </summary>
|
||||
public Vector Vector { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The running length of the stroke.
|
||||
/// </summary>
|
||||
public double RunningLength { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,7 +190,7 @@ namespace InkCanvasForClassX.Libraries
|
||||
/// <summary>
|
||||
/// 獲取一個向量和另一個向量的平方距離
|
||||
/// </summary>
|
||||
public double DistLengthSquaredVectors(Vector vec1, Vector vec2)
|
||||
public static double DistLengthSquaredVectors(Vector vec1, Vector vec2)
|
||||
{
|
||||
var subVec = Vector.SubtractVectors(vec1, vec2);
|
||||
return subVec.LengthSquared;
|
||||
@ -206,7 +206,7 @@ namespace InkCanvasForClassX.Libraries
|
||||
/// <summary>
|
||||
/// 獲取一個向量和另一個向量的距離
|
||||
/// </summary>
|
||||
public double DistLengthVectors(Vector vec1, Vector vec2)
|
||||
public static double DistLengthVectors(Vector vec1, Vector vec2)
|
||||
{
|
||||
return Vector.Hypot(vec1.Y - vec2.Y, vec1.X - vec2.X);
|
||||
}
|
||||
@ -266,22 +266,49 @@ namespace InkCanvasForClassX.Libraries
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 將該向量與另一個向量插值,TODO
|
||||
/// 將該向量與另一個向量插值
|
||||
/// </summary>
|
||||
public Vector Interpolate(Vector vec) {
|
||||
throw new NotImplementedException();
|
||||
public Vector Interpolate(Vector vec, double t) {
|
||||
Add(SubtractVectors(vec, this).Multiply(t));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 將一個向量與另一個向量插值,TODO
|
||||
/// 將一個向量與另一個向量插值
|
||||
/// </summary>
|
||||
public static Vector InterpolateVectors(Vector vec1, Vector vec2)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return new Vector();
|
||||
public static Vector InterpolateVectors(Vector vec1, Vector vec2, double t) {
|
||||
return AddVectors(vec1, SubtractVectors(vec2, vec1).Multiply(t));
|
||||
}
|
||||
|
||||
// TODO: pjr();
|
||||
/// <summary>
|
||||
/// 將該向量投影在向量<paramref name="vec"/>的方向上,並附加距離<paramref name="c"/>
|
||||
/// </summary>
|
||||
public Vector Project(Vector vec, double c) {
|
||||
Add(vec.Multiply(c));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 將<paramref name="vec1"/>投影在向量<paramref name="vec2"/>的方向上,並附加距離<paramref name="c"/>
|
||||
/// </summary>
|
||||
public static Vector ProjectVectors(Vector vec1, Vector vec2, double c) {
|
||||
return AddVectors(vec1, MultiplyVector(vec2,c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得單位向量
|
||||
/// </summary>
|
||||
public Vector Unit()
|
||||
{
|
||||
return DivideByVector(this, Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得單位向量
|
||||
/// </summary>
|
||||
public static Vector UnitVector(Vector vec)
|
||||
{
|
||||
return DivideByVector(vec, vec.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user