diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/wpf-gpu-raster/src/bezier.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/wpf-gpu-raster/src/bezier.rs')
-rw-r--r-- | third_party/rust/wpf-gpu-raster/src/bezier.rs | 990 |
1 files changed, 990 insertions, 0 deletions
diff --git a/third_party/rust/wpf-gpu-raster/src/bezier.rs b/third_party/rust/wpf-gpu-raster/src/bezier.rs new file mode 100644 index 0000000000..fe54628a40 --- /dev/null +++ b/third_party/rust/wpf-gpu-raster/src/bezier.rs @@ -0,0 +1,990 @@ +// 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. +// + + +#![allow(unused_parens)] +#![allow(non_upper_case_globals)] +//+----------------------------------------------------------------------------- +// + +// +// $TAG ENGR + +// $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. +/* +#ifdef BEZIER_FLATTEN_GDI_COMPATIBLE + +// Flatten to an error of 2/3. During initial phase, use 18.14 format. + +#define TEST_MAGNITUDE_INITIAL (6 * 0x00002aa0L) + +// Error of 2/3. During normal phase, use 15.17 format. + +#define TEST_MAGNITUDE_NORMAL (TEST_MAGNITUDE_INITIAL << 3) + +#else +*/ +use crate::types::*; +/* +// Flatten to an error of 1/4. During initial phase, use 18.14 format. + +const TEST_MAGNITUDE_INITIAL: i32 = (6 * 0x00001000); + +// Error of 1/4. During normal phase, use 15.17 format. + +const TEST_MAGNITUDE_NORMAL: i32 = (TEST_MAGNITUDE_INITIAL << 3); +*/ + +// 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. + +#[derive(Default)] +struct HfdBasis32 +{ + e0: LONG, + e1: LONG, + e2: LONG, + e3: LONG, +} + +impl HfdBasis32 { + fn lParentErrorDividedBy4(&self) -> LONG { + self.e3.abs().max((self.e2 + self.e2 - self.e3).abs()) + } + + fn lError(&self) -> LONG + { + self.e2.abs().max(self.e3.abs()) + } + + fn fxValue(&self) -> INT + { + return((self.e0 + HFD32_ROUND) >> HFD32_SHIFT); + } + + fn bInit(&mut self, p1: INT, p2: INT, p3: INT, p4: INT) -> bool + { + // Change basis and convert from 28.4 to 18.14 format: + + self.e0 = (p1 ) << HFD32_INITIAL_SHIFT; + self.e1 = (p4 - p1 ) << HFD32_INITIAL_SHIFT; + + self.e2 = 6 * (p2 - p3 - p3 + p4); + self.e3 = 6 * (p1 - p2 - p2 + p3); + + if (self.lError() >= HFD32_MAX_ERROR) + { + // Large error, will require too many subdivision for this 32 bit hfd + return false; + } + + self.e2 <<= HFD32_INITIAL_SHIFT; + self.e3 <<= HFD32_INITIAL_SHIFT; + + return true; + } + + fn vLazyHalveStepSize(&mut self, cShift: LONG) + { + self.e2 = self.ExactShiftRight(self.e2 + self.e3, 1); + self.e1 = self.ExactShiftRight(self.e1 - self.ExactShiftRight(self.e2, cShift), 1); + } + + fn vSteadyState(&mut self, cShift: LONG) + { + // We now convert from 18.14 fixed format to 15.17: + + self.e0 <<= HFD32_ADDITIONAL_SHIFT; + self.e1 <<= HFD32_ADDITIONAL_SHIFT; + + let mut lShift = cShift - HFD32_ADDITIONAL_SHIFT; + + if (lShift < 0) + { + lShift = -lShift; + self.e2 <<= lShift; + self.e3 <<= lShift; + } + else + { + self.e2 >>= lShift; + self.e3 >>= lShift; + } + } + + fn vHalveStepSize(&mut self) + { + self.e2 = self.ExactShiftRight(self.e2 + self.e3, 3); + self.e1 = self.ExactShiftRight(self.e1 - self.e2, 1); + self.e3 = self.ExactShiftRight(self.e3, 2); + } + + fn vDoubleStepSize(&mut self) + { + self.e1 += self.e1 + self.e2; + self.e3 <<= 2; + self.e2 = (self.e2 << 3) - self.e3; + } + + fn vTakeStep(&mut self) + { + self.e0 += self.e1; + let lTemp = self.e2; + self.e1 += lTemp; + self.e2 += lTemp - self.e3; + self.e3 = lTemp; + } + + fn ExactShiftRight(&self, num: i32, shift: i32) -> i32 + { + // Performs a shift to the right while asserting that we're not + // losing significant bits + + assert!(num == (num >> shift) << shift); + return num >> shift; + } +} + +fn vBoundBox( + aptfx: &[POINT; 4]) -> RECT +{ + let mut left = aptfx[0].x; + let mut right = aptfx[0].x; + let mut top = aptfx[0].y; + let mut bottom = aptfx[0].y; + + for i in 1..4 + { + left = left.min(aptfx[i].x); + top = top.min(aptfx[i].y); + right = right.max(aptfx[i].x); + bottom = bottom.max(aptfx[i].y); + } + + // We make the bounds one pixel loose for the nominal width + // stroke case, which increases the bounds by half a pixel + // in every dimension: + + RECT { left: left - 16, top: top - 16, right: right + 16, bottom: bottom + 16} +} + + + +fn bIntersect( + a: &RECT, + b: &RECT) -> bool +{ + return((a.left < b.right) && + (a.top < b.bottom) && + (a.right > b.left) && + (a.bottom > b.top)); +} + +#[derive(Default)] +pub struct Bezier32 +{ + cSteps: LONG, + x: HfdBasis32, + y: HfdBasis32, + rcfxBound: RECT +} +impl Bezier32 { + +fn bInit(&mut self, + aptfxBez: &[POINT; 4], + // Pointer to 4 control points + prcfxClip: Option<&RECT>) -> bool + // Bound box of visible region (optional) +{ + let mut aptfx; + let mut cShift = 0; // Keeps track of 'lazy' shifts + + self.cSteps = 1; // Number of steps to do before reach end of curve + + self.rcfxBound = vBoundBox(aptfxBez); + + aptfx = aptfxBez.clone(); + + { + let mut fxOr; + let mut fxOffset; + + // find out if the coordinates minus the bounding box + // exceed 10 bits + fxOffset = self.rcfxBound.left; + fxOr = {aptfx[0].x -= fxOffset; aptfx[0].x}; + fxOr |= {aptfx[1].x -= fxOffset; aptfx[1].x}; + fxOr |= {aptfx[2].x -= fxOffset; aptfx[2].x}; + fxOr |= {aptfx[3].x -= fxOffset; aptfx[3].x}; + + fxOffset = self.rcfxBound.top; + fxOr |= {aptfx[0].y -= fxOffset; aptfx[0].y}; + fxOr |= {aptfx[1].y -= fxOffset; aptfx[1].y}; + fxOr |= {aptfx[2].y -= fxOffset; aptfx[2].y}; + fxOr |= {aptfx[3].y -= fxOffset; aptfx[3].y}; + + // This 32 bit cracker can only handle points in a 10 bit space: + + if ((fxOr as i64 & HFD32_MAX_SIZE) != 0) { + return false; + } + } + + if (!self.x.bInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x)) + { + return false; + } + if (!self.y.bInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y)) + { + return false; + } + + + if (match prcfxClip { None => true, Some(clip) => bIntersect(&self.rcfxBound, clip)}) + { + + loop { + let lTestMagnitude = (HFD32_INITIAL_TEST_MAGNITUDE << cShift) as LONG; + + if (self.x.lError() <= lTestMagnitude && self.y.lError() <= lTestMagnitude) { + break; + } + + cShift += 2; + self.x.vLazyHalveStepSize(cShift); + self.y.vLazyHalveStepSize(cShift); + self.cSteps <<= 1; + } + } + + self.x.vSteadyState(cShift); + self.y.vSteadyState(cShift); + +// Note that this handles the case where the initial error for +// the Bezier is already less than HFD32_TEST_MAGNITUDE: + + self.x.vTakeStep(); + self.y.vTakeStep(); + self.cSteps-=1; + + return true; +} + + +fn cFlatten(&mut self, + mut pptfx: &mut [POINT], + pbMore: &mut bool) -> i32 +{ + let mut cptfx = pptfx.len(); + assert!(cptfx > 0); + + let cptfxOriginal = cptfx; + + while { + // Return current point: + + pptfx[0].x = self.x.fxValue() + self.rcfxBound.left; + pptfx[0].y = self.y.fxValue() + self.rcfxBound.top; + pptfx = &mut pptfx[1..]; + + // 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; + } + + self.cSteps -=1 ; + self.x.vTakeStep(); + self.y.vTakeStep(); + cptfx -= 1; + cptfx != 0 + } {} + + *pbMore = true; + return cptfxOriginal as i32; +} +} + + +/////////////////////////////////////////////////////////////////////////// +// 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, +} + +impl HfdBasis64 { +fn vParentError(&self) -> LONGLONG +{ + (self.e3 << 2).abs().max(((self.e2 << 3) - (self.e3 << 2)).abs()) +} + +fn vError(&self) -> LONGLONG +{ + self.e2.abs().max(self.e3.abs()) +} + +fn fxValue(&self) -> INT +{ +// Convert from 36.28 and round: + + let mut eq = self.e0; + eq += (1 << (BEZIER64_FRACTION - 1)); + eq >>= BEZIER64_FRACTION; + return eq as LONG as INT; +} + +fn vInit(&mut self, p1: INT, p2: INT, p3: INT, p4: INT) +{ + let mut eqTmp; + let eqP2 = p2 as LONGLONG; + let eqP3 = p3 as LONGLONG; + +// e0 = p1 +// e1 = p4 - p1 +// e2 = 6(p2 - 2p3 + p4) +// e3 = 6(p1 - 2p2 + p3) + +// Change basis: + + self.e0 = p1 as LONGLONG; // e0 = p1 + self.e1 = p4 as LONGLONG; + self.e2 = eqP2; self.e2 -= eqP3; self.e2 -= eqP3; self.e2 += self.e1; // e2 = p2 - 2*p3 + p4 + self.e3 = self.e0; self.e3 -= eqP2; self.e3 -= eqP2; self.e3 += eqP3; // e3 = p1 - 2*p2 + p3 + self.e1 -= self.e0; // e1 = p4 - p1 + +// Convert to 36.28 format and multiply e2 and e3 by six: + + self.e0 <<= BEZIER64_FRACTION; + self.e1 <<= BEZIER64_FRACTION; + eqTmp = self.e2; self.e2 += eqTmp; self.e2 += eqTmp; self.e2 <<= (BEZIER64_FRACTION + 1); + eqTmp = self.e3; self.e3 += eqTmp; self.e3 += eqTmp; self.e3 <<= (BEZIER64_FRACTION + 1); +} + +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. + + let mut eqP0; + let mut eqP1; + let mut eqP2; + let mut eqP3; + +// p0 = e0 +// p1 = e0 + (6e1 - e2 - 2e3)/18 +// p2 = e0 + (12e1 - 2e2 - e3)/18 +// p3 = e0 + e1 + + eqP0 = self.e0; + +// NOTE PERF: Convert this to a multiply by 6: [andrewgo] + + eqP2 = self.e1; + eqP2 += self.e1; + eqP2 += self.e1; + eqP1 = eqP2; + eqP1 += eqP2; // 6e1 + eqP1 -= self.e2; // 6e1 - e2 + eqP2 = eqP1; + eqP2 += eqP1; // 12e1 - 2e2 + eqP2 -= self.e3; // 12e1 - 2e2 - e3 + eqP1 -= self.e3; + eqP1 -= self.e3; // 6e1 - e2 - 2e3 + +// NOTE: May just want to approximate these divides! [andrewgo] +// Or can do a 64 bit divide by 32 bit to get 32 bits right here. + + eqP1 /= 18; + eqP2 /= 18; + eqP1 += self.e0; + eqP2 += self.e0; + + eqP3 = self.e0; + eqP3 += self.e1; + +// Convert from 36.28 format with rounding: + + eqP0 += (1 << (BEZIER64_FRACTION - 1)); eqP0 >>= BEZIER64_FRACTION; *field(&mut afx[0]) = eqP0 as LONG; + eqP1 += (1 << (BEZIER64_FRACTION - 1)); eqP1 >>= BEZIER64_FRACTION; *field(&mut afx[1]) = eqP1 as LONG; + eqP2 += (1 << (BEZIER64_FRACTION - 1)); eqP2 >>= BEZIER64_FRACTION; *field(&mut afx[2]) = eqP2 as LONG; + eqP3 += (1 << (BEZIER64_FRACTION - 1)); eqP3 >>= BEZIER64_FRACTION; *field(&mut afx[3]) = eqP3 as LONG; +} + +fn vHalveStepSize(&mut self) +{ +// e2 = (e2 + e3) >> 3 +// e1 = (e1 - e2) >> 1 +// e3 >>= 2 + + self.e2 += self.e3; self.e2 >>= 3; + self.e1 -= self.e2; self.e1 >>= 1; + self.e3 >>= 2; +} + +fn vDoubleStepSize(&mut self) +{ +// e1 = 2e1 + e2 +// e3 = 4e3; +// e2 = 8e2 - e3 + + self.e1 <<= 1; self.e1 += self.e2; + self.e3 <<= 2; + self.e2 <<= 3; self.e2 -= self.e3; +} + +fn vTakeStep(&mut self) +{ + self.e0 += self.e1; + let eqTmp = self.e2; + self.e1 += self.e2; + self.e2 += eqTmp; self.e2 -= self.e3; + self.e3 = eqTmp; +} +} + +const BEZIER64_FRACTION: LONG = 28; + +// 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: + +const geqErrorHigh: LONGLONG = (6 * (1 << 15) >> (32 - BEZIER64_FRACTION)) << 32; + +/*#ifdef BEZIER_FLATTEN_GDI_COMPATIBLE + +// The following is the default 2/3 error encoded as a 36.28 number, +// multiplied by 6, and leaving 4 bits for fraction: + +const LONGLONG geqErrorLow = (LONGLONG)(4) << 32; + +#else*/ + +// The following is the default 1/4 error encoded as a 36.28 number, +// multiplied by 6, and leaving 4 bits for fraction: + +use crate::types::POINT; + +const geqErrorLow: LONGLONG = (3) << 31; + +//#endif +#[derive(Default)] +pub struct Bezier64 +{ + xLow: HfdBasis64, + yLow: HfdBasis64, + xHigh: HfdBasis64, + yHigh: HfdBasis64, + + eqErrorLow: LONGLONG, + rcfxClip: Option<RECT>, + + cStepsHigh: LONG, + cStepsLow: LONG +} + +impl Bezier64 { + +fn vInit(&mut self, + aptfx: &[POINT; 4], + // Pointer to 4 control points + prcfxVis: Option<&RECT>, + // Pointer to bound box of visible area (may be NULL) + eqError: LONGLONG) + // Fractional maximum error (32.32 format) +{ + self.cStepsHigh = 1; + self.cStepsLow = 0; + + self.xHigh.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); + self.yHigh.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); + +// Initialize error: + + self.eqErrorLow = eqError; + + self.rcfxClip = prcfxVis.cloned(); + + while (((self.xHigh.vError()) > geqErrorHigh) || + ((self.yHigh.vError()) > geqErrorHigh)) + { + self.cStepsHigh <<= 1; + self.xHigh.vHalveStepSize(); + self.yHigh.vHalveStepSize(); + } +} + +fn cFlatten( + &mut self, + mut pptfx: &mut [POINT], + pbMore: &mut bool) -> INT +{ + let mut aptfx: [POINT; 4] = Default::default(); + let mut cptfx = pptfx.len(); + let mut rcfxBound: RECT; + let cptfxOriginal = cptfx; + + assert!(cptfx > 0); + + 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: + + self.xHigh.vUntransform(&mut aptfx, |p| &mut p.x); + self.yHigh.vUntransform(&mut aptfx, |p| &mut p.y); + + self.xLow.vInit(aptfx[0].x, aptfx[1].x, aptfx[2].x, aptfx[3].x); + self.yLow.vInit(aptfx[0].y, aptfx[1].y, aptfx[2].y, aptfx[3].y); + self.cStepsLow = 1; + + if (match &self.rcfxClip { None => true, Some(clip) => {rcfxBound = vBoundBox(&aptfx); bIntersect(&rcfxBound, &clip)}}) + { + while (((self.xLow.vError()) > self.eqErrorLow) || + ((self.yLow.vError()) > self.eqErrorLow)) + { + self.cStepsLow <<= 1; + self.xLow.vHalveStepSize(); + self.yLow.vHalveStepSize(); + } + } + + // This 'if' handles the case where the initial error for the Bezier + // is already less than the target error: + + if ({self.cStepsHigh -= 1; self.cStepsHigh} != 0) + { + self.xHigh.vTakeStep(); + self.yHigh.vTakeStep(); + + if (((self.xHigh.vError()) > geqErrorHigh) || + ((self.yHigh.vError()) > geqErrorHigh)) + { + self.cStepsHigh <<= 1; + self.xHigh.vHalveStepSize(); + self.yHigh.vHalveStepSize(); + } + + while (!(self.cStepsHigh & 1 != 0) && + ((self.xHigh.vParentError()) <= geqErrorHigh) && + ((self.yHigh.vParentError()) <= geqErrorHigh)) + { + self.xHigh.vDoubleStepSize(); + self.yHigh.vDoubleStepSize(); + self.cStepsHigh >>= 1; + } + } + } + + self.xLow.vTakeStep(); + self.yLow.vTakeStep(); + + pptfx[0].x = self.xLow.fxValue(); + pptfx[0].y = self.yLow.fxValue(); + pptfx = &mut pptfx[1..]; + + self.cStepsLow-=1; + if (self.cStepsLow == 0 && self.cStepsHigh == 0) + { + *pbMore = false; + + // '+1' because we haven't decremented 'cptfx' yet: + + return(cptfxOriginal - cptfx + 1) as INT; + } + + if ((self.xLow.vError() > self.eqErrorLow) || + (self.yLow.vError() > self.eqErrorLow)) + { + self.cStepsLow <<= 1; + self.xLow.vHalveStepSize(); + self.yLow.vHalveStepSize(); + } + + while (!(self.cStepsLow & 1 != 0) && + ((self.xLow.vParentError()) <= self.eqErrorLow) && + ((self.yLow.vParentError()) <= self.eqErrorLow)) + { + self.xLow.vDoubleStepSize(); + self.yLow.vDoubleStepSize(); + self.cStepsLow >>= 1; + } + cptfx -= 1; + cptfx != 0 + } {}; + + *pbMore = true; + return(cptfxOriginal) as INT; +} +} + +//+----------------------------------------------------------------------------- +// +// 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: + pub fn new(aptfxBez: &[POINT; 4], prcfxClip: Option<&RECT>) -> Self { + let mut bez32 = Bezier32::default(); + let bBez32 = bez32.bInit(aptfxBez, prcfxClip); + if bBez32 { + CMILBezier::Bezier32(bez32) + } else { + let mut 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. + pub fn Flatten( &mut self, + pptfx: &mut [POINT], + pbMore: &mut bool) -> INT { + match self { + CMILBezier::Bezier32(bez) => bez.cFlatten(pptfx, pbMore), + CMILBezier::Bezier64(bez) => bez.cFlatten(pptfx, pbMore) + } + } +} + +#[test] +fn flatten() { + let curve: [POINT; 4] = [ + POINT{x: 1715, y: 6506}, + POINT{x: 1692, y: 6506}, + POINT{x: 1227, y: 5148}, + POINT{x: 647, y: 5211}]; + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 32] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result, &mut more); + assert_eq!(count, 21); + assert_eq!(more, false); +} + +#[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}]; + + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 8] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result[..5], &mut more); + assert_eq!(count, 5); + assert_eq!(more, true); + let count = bez.Flatten(&mut result[5..], &mut more); + assert_eq!(count, 3); + assert_eq!(more, false); + + let mut bez = CMILBezier::new(&curve, None); + let mut full_result: [POINT; 8] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut full_result, &mut more); + assert_eq!(count, 8); + assert_eq!(more, false); + assert!(result == full_result); +} + +#[test] +fn flatten32() { + let curve: [POINT; 4] = [ + POINT{x: 100, y: 100}, + POINT{x: 110, y: 100}, + POINT{x: 110, y: 110}, + POINT{x: 110, y: 100}]; + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 32] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result, &mut more); + assert_eq!(count, 3); + assert_eq!(more, false); +} + +#[test] +fn flatten32_double_step_size() { + let curve: [POINT; 4] = [ + POINT{x: 1761, y: 8152}, + POINT{x: 1761, y: 8152}, + POINT{x: 1750, y: 8355}, + POINT{x: 1795, y: 8445}]; + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 32] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result, &mut more); + assert_eq!(count, 7); + assert_eq!(more, false); +} + +#[test] +fn bezier64_init_high_num_steps() { + let curve: [POINT; 4] = [ + POINT{x: 33, y: -1}, + POINT{x: -1, y: -1}, + POINT{x: -1, y: -16385}, + POINT{x: -226, y: 10}]; + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 32] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result, &mut more); + assert_eq!(count, 32); + assert_eq!(more, true); +} + +#[test] +fn bezier64_high_error() { + let curve: [POINT; 4] = [ + POINT{x: -1, y: -1}, + POINT{x: -4097, y: -1}, + POINT{x: 65471, y: -256}, + POINT{x: -1, y: 0}]; + let mut bez = CMILBezier::new(&curve, None); + let mut result: [POINT; 32] = Default::default(); + let mut more: bool = false; + let count = bez.Flatten(&mut result, &mut more); + assert_eq!(count, 32); + assert_eq!(more, true); +}
\ No newline at end of file |