//------------------变形方法和其辅助函数 End----------------------------------------- // 三种变形的并行版本 // 变换思路和代码基本同前,只将为目标图每个格点赋像素值的步骤进行并行,即每个格点同时 // 计算其在原图中对应的坐标,并查找像素信息 // 故只将串行的两重for,转换为并行的循环 public static byte[,,] RotateTwistImgParallel(byte[,,] img, InterpolationFunc interpolationFunc, double maxAngle, double radius, double centerX, double centerY) { int height = img.GetLength(0); int width = img.GetLength(1); byte[,,] imgTwisted = new byte[height, width, img.GetLength(2)]; System.Threading.Tasks.Parallel.For(0, height * width, index => { int i = index / width; int j = index % width; double distance = Math.Sqrt((i - centerX) * (i - centerX) + (j - centerY) * (j - centerY)); if (distance > radius) { for (int channel = 0; channel < 3; channel++) { imgTwisted[i, j, channel] = img[i, j, channel]; } return; } double angle = maxAngle * (radius - distance) / radius; double x = Math.Cos(angle) * (i - centerX) - Math.Sin(angle) * (j - centerY) + centerX; double y = Math.Sin(angle) * (i - centerX) + Math.Cos(angle) * (j - centerY) + centerY; for (int channel = 0; channel < 3; channel++) { imgTwisted[i, j, channel] = interpolationFunc(img, x, y, channel); } }); return(imgTwisted); }
//------------------插值方法 End----------------------------------------- // 变形方法 参数格式byte[,,]img, InterpolationFunc interpolationFunc, 其他变形相关参数 返回值byte[,,] // maxAngle弧度制,在前端中,为了用户友好,规定写入角度值,并且由前端完成角度弧度转换这样的简单逻辑 public static byte[,,] RotateTwistImg(byte[,,] img, InterpolationFunc interpolationFunc, double maxAngle, double radius, double centerX, double centerY) { int height = img.GetLength(0); int width = img.GetLength(1); byte[,,] imgTwisted = new byte[height, width, img.GetLength(2)]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { // 按照变化公式,计算目标图(i,j)应到原图哪个位置取像素信息 double distance = Math.Sqrt((i - centerX) * (i - centerX) + (j - centerY) * (j - centerY)); if (distance > radius) { for (int channel = 0; channel < 3; channel++) { imgTwisted[i, j, channel] = img[i, j, channel]; } continue; } // 原始公式 angle = maxAngle * (radius - distance) / radius, 化简防止radius distance差距过大大数吃小数 double angle = maxAngle * (1 - distance / radius); double x = Math.Cos(angle) * (i - centerX) - Math.Sin(angle) * (j - centerY) + centerX; double y = Math.Sin(angle) * (i - centerX) + Math.Cos(angle) * (j - centerY) + centerY; for (int channel = 0; channel < 3; channel++) { imgTwisted[i, j, channel] = interpolationFunc(img, x, y, channel); } } } return(imgTwisted); }
public TransformBindable(Bindable <TValue> targetBindable, InterpolationFunc <TValue> interpolationFunc) { this.targetBindable = targetBindable; this.interpolationFunc = interpolationFunc ?? Interpolation <TValue> .ValueAt; TargetMember = $"{targetBindable.GetHashCode()}.Value"; }
static GenericInterpolation() { const string interpolation_method = nameof(GenericInterpolation <TEasing> .ValueAt); var parameters = typeof(InterpolationFunc <TValue, TEasing>) .GetMethod(nameof(InterpolationFunc <TValue, TEasing> .Invoke)) ?.GetParameters().Select(p => p.ParameterType).ToArray(); MethodInfo valueAtMethod = typeof(GenericInterpolation <TEasing>).GetMethod(interpolation_method, parameters); if (valueAtMethod != null) { FUNCTION = (InterpolationFunc <TValue, TEasing>)valueAtMethod.CreateDelegate(typeof(InterpolationFunc <TValue, TEasing>)); } else { var typeRef = FormatterServices.GetSafeUninitializedObject(typeof(TValue)) as IInterpolable <TValue>; if (typeRef == null) { throw new NotSupportedException($"Type {typeof(TValue)} has no interpolation function. Implement the interface {typeof(IInterpolable<TValue>)} interface on the object."); } FUNCTION = typeRef.ValueAt; } }
public TransformBindable(Bindable <TValue> targetBindable) { this.targetBindable = targetBindable; interpolationFunc = Interpolation.ValueAt; TargetMember = $"{targetBindable.GetHashCode()}.Value"; }
/// <summary> /// Creates a new instance operating on a property or field of <typeparamref name="T"/>. The property or field is /// denoted by its name, passed as <paramref name="propertyOrFieldName"/>. /// By default, an interpolation method "ValueAt" from <see cref="Interpolation"/> with suitable signature is /// picked for interpolating between <see cref="Transform{TValue}.StartValue"/> and /// <see cref="Transform{TValue}.EndValue"/> according to <see cref="Transform.StartTime"/>, /// <see cref="Transform.EndTime"/>, and a current time. /// </summary> /// <param name="propertyOrFieldName">The property or field name to be operated upon.</param> public TransformCustom(string propertyOrFieldName) { TargetMember = propertyOrFieldName; accessor = getAccessor(propertyOrFieldName); Trace.Assert(accessor.Read != null && accessor.Write != null, $"Failed to populate {nameof(accessor)}."); interpolationFunc = Interpolation.ValueAt; }
static TransformCustom() { interpolation_func = (InterpolationFunc<TValue>)typeof(Interpolation).GetMethod( nameof(Interpolation.ValueAt), typeof(InterpolationFunc<TValue>) .GetMethod(nameof(InterpolationFunc<TValue>.Invoke)) .GetParameters().Select(p => p.ParameterType).ToArray() )?.CreateDelegate(typeof(InterpolationFunc<TValue>)); }
public TransformBindable(Bindable <TValue> targetBindable) { this.targetBindable = targetBindable; // Lambda expression is used so that the delegate is cached (see: https://github.com/dotnet/roslyn/issues/5835) interpolationFunc = (double d, TValue value, TValue tValue, double time, double endTime, in TEasing type) => Interpolation.ValueAt(d, value, tValue, time, endTime, in type); TargetMember = $"{targetBindable.GetHashCode()}.Value"; }
/// <summary> /// Creates a new instance operating on a property or field of <typeparamref name="T"/>. The property or field is /// denoted by its name, passed as <paramref name="propertyOrFieldName"/>. /// By default, an interpolation method "ValueAt" from <see cref="Interpolation"/> with suitable signature is /// picked for interpolating between <see cref="Transform{TValue}.StartValue"/> and /// <see cref="Transform{TValue}.EndValue"/> according to <see cref="Transform.StartTime"/>, /// <see cref="Transform.EndTime"/>, and a current time. /// </summary> /// <param name="propertyOrFieldName">The property or field name to be operated upon.</param> public TransformCustom(string propertyOrFieldName) { TargetMember = propertyOrFieldName; accessor = getAccessor(propertyOrFieldName); Trace.Assert(accessor.Read != null && accessor.Write != null, $"Failed to populate {nameof(accessor)}."); // Lambda expression is used so that the delegate is cached (see: https://github.com/dotnet/roslyn/issues/5835) interpolationFunc = (double d, TValue value, TValue tValue, double time, double endTime, in TEasing type) => Interpolation.ValueAt(d, value, tValue, time, endTime, in type); }
/// <summary> /// Creates a new instance operating on a property or field of <see cref="T"/>. The property or field is /// denoted by its name, passed as <paramref name="propertyOrFieldName"/>. /// By default, an interpolation method "ValueAt" from <see cref="Interpolation"/> with suitable signature is /// picked for interpolating between <see cref="Transform{TValue}.StartValue"/> and /// <see cref="Transform{TValue}.EndValue"/> according to <see cref="Transform.StartTime"/>, /// <see cref="Transform.EndTime"/>, and a current time. /// Optionally, or when no suitable "ValueAt" from <see cref="Interpolation"/> exists, a custom function can be supplied /// via <paramref name="interpolationFunc"/>. /// </summary> /// <param name="propertyOrFieldName">The property or field name to be operated upon.</param> /// <param name="interpolationFunc"> /// The function to be used for interpolating between <see cref="Transform{TValue}.StartValue"/> and /// <see cref="Transform{TValue}.EndValue"/> according to <see cref="Transform.StartTime"/>, /// <see cref="Transform.EndTime"/>, and a current time. /// If null, an interpolation method "ValueAt" from <see cref="Interpolation"/> with a suitable signature is picked. /// If none exists, then this parameter must not be null. /// </param> public TransformCustom(string propertyOrFieldName, InterpolationFunc<TValue> interpolationFunc = null) { TargetMember = propertyOrFieldName; accessor = getAccessor(propertyOrFieldName); Trace.Assert(accessor.Read != null && accessor.Write != null, $"Failed to populate {nameof(accessor)}."); this.interpolationFunc = interpolationFunc ?? interpolation_func; if (this.interpolationFunc == null) throw new InvalidOperationException( $"Need to pass a custom {nameof(interpolationFunc)} since no default {nameof(Interpolation)}.{nameof(Interpolation.ValueAt)} exists."); }
public static void DrawCatmullPath(List<Transform> points, InterpolationFunc sampler, int samples) { if (!PreDrawPath(points, 4)) return; float sampleInterval = 1.0f / (float)samples; int p0, p1, p2, p3; for (int i = 0; i < points.Count; ++i) { p1 = i; p0 = p1 - 1; if (p0 < 0) p0 = points.Count - 1; p2 = p1 + 1; if (p2 >= points.Count) p2 = 0; p3 = p2 + 1; if (p3 >= points.Count) p3 = 0; Vector3 sample, prevSample; prevSample = points[p1].position; for (int j = 1; j <= samples; ++j) { float t = j * sampleInterval; sample = sampler(points[p0].position, points[p1].position, points[p2].position, points[p3].position, t); Handles.DrawLine(prevSample, sample); prevSample = sample; } } }
public static byte[,,] TPSImgParallel(byte[,,] img, InterpolationFunc interpolationFunc, double[,] pointPair) { // 置矩阵,高斯消元解系数同串行版本 if (pointPair.GetLength(1) != 4) { return(null); // Error pointPair } int pointNumber = pointPair.GetLength(0); double[,] Y = new double[pointNumber + 3, 2]; for (var i = 0; i < pointNumber; i++) { Y[i, 0] = pointPair[i, 2]; Y[i, 1] = pointPair[i, 3]; } double[,] L = new double[pointNumber + 3, pointNumber + 3]; for (var j = 0; j < pointNumber; j++) { L[pointNumber, j] = 1; L[j, pointNumber] = 1; } for (var i = pointNumber + 1; i < pointNumber + 3; i++) { for (var j = 0; j < pointNumber; j++) { L[i, j] = pointPair[j, i - pointNumber - 1]; L[j, i] = L[i, j]; } } for (var i = 0; i < pointNumber; i++) { for (var j = 0; j <= i; j++) { if (j == i) { L[i, j] = 0; break; } L[i, j] = RadialbasisByPoint(pointPair[i, 0], pointPair[i, 1], pointPair[j, 0], pointPair[j, 1]); L[j, i] = L[i, j]; } } // 传入高斯消元函数的为引用,会被函数体修改矩阵值,原本应使用 // double[,] coe = GaussianElimination(L.Clone(), Y.Clone()); // 但LY不再使用,故为节约耗时,不clone double[,] coe = GaussianElimination(L, Y); if (coe == null) { return(null); } // 以下开始并行 int height = img.GetLength(0); int width = img.GetLength(1); byte[,,] imgTPSed = new byte[height, width, 3]; System.Threading.Tasks.Parallel.For(0, height * width, index => { double x; double y; int i = index / width; int j = index % width; x = coe[pointNumber, 0] + coe[pointNumber + 1, 0] * i + coe[pointNumber + 2, 0] * j; y = coe[pointNumber, 1] + coe[pointNumber + 1, 1] * i + coe[pointNumber + 2, 1] * j; double[,] weightedUArray = new double[pointNumber, 4]; // 分正负相消累计,防止大数吃小数 double u; double weightedUX; double weightedUY; int posUXcount = 0; int negUXcount = 0; int posUYcount = 0; int negUYcount = 0; for (var wCount = 0; wCount < pointNumber; wCount++) { u = RadialbasisByPoint(i, j, pointPair[wCount, 0], pointPair[wCount, 1]); weightedUX = u * coe[wCount, 0]; weightedUY = u * coe[wCount, 1]; if (weightedUX > 0) { weightedUArray[posUXcount, 0] = weightedUX; posUXcount++; } else { weightedUArray[negUXcount, 1] = weightedUX; negUXcount++; } if (weightedUY > 0) { weightedUArray[posUYcount, 2] = weightedUY; posUYcount++; } else { weightedUArray[negUYcount, 3] = weightedUY; negUYcount++; } } posUXcount--; posUYcount--; negUXcount--; negUYcount--; while (posUXcount >= 0 && negUXcount >= 0) { if (x > 0) { x += weightedUArray[negUXcount, 1]; negUXcount--; } else { x += weightedUArray[posUXcount, 0]; posUXcount--; } } while (posUXcount >= 0) { x += weightedUArray[posUXcount, 0]; posUXcount--; } while (negUXcount >= 0) { x += weightedUArray[negUXcount, 1]; negUXcount--; } while (posUYcount >= 0 && negUYcount >= 0) { if (y > 0) { y += weightedUArray[negUYcount, 3]; negUYcount--; } else { y += weightedUArray[posUYcount, 2]; posUYcount--; } } while (posUYcount >= 0) { y += weightedUArray[posUYcount, 2]; posUYcount--; } while (negUYcount >= 0) { y += weightedUArray[negUYcount, 3]; negUYcount--; } if (x < 0 || x >= height || y < 0 || y > width) { return; } for (var channel = 0; channel < 3; channel++) { imgTPSed[i, j, channel] = interpolationFunc(img, x, y, channel); } }); return(imgTPSed); }
public static byte[,,] DistortImgParallel(byte[,,] img, InterpolationFunc interpolationFunc, double radius, double centerX, double centerY, bool isAdjustPillowDistort) { int height = img.GetLength(0); int width = img.GetLength(1); byte[,,] imgDistorted = new byte[height, width, img.GetLength(2)]; if (isAdjustPillowDistort) // 校正枕形畸变,即对图像做桶形畸变 { System.Threading.Tasks.Parallel.For(0, height * width, index => { int i = index / width; int j = index % width; double distance = Math.Sqrt((i - centerX) * (i - centerX) + (j - centerY) * (j - centerY)); if (distance > radius) { return; // 投影球外的点,置为黑 0,0,0,即默认值 } double k; if (distance / radius < 0.001) { k = 1; // k 极小时 } else { k = radius / distance * Math.Asin(distance / radius); } // 为了精确快速调整计算顺序,把原始公式在注释给出 double x = k * i + (1 - k) * centerX; // 原始公式 k * (i - centerX) + centerX double y = k * j + (1 - k) * centerY; // 原始公式 k * (i - centerX) + centerX if (x >= height || x < 0 || y >= width || y < 0) { return; } for (int channel = 0; channel < 3; channel++) { imgDistorted[i, j, channel] = interpolationFunc(img, x, y, channel); } }); } else // 校正桶形畸变,即对图像做枕形畸变 { System.Threading.Tasks.Parallel.For(0, height * width, index => { int i = index / width; int j = index % width; double distance = Math.Sqrt((i - centerX) * (i - centerX) + (j - centerY) * (j - centerY)); if (distance > Math.PI / 2 * radius) { return; // 投影球外的点,置为黑 0,0,0,即默认值 } double k; if (distance / radius < 0.001) { k = 1; // k 极小时 } else { k = Math.Sin(distance / radius) * radius / distance; } // 为了精确快速调整计算顺序,把原始公式在注释给出 double x = k * i + (1 - k) * centerX; // 原始公式 k * (i - centerX) + centerX double y = k * j + (1 - k) * centerY; // 原始公式 k * (i - centerX) + centerX if (x >= height || x < 0 || y >= width || y < 0) { return; } for (int channel = 0; channel < 3; channel++) { imgDistorted[i, j, channel] = interpolationFunc(img, x, y, channel); } }); } return(imgDistorted); }
// pointPair[i,0] ~ [i,3] 分别为 控制点x1 控制点y1 目标点x2 目标点y2,控制点:想变成的参考样貌;目标点:待变形图上的坐标 public static byte[,,] TPSImg(byte[,,] img, InterpolationFunc interpolationFunc, double[,] pointPair) { if (pointPair.GetLength(1) != 4) { return(null); // Error pointPair } int pointNumber = pointPair.GetLength(0); // 构造矩阵Y double[,] Y = new double[pointNumber + 3, 2]; for (var i = 0; i < pointNumber; i++) { Y[i, 0] = pointPair[i, 2]; Y[i, 1] = pointPair[i, 3]; } // 构造矩阵L,注意到L为对称阵,赋值时,循环体可以在第二重循环时减半循环量 double[,] L = new double[pointNumber + 3, pointNumber + 3]; for (var j = 0; j < pointNumber; j++) { L[pointNumber, j] = 1; L[j, pointNumber] = 1; } for (var i = pointNumber + 1; i < pointNumber + 3; i++) { for (var j = 0; j < pointNumber; j++) { L[i, j] = pointPair[j, i - pointNumber - 1]; L[j, i] = L[i, j]; } } for (var i = 0; i < pointNumber; i++) { for (var j = 0; j <= i; j++) { if (j == i) { L[i, j] = 0; break; } L[i, j] = RadialbasisByPoint(pointPair[i, 0], pointPair[i, 1], pointPair[j, 0], pointPair[j, 1]); L[j, i] = L[i, j]; } } // 传入高斯消元函数的为引用,会被函数体修改矩阵值,原本应使用 // double[,] coe = GaussianElimination(L.Clone(), Y.Clone()); // 但LY不再使用,故为节约耗时,不clone double[,] coe = GaussianElimination(L, Y); if (coe == null) { return(null); // 方程无解,返回空图 } int height = img.GetLength(0); int width = img.GetLength(1); byte[,,] imgTPSed = new byte[height, width, 3]; double x; double y; for (var i = 0; i < height; i++) { for (var j = 0; j < width; j++) { // 先计算 wU之外的项目 x = coe[pointNumber, 0] + coe[pointNumber + 1, 0] * i + coe[pointNumber + 2, 0] * j; y = coe[pointNumber, 1] + coe[pointNumber + 1, 1] * i + coe[pointNumber + 2, 1] * j; // 分别计算各wU的结果,按正负号存入数组中,累加时, // 保证xy和新作为加项的wU正负异号 // 则整体加法流程充分地让正负相消累计,每一步得到的数的绝对值较小, // 防止double型在位数提高时新引入舍入误差 double[,] weightedUArray = new double[pointNumber, 4]; double u; double weightedUX; double weightedUY; int posUXcount = 0; int negUXcount = 0; int posUYcount = 0; int negUYcount = 0; // 计算wU并按正负号存储 for (var wCount = 0; wCount < pointNumber; wCount++) { u = RadialbasisByPoint(i, j, pointPair[wCount, 0], pointPair[wCount, 1]); weightedUX = u * coe[wCount, 0]; weightedUY = u * coe[wCount, 1]; if (weightedUX > 0) { weightedUArray[posUXcount, 0] = weightedUX; posUXcount++; } else { weightedUArray[negUXcount, 1] = weightedUX; negUXcount++; } if (weightedUY > 0) { weightedUArray[posUYcount, 2] = weightedUY; posUYcount++; } else { weightedUArray[negUYcount, 3] = weightedUY; negUYcount++; } } posUXcount--; posUYcount--; negUXcount--; negUYcount--; // 分正负,尽可能异号累加得到x while (posUXcount >= 0 && negUXcount >= 0) { if (x > 0) { x += weightedUArray[negUXcount, 1]; negUXcount--; } else { x += weightedUArray[posUXcount, 0]; posUXcount--; } } while (posUXcount >= 0) { x += weightedUArray[posUXcount, 0]; posUXcount--; } while (negUXcount >= 0) { x += weightedUArray[negUXcount, 1]; negUXcount--; } // 分正负,尽可能异号累加得到y while (posUYcount >= 0 && negUYcount >= 0) { if (y > 0) { y += weightedUArray[negUYcount, 3]; negUYcount--; } else { y += weightedUArray[posUYcount, 2]; posUYcount--; } } while (posUYcount >= 0) { y += weightedUArray[posUYcount, 2]; posUYcount--; } while (negUYcount >= 0) { y += weightedUArray[negUYcount, 3]; negUYcount--; } // 得到对应坐标点,到原图中查找像素信息 if (x < 0 || x >= height || y < 0 || y > width) { continue; } for (var channel = 0; channel < 3; channel++) { imgTPSed[i, j, channel] = interpolationFunc(img, x, y, channel); } } } return(imgTPSed); }
/// <summary> /// Smoothly adjusts the value of a <see cref="Bindable{TValue}"/> over time. /// </summary> /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> public static TransformSequence <T> TransformBindableTo <T, TValue>(this T drawable, [NotNull] Bindable <TValue> bindable, TValue newValue, double duration = 0, Easing easing = Easing.None, InterpolationFunc <TValue> interpolationFunc = null) where T : ITransformable => drawable.TransformTo(drawable.PopulateTransform(new TransformBindable <TValue, T>(bindable, interpolationFunc), newValue, duration, easing));
/// <summary> /// Smoothly adjusts the value of a <see cref="Bindable{TValue}"/> over time. /// </summary> /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> public static TransformSequence <T> TransformBindableTo <T, TValue>(this TransformSequence <T> t, [NotNull] Bindable <TValue> bindable, TValue newValue, double duration = 0, Easing easing = Easing.None, InterpolationFunc <TValue> interpolationFunc = null) where T : ITransformable => t.Append(o => o.TransformBindableTo(bindable, newValue, duration, easing, interpolationFunc));