// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information.
//+----------------------------------------------------------------------------- // // class Bezier32 // // Bezier cracker. // // A hybrid cubic Bezier curve flattener based on KirkO's error factor. // Generates line segments fast without using the stack. Used to flatten a // path. // // For an understanding of the methods used, see: // // Kirk Olynyk, "..." // Goossen and Olynyk, "System and Method of Hybrid Forward // Differencing to Render Bezier Splines" // Lien, Shantz and Vaughan Pratt, "Adaptive Forward Differencing for // Rendering Curves and Surfaces", Computer Graphics, July 1987 // Chang and Shantz, "Rendering Trimmed NURBS with Adaptive Forward // Differencing", Computer Graphics, August 1988 // Foley and Van Dam, "Fundamentals of Interactive Computer Graphics" // // Public Interface: // bInit(pptfx) - pptfx points to 4 control points of // Bezier. Current point is set to the first // point after the start-point. // Bezier32(pptfx) - Constructor with initialization. // vGetCurrent(pptfx) - Returns current polyline point. // bCurrentIsEndPoint() - TRUE if current point is end-point. // vNext() - Moves to next polyline point. //
// $Module: win_mil_graphics_geometry // $Keywords: // // $Description: // Class for flattening a bezier. // // $ENDTAG // //------------------------------------------------------------------------------
// First conversion from original 28.4 to 18.14 format const HFD32_INITIAL_SHIFT: i32 = 10;
// Second conversion to 15.17 format const HFD32_ADDITIONAL_SHIFT: i32 = 3;
// BEZIER_FLATTEN_GDI_COMPATIBLE: // // Don't turn on this switch without testing carefully. It's more for // documentation's sake - to show the values that GDI used - for an error // tolerance of 2/3.
// It turns out that 2/3 produces very noticable artifacts on antialiased lines, // so we want to use 1/4 instead. /* #ifdefBEZIER_FLATTEN_GDI_COMPATIBLE
// Flatten to an error of 2/3. During initial phase, use 18.14 format.
#defineTEST_MAGNITUDE_INITIAL(6*0x00002aa0L)
// Error of 2/3. During normal phase, use 15.17 format.
// I have modified the constants for HFD32 as part of fixing accuracy errors // (Bug 816015). Something similar could be done for the 64 bit hfd, but it ain't // broke so I'd rather not fix it.
// The shift to the steady state 15.17 format const HFD32_SHIFT: LONG = HFD32_INITIAL_SHIFT + HFD32_ADDITIONAL_SHIFT;
// Added to output numbers before rounding back to original representation const HFD32_ROUND: LONG = 1 << (HFD32_SHIFT - 1);
// The error is tested on max(|e2|, |e3|), which represent 6 times the actual error. // The flattening tolerance is hard coded to 1/4 in the original geometry space, // which translates to 4 in 28.4 format. So 6 times that is:
const HFD32_TOLERANCE: LONGLONG = 24;
// During the initial phase, while working in 18.14 format const HFD32_INITIAL_TEST_MAGNITUDE: LONGLONG = HFD32_TOLERANCE << HFD32_INITIAL_SHIFT;
// During the steady state, while working in 15.17 format const HFD32_TEST_MAGNITUDE: LONGLONG = HFD32_INITIAL_TEST_MAGNITUDE << HFD32_ADDITIONAL_SHIFT;
// We will stop halving the segment with basis e1, e2, e3, e4 when max(|e2|, |e3|) // is less than HFD32_TOLERANCE. The operation e2 = (e2 + e3) >> 3 in vHalveStepSize() may // eat up 3 bits of accuracy. HfdBasis32 starts off with a pad of HFD32_SHIFT zeros, so // we can stay exact up to HFD32_SHIFT/3 subdivisions. Since every subdivision is guaranteed // to shift max(|e2|, |e3|) at least by 2, we will subdivide no more than n times if the // initial max(|e2|, |e3|) is less than than HFD32_TOLERANCE << 2n. But if the initial // max(|e2|, |e3|) is greater than HFD32_TOLERANCE >> (HFD32_SHIFT / 3) then we may not be // able to flatten with the 32 bit hfd, so we need to resort to the 64 bit hfd.
const HFD32_MAX_ERROR: INT = (HFD32_TOLERANCE as i32) << ((2 * HFD32_INITIAL_SHIFT) / 3);
// The maximum size of coefficients that can be handled by HfdBasis32. const HFD32_MAX_SIZE: LONGLONG = 0xffffc000;
// Michka 9/12/03: I found this number in the the body of the code witout any explanation. // My analysis suggests that we could get away with larger numbers, but if I'm wrong we // could be in big trouble, so let us stay conservative. // // In bInit() we subtract Min(Bezier coeffients) from the original coefficients, so after // that 0 <= coefficients <= Bound, and the test will be Bound < HFD32_MAX_SIZE. When // switching to the HFD basis in bInit(): // * e0 is the first Bezier coeffient, so abs(e0) <= Bound. // * e1 is a difference of non-negative coefficients so abs(e1) <= Bound. // * e2 and e3 can be written as 12*(p - (q + r)/2) where p,q and r are coefficients. // 0 <=(q + r)/2 <= Bound, so abs(p - (q + r)/2) <= 2*Bound, hence // abs(e2), abs(e3) <= 12*Bound. // // During vLazyHalveStepSize we add e2 + e3, resulting in absolute value <= 24*Bound. // Initially HfdBasis32 shifts the numbers by HFD32_INITIAL_SHIFT, so we need to handle // 24*bounds*(2^HFD32_SHIFT), and that needs to be less than 2^31. So the bounds need to // be less than 2^(31-HFD32_INITIAL_SHIFT)/24). // // For speed, the algorithm uses & rather than < for comparison. To facilitate that we // replace 24 by 32=2^5, and then the binary representation of the number is of the form // 0...010...0 with HFD32_SHIFT+5 trailing zeros. By subtracting that from 2^32 = 0xffffffff+1 // we get a number that is 1..110...0 with the same number of trailing zeros, and that can be // used with an & for comparison. So the number should be: // // 0xffffffffL - (1L << (31 - HFD32_INITIAL_SHIFT - 5)) + 1 = (1L << 16) + 1 = 0xffff0000 // // For the current values of HFD32_INITIAL_SHIFT=10 and HFD32_ADDITIONAL_SHIFT=3, the steady // state doesn't pose additional requirements, as shown below. // // For some reason the current code uses 0xfffc0000 = (1L << 14) + 1. // // Here is why the steady state doesn't pose additional requirements: // // In vSteadyState we multiply e0 and e1 by 8, so the requirement is Bounds*2^13 < 2^31, // or Bounds < 2^18, less stringent than the above. // // In vLazyHalveStepSize we cut the error down by subdivision, making abs(e2) and abs(e3) // less than HFD32_TEST_MAGNITUDE = 24*2^13, well below 2^31. // // During all the steady-state operations - vTakeStep, vHalveStepSize and vDoubleStepSize, // e0 is on the curve and e1 is a difference of 2 points on the curve, so // abs(e0), abs(e1) < Bounds * 2^13, which requires Bound < 2^(31-13) = 2^18. e2 and e3 // are errors, kept below 6*HFD32_TEST_MAGNITUDE = 216*2^13. Details: // // In vTakeStep e2 = 2e2 - e3 keeps abs(e2) < 3*HFD32_TEST_MAGNITUDE = 72*2^13, // well below 2^31 // // In vHalveStepSize we add e2 + e3 when their absolute is < 3*HFD32_TEST_MAGNITUDE (because // this comes after a step), so that keeps the result below 6*HFD32_TEST_MAGNITUDE = 216*2^13. // // In vDoubleStepSize we know that abs(e2), abs(e3) < HFD32_TEST_MAGNITUDE/4, otherwise we // would not have doubled the step.
// If cSteps == 0, that was the end point in the curve!
if (self.cSteps == 0)
{
*pbMore = false;
// '+1' because we haven't decremented 'cptfx' yet:
return(cptfxOriginal - cptfx + 1) as i32;
}
// Okay, we have to step:
if (self.x.lError().max(self.y.lError()) > HFD32_TEST_MAGNITUDE as LONG)
{ self.x.vHalveStepSize(); self.y.vHalveStepSize(); self.cSteps <<= 1;
}
// We are here after vTakeStep. Before that the error max(|e2|,|e3|) was less // than HFD32_TEST_MAGNITUDE. vTakeStep changed e2 to 2e2-e3. Since // |2e2-e3| < max(|e2|,|e3|) << 2 and vHalveStepSize is guaranteed to reduce // max(|e2|,|e3|) by >> 2, no more than one subdivision should be required to // bring the new max(|e2|,|e3|) back to within HFD32_TEST_MAGNITUDE, so:
assert!(self.x.lError().max(self.y.lError()) <= HFD32_TEST_MAGNITUDE as LONG);
while (!(self.cSteps & 1 != 0) && self.x.lParentErrorDividedBy4() <= (HFD32_TEST_MAGNITUDE as LONG >> 2) && self.y.lParentErrorDividedBy4() <= (HFD32_TEST_MAGNITUDE as LONG >> 2))
{ self.x.vDoubleStepSize(); self.y.vDoubleStepSize(); self.cSteps >>= 1;
}
/////////////////////////////////////////////////////////////////////////// // Bezier64 // // All math is done using 64 bit fixed numbers in a 36.28 format. // // All drawing is done in a 31 bit space, then a 31 bit window offset // is applied. In the initial transform where we change to the HFD // basis, e2 and e3 require the most bits precision: e2 = 6(p2 - 2p3 + p4). // This requires an additional 4 bits precision -- hence we require 36 bits // for the integer part, and the remaining 28 bits is given to the fraction. // // In rendering a Bezier, every 'subdivide' requires an extra 3 bits of // fractional precision. In order to be reversible, we can allow no // error to creep in. Since a INT coordinate is 32 bits, and we // require an additional 4 bits as mentioned above, that leaves us // 28 bits fractional precision -- meaning we can do a maximum of // 9 subdivides. Now, the maximum absolute error of a Bezier curve in 27 // bit integer space is 2^29 - 1. But 9 subdivides reduces the error by a // guaranteed factor of 2^18, meaning we can subdivide down only to an error // of 2^11 before we overflow, when in fact we want to reduce error to less // than 1. // // So what we do is HFD until we hit an error less than 2^11, reverse our // basis transform to get the four control points of this smaller curve // (rounding in the process to 32 bits), then invoke another copy of HFD // on the reduced Bezier curve. We again have enough precision, but since // its starting error is less than 2^11, we can reduce error to 2^-7 before // overflowing! We'll start a low HFD after every step of the high HFD. //////////////////////////////////////////////////////////////////////////// #[derive(Default)] struct HfdBasis64
{
e0: LONGLONG,
e1: LONGLONG,
e2: LONGLONG,
e3: LONGLONG,
}
fn vUntransform<F: Fn(&mut POINT) -> &mut LONG>(&self,
afx: &mut [POINT; 4], field: F)
{ // Declare some temps to hold our operations, since we can't modify e0..e3.
// The following is our 2^11 target error encoded as a 36.28 number // (don't forget the additional 4 bits of fractional precision!) and // the 6 times error multiplier:
while { if (self.cStepsLow == 0)
{ // Optimization that if the bound box of the control points doesn't // intersect with the bound box of the visible area, render entire // curve as a single line:
//+----------------------------------------------------------------------------- // // class CMILBezier // // Bezier cracker. Flattens any Bezier in our 28.4 device space down to a // smallest 'error' of 2^-7 = 0.0078. Will use fast 32 bit cracker for small // curves and slower 64 bit cracker for big curves. // // Public Interface: // vInit(aptfx, prcfxClip, peqError) // - pptfx points to 4 control points of Bezier. The first point // retrieved by bNext() is the the first point in the approximation // after the start-point. // // - prcfxClip is an optional pointer to the bound box of the visible // region. This is used to optimize clipping of Bezier curves that // won't be seen. Note that this value should account for the pen's // width! // // - optional maximum error in 32.32 format, corresponding to Kirko's // error factor. // // bNext(pptfx) // - pptfx points to where next point in approximation will be // returned. Returns FALSE if the point is the end-point of the // curve. // pub (crate) enum CMILBezier
{
Bezier64(Bezier64),
Bezier32(Bezier32)
}
impl CMILBezier { // All coordinates must be in 28.4 format: pubfn new(aptfxBez: &[POINT; 4], prcfxClip: Option<&RECT>) -> Self { letmut bez32 = Bezier32::default(); let bBez32 = bez32.bInit(aptfxBez, prcfxClip); if bBez32 {
CMILBezier::Bezier32(bez32)
} else { letmut bez64 = Bezier64::default();
bez64.vInit(aptfxBez, prcfxClip, geqErrorLow);
CMILBezier::Bezier64(bez64)
}
}
// Returns the number of points filled in. This will never be zero. // // The last point returned may not be exactly the last control // point. The workaround is for calling code to add an extra // point if this is the case. pubfn Flatten( &mutself,
pptfx: &mut [POINT],
pbMore: &mut bool) -> INT { matchself {
CMILBezier::Bezier32(bez) => bez.cFlatten(pptfx, pbMore),
CMILBezier::Bezier64(bez) => bez.cFlatten(pptfx, pbMore)
}
}
}
#[test] fn split_flatten32() { // make sure that flattening a curve into two small buffers matches // doing it into a large buffer let curve: [POINT; 4] = [
POINT{x: 1795, y: 8445},
POINT{x: 1795, y: 8445},
POINT{x: 1908, y: 8683},
POINT{x: 2043, y: 8705}];
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.